| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -61,7 +61,6 @@ static/hash | |||
| 61 | 61 | static/requirejs | |
| 62 | 62 | static/target | |
| 63 | 63 | ||
| 64 | - static/src/systemjs-bundle-config.js | ||
| 65 | 64 | static/src/stylesheets/icons/* | |
| 66 | 65 | ||
| 67 | 66 | # Eclipse # | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,7 @@ module.exports = function (grunt) { | |||
| 10 | 10 | isDev: (grunt.option('dev') !== undefined) ? Boolean(grunt.option('dev')) : process.env.GRUNT_ISDEV === '1', | |
| 11 | 11 | singleRun: grunt.option('single-run') !== false, | |
| 12 | 12 | staticTargetDir: './static/target/', | |
| 13 | + staticSrcDir: './static/src/', | ||
| 13 | 14 | staticHashDir: './static/hash/', | |
| 14 | 15 | testConfDir: './static/test/javascripts/conf/', | |
| 15 | 16 | requirejsDir: './static/requirejs', | |
@@ -111,7 +112,7 @@ module.exports = function (grunt) { | |||
| 111 | 112 | grunt.registerTask('compile:fonts', ['mkdir:fontsTarget', 'webfontjson']); | |
| 112 | 113 | grunt.registerTask('compile:flash', ['copy:flash']); | |
| 113 | 114 | grunt.registerTask('compile:inlineSvgs', ['copy:inlineSVGs', 'svgmin:inlineSVGs']); | |
| 114 | - grunt.registerTask('compile:conf', ['copy:headJs', 'copy:inlineCss', 'copy:assetMap', 'compile:inlineSvgs', 'uglify:conf']); | ||
| 115 | + grunt.registerTask('compile:conf', ['copy:headJs', 'copy:inlineCss', 'copy:assetMaps', 'compile:inlineSvgs', 'uglify:conf']); | ||
| 115 | 116 | grunt.registerTask('compile', [ | |
| 116 | 117 | 'compile:css', | |
| 117 | 118 | 'compile:js', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -77,7 +77,7 @@ | |||
| 77 | 77 | <link href='//fonts.googleapis.com/css?family=Open+Sans:800' rel='stylesheet' type='text/css'> | |
| 78 | 78 | ||
| 79 | 79 | <script> | |
| 80 | - @Html(Static.js.systemJsSetupFragment) | ||
| 80 | + @Html(Static.systemJs.setupFragment) | ||
| 81 | 81 | ||
| 82 | 82 | // Our app assumes a normal env | |
| 83 | 83 | window.guardian = { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,21 +1,59 @@ | |||
| 1 | 1 | package controllers | |
| 2 | 2 | ||
| 3 | - import common.ExecutionContexts | ||
| 4 | - import model.Cached | ||
| 5 | - import play.api.mvc.{Action, Controller} | ||
| 3 | + import model.{MetaData, Cached} | ||
| 4 | + import com.gu.contentapi.client.model.Crossword | ||
| 5 | + import common.{Logging, Edition, ExecutionContexts} | ||
| 6 | + import conf.LiveContentApi | ||
| 7 | + import crosswords.CrosswordData | ||
| 8 | + import play.api.mvc.{RequestHeader, Result, Action, Controller} | ||
| 6 | 9 | ||
| 7 | - object WebAppController extends Controller with ExecutionContexts { | ||
| 10 | + import scala.concurrent.Future | ||
| 11 | + | ||
| 12 | + class OfflinePage(val crossword: CrosswordData) extends MetaData { | ||
| 13 | + lazy val id: String = "offline-page" | ||
| 14 | + lazy val section: String = "" | ||
| 15 | + lazy val analyticsName: String = id | ||
| 16 | + lazy val webTitle: String = "Unable to connect to the Internet" | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + object WebAppController extends Controller with ExecutionContexts with Logging { | ||
| 8 | 20 | ||
| 9 | 21 | def serviceWorker() = Action { implicit request => | |
| 10 | 22 | Cached(3600) { | |
| 11 | - conf.Switches.NotificationsSwitch.isSwitchedOn match { | ||
| 12 | - case true => Ok(templates.js.serviceWorker()) | ||
| 13 | - case false => NotFound | ||
| 23 | + if (conf.Switches.NotificationsSwitch.isSwitchedOn || conf.Switches.OfflinePageSwitch.isSwitchedOn) { | ||
| 24 | + Ok(templates.js.serviceWorker()) | ||
| 25 | + } else { | ||
| 26 | + NotFound | ||
| 14 | 27 | } | |
| 15 | 28 | } | |
| 16 | 29 | } | |
| 17 | 30 | ||
| 18 | 31 | def manifest() = Action { | |
| 19 | 32 | Cached(3600) { Ok(templates.js.webAppManifest()) } | |
| 20 | 33 | } | |
| 34 | + | ||
| 35 | + | ||
| 36 | + protected def withCrossword(crosswordType: String, id: Int)(f: (Crossword) => Result)(implicit request: RequestHeader): Future[Result] = { | ||
| 37 | + LiveContentApi.getResponse(LiveContentApi.item(s"crosswords/series/quick", Edition(request)).showFields("all")).map { response => | ||
| 38 | + val maybeCrossword = for { | ||
| 39 | + content <- response.results.headOption | ||
| 40 | + crossword <- content.crossword } | ||
| 41 | + yield f(crossword) | ||
| 42 | + maybeCrossword getOrElse InternalServerError("Crossword response from Content API invalid.") | ||
| 43 | + } recover { case e => | ||
| 44 | + log.error("Content API query returned an error.", e) | ||
| 45 | + InternalServerError("Content API query returned an error.") | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + def offlinePage() = Action.async { implicit request => | ||
| 50 | + if (conf.Switches.OfflinePageSwitch.isSwitchedOn) { | ||
| 51 | + withCrossword("quick", 14127) { crossword => | ||
| 52 | + Cached(60)(Ok(views.html.offlinePage( | ||
| 53 | + new OfflinePage(CrosswordData.fromCrossword(crossword))))) | ||
| 54 | + } | ||
| 55 | + } else { | ||
| 56 | + Future(NotFound) | ||
| 57 | + } | ||
| 58 | + } | ||
| 21 | 59 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,6 +5,74 @@ | |||
| 5 | 5 | /*global self*/ | |
| 6 | 6 | /*global clients*/ | |
| 7 | 7 | ||
| 8 | + // | ||
| 9 | + // Offline page | ||
| 10 | + // | ||
| 11 | + | ||
| 12 | + var staticCacheName = 'static'; | ||
| 13 | + | ||
| 14 | + var getISODate = function () { return new Date().toISOString().split('T')[0]; }; | ||
| 15 | + | ||
| 16 | + var updateCache = function () { | ||
| 17 | + return caches.open([getISODate(), staticCacheName].join('-')).then(function (cache) { | ||
| 18 | + return cache.addAll([ | ||
| 19 | + '/offline-page', | ||
| 20 | + '@Static("stylesheets/head.content.css")', | ||
| 21 | + '@Static("stylesheets/content.css")', | ||
| 22 | + '@Static("stylesheets/print.css")', | ||
| 23 | + // Crossword pages use jspm | ||
| 24 | + '@StaticJspm("javascripts/core.js")', | ||
| 25 | + '@StaticJspm("javascripts/bootstraps/app.js")', | ||
| 26 | + '@StaticJspm("javascripts/es6/bootstraps/crosswords.js")' | ||
| 27 | + ]); | ||
| 28 | + }); | ||
| 29 | + }; | ||
| 30 | + | ||
| 31 | + var deleteOldCaches = function () { | ||
| 32 | + return caches.keys().then(function (keys) { | ||
| 33 | + return Promise.all( | ||
| 34 | + keys.map(function (key) { | ||
| 35 | + if (!keyMatchesTodaysCache(key)) { | ||
| 36 | + return caches.delete(key); | ||
| 37 | + } | ||
| 38 | + }) | ||
| 39 | + ); | ||
| 40 | + }) | ||
| 41 | + }; | ||
| 42 | + | ||
| 43 | + var keyMatchesTodaysCache = function (key) { | ||
| 44 | + return new RegExp('^' + getISODate() + '-').test(key); | ||
| 45 | + }; | ||
| 46 | + | ||
| 47 | + self.addEventListener('install', function (event) { | ||
| 48 | + event.waitUntil(updateCache()); | ||
| 49 | + }); | ||
| 50 | + | ||
| 51 | + this.addEventListener('fetch', function (event) { | ||
| 52 | + caches.keys().then(function (keys) { | ||
| 53 | + var isUpdated = keys.some(keyMatchesTodaysCache); | ||
| 54 | + | ||
| 55 | + if (!isUpdated) { | ||
| 56 | + updateCache().then(deleteOldCaches); | ||
| 57 | + } | ||
| 58 | + }); | ||
| 59 | + | ||
| 60 | + event.respondWith( | ||
| 61 | + fetch(event.request) | ||
| 62 | + .catch(function () { | ||
| 63 | + // If a request is cached, respond with that. Otherwise respond | ||
| 64 | + // with the shell, whose subresources will be in the cache. | ||
| 65 | + return caches.match(event.request).then(function (response) { | ||
| 66 | + return response || caches.match('/offline-page'); | ||
| 67 | + }) | ||
| 68 | + }) | ||
| 69 | + ); | ||
| 70 | + }); | ||
| 71 | + | ||
| 72 | + // | ||
| 73 | + // Push notifications | ||
| 74 | + // | ||
| 75 | + | ||
| 8 | 76 | var findInArray = function (array, fn) { | |
| 9 | 77 | for (var i = array.length - 1; i >= 0; i--) { | |
| 10 | 78 | var value = array[i]; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,71 @@ | |||
| 1 | + @(offlinePage: controllers.OfflinePage)(implicit request: RequestHeader) | ||
| 2 | + | ||
| 3 | + @import play.api.libs.json._ | ||
| 4 | + | ||
| 5 | + <!DOCTYPE html> | ||
| 6 | + <html id="js-context" class="js-off is-not-modern id--signed-out" data-page-path="@request.path"> | ||
| 7 | + | ||
| 8 | + <head> | ||
| 9 | + <meta charset="utf-8" /> | ||
| 10 | + <title>@views.support.Title(offlinePage)</title> | ||
| 11 | + | ||
| 12 | + @fragments.metaData(offlinePage) | ||
| 13 | + | ||
| 14 | + @* get the stylesheets downloading ASAP *@ | ||
| 15 | + @fragments.stylesheetLinks(None) | ||
| 16 | + | ||
| 17 | + @* polyfill, feature detect etc before we try and use the stylesheets *@ | ||
| 18 | + @fragments.javaScriptPreFlight(offlinePage) | ||
| 19 | + | ||
| 20 | + @* try and load fonts before we use the stylesheets *@ | ||
| 21 | + @fragments.fonts() | ||
| 22 | + | ||
| 23 | + @* start trying to use the stylesheets *@ | ||
| 24 | + @fragments.stylesheetLinksEnable() | ||
| 25 | + | ||
| 26 | + <script> | ||
| 27 | + @Html(Static.systemJs.setupFragment) | ||
| 28 | + | ||
| 29 | + @* Service worker only caches the bundles, so the offline page can only ask for those. | ||
| 30 | + For production, we rely on bundles from systemJsSetup *@ | ||
| 31 | + @if(play.Play.isDev()) { | ||
| 32 | + System.config({ bundles: @Html(Static.systemJs.bundleConfig) }); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Html(templates.headerInlineJS.js.bootSystemJS(offlinePage).body) | ||
| 36 | + </script> | ||
| 37 | + </head> | ||
| 38 | + <body | ||
| 39 | + id="top" | ||
| 40 | + itemscope itemtype="http://schema.org/WebPage"> | ||
| 41 | + | ||
| 42 | + <a class="u-h skip" href="#maincontent" data-link-name="skip : main content">Skip to main content</a> | ||
| 43 | + | ||
| 44 | + @fragments.header(offlinePage) | ||
| 45 | + | ||
| 46 | + <div class="l-side-margins"> | ||
| 47 | + <article id="crossword" class="content content--article tonal tonal--tone-news" role="main"> | ||
| 48 | + <div class="content__head tonal__head tonal__head--tone-news"> | ||
| 49 | + <div class="gs-container"> | ||
| 50 | + <div class="content__main-column content__main-column--single-column"> | ||
| 51 | + <h1 class="content__headline">Unable to connect to the Internet</h1> | ||
| 52 | + <p>In the meantime, how about a quick crossword?</p> | ||
| 53 | + <button class="button js-open-crossword-btn">Open crossword</button> | ||
| 54 | + </div> | ||
| 55 | + <div class="js-crossword-container is-hidden u-baseline-top"> | ||
| 56 | + <h2><a href="/crosswords/@offlinePage.crossword.id">@offlinePage.crossword.name</a></h2> | ||
| 57 | + <div class="js-crossword" | ||
| 58 | + data-crossword-data="@Json.stringify(Json.toJson(offlinePage.crossword))"> | ||
| 59 | + </div> | ||
| 60 | + </div> | ||
| 61 | + </div> | ||
| 62 | + </div> | ||
| 63 | + </article> | ||
| 64 | + </div> | ||
| 65 | + | ||
| 66 | + @fragments.footer(offlinePage) | ||
| 67 | + | ||
| 68 | + @fragments.footerJavaScript() | ||
| 69 | + | ||
| 70 | + </body> | ||
| 71 | + </html> | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,7 +29,7 @@ | |||
| 29 | 29 | ||
| 30 | 30 | @bootSystemJs() = { | |
| 31 | 31 | <script> | |
| 32 | - @Html(Static.js.systemJsSetupFragment) | ||
| 32 | + @Html(Static.systemJs.setupFragment) | ||
| 33 | 33 | ||
| 34 | 34 | // Bracket notation for IE8 (import is reserved) | |
| 35 | 35 | System['import']('core').then(function () { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -33,7 +33,8 @@ GET /crosswords/optin | |||
| 33 | 33 | GET /crosswords/optout controllers.CrosswordPreferencesController.crosswordsOptOut | |
| 34 | 34 | ||
| 35 | 35 | # Web App paths | |
| 36 | - GET /2015-05-28-2-service-worker.js controllers.WebAppController.serviceWorker() | ||
| 36 | + GET /offline-page controllers.WebAppController.offlinePage() | ||
| 37 | + GET /service-worker.js controllers.WebAppController.serviceWorker() | ||
| 37 | 38 | GET /2015-06-24-manifest.json controllers.WebAppController.manifest() | |
| 38 | 39 | ||
| 39 | 40 | GET /$path<.+/\d\d\d\d/\w\w\w/\d\d> controllers.AllIndexController.on(path) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -85,21 +85,43 @@ var writeBundlesToDisk = function (bundles) { | |||
| 85 | 85 | }); | |
| 86 | 86 | }; | |
| 87 | 87 | ||
| 88 | + var createModuleExpressionToFilenameMap = function (bundles) { | ||
| 89 | + return Promise.all(bundles.map(function (bundle) { | ||
| 90 | + return System.normalize(bundle.id).then(function (absolutePath) { | ||
| 91 | + absolutePath = absolutePath.replace('file://', ''); | ||
| 92 | + var pathRelativeToDir = path.relative(__dirname, absolutePath); | ||
| 93 | + return path.relative(jspmBaseUrl, pathRelativeToDir); | ||
| 94 | + }).then(function (relativeFilename) { | ||
| 95 | + return { | ||
| 96 | + expression: bundle.id, | ||
| 97 | + relativeFilename: relativeFilename | ||
| 98 | + }; | ||
| 99 | + }); | ||
| 100 | + })).then(function (modules) { | ||
| 101 | + return modules.reduce(function (accumulator, module) { | ||
| 102 | + accumulator[module.expression] = module.relativeFilename; | ||
| 103 | + return accumulator; | ||
| 104 | + }, {}); | ||
| 105 | + }); | ||
| 106 | + }; | ||
| 107 | + | ||
| 88 | 108 | var writeBundlesConfig = function (bundles) { | |
| 89 | - var bundlesConfig = bundles.reduce(function (accumulator, bundle) { | ||
| 90 | - accumulator[bundle.uri.replace('.js', '')] = [bundle.id]; | ||
| 91 | - return accumulator; | ||
| 92 | - }, {}); | ||
| 93 | - var configFilePath = path.join(jspmBaseUrl, 'systemjs-bundle-config.js'); | ||
| 94 | - var configFileData = 'System.config({ bundles: ' + JSON.stringify(bundlesConfig, null, '\t') + ' })'; | ||
| 95 | - console.log('writing to %s', configFilePath); | ||
| 96 | - fs.writeFileSync(configFilePath, configFileData); | ||
| 109 | + return createModuleExpressionToFilenameMap(bundles).then(function (map) { | ||
| 110 | + var bundlesConfig = bundles.reduce(function (accumulator, bundle) { | ||
| 111 | + accumulator[map[bundle.id]] = bundle.uri; | ||
| 112 | + return accumulator; | ||
| 113 | + }, {}); | ||
| 114 | + var filePath = path.join(prefixPath, 'assets/jspm-assets.map'); | ||
| 115 | + var fileData = JSON.stringify(bundlesConfig, null, '\t'); | ||
| 116 | + console.log('writing to %s', filePath); | ||
| 117 | + fs.writeFileSync(filePath, fileData); | ||
| 118 | + }); | ||
| 97 | 119 | }; | |
| 98 | 120 | ||
| 99 | 121 | Promise.all(bundleConfigs.map(createBundle)) | |
| 100 | 122 | .then(function (bundles) { | |
| 101 | 123 | writeBundlesToDisk(bundles); | |
| 102 | - writeBundlesConfig(bundles); | ||
| 124 | + return writeBundlesConfig(bundles); | ||
| 103 | 125 | }) | |
| 104 | 126 | .catch(function (error) { | |
| 105 | 127 | console.error(error.stack); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,7 +5,7 @@ import java.net.URL | |||
| 5 | 5 | import common.{Logging, RelativePathEscaper} | |
| 6 | 6 | import conf.Configuration | |
| 7 | 7 | import org.apache.commons.io.IOUtils | |
| 8 | - import play.api.libs.json.{JsObject, JsString, Json} | ||
| 8 | + import play.api.libs.json._ | ||
| 9 | 9 | import play.api.{Mode, Play} | |
| 10 | 10 | ||
| 11 | 11 | import scala.collection.concurrent.{Map => ConcurrentMap, TrieMap} | |
@@ -144,14 +144,30 @@ class Assets(base: String, assetMap: String = "assets/assets.map") extends Loggi | |||
| 144 | 144 | private def inlineJs(path: String): String = IOUtils.toString(AssetFinder(path)) | |
| 145 | 145 | ||
| 146 | 146 | val curl: String = RelativePathEscaper.escapeLeadingDotPaths(inlineJs("assets/curl-domReady.js")) | |
| 147 | + } | ||
| 148 | + | ||
| 149 | + object systemJs { | ||
| 150 | + private def contents(path: String): String = IOUtils.toString(AssetFinder(path)) | ||
| 151 | + | ||
| 152 | + val main: String = contents("assets/system.src.js") | ||
| 153 | + val polyfills: String = contents("assets/system-polyfills.src.js") | ||
| 154 | + val appConfig: String = contents("assets/systemjs-config.js") | ||
| 155 | + val normalize: String = contents("assets/systemjs-normalize.js") | ||
| 156 | + | ||
| 157 | + lazy val setupFragment: String = templates.js.systemJsSetup().body | ||
| 147 | 158 | ||
| 148 | - val systemJsPolyfills: String = inlineJs("assets/system-polyfills.src.js") | ||
| 149 | - val systemJs: String = inlineJs("assets/system.src.js") | ||
| 150 | - val systemJsAppConfig: String = inlineJs("assets/systemjs-config.js") | ||
| 151 | - val systemJsNormalize: String = inlineJs("assets/systemjs-normalize.js") | ||
| 152 | - val systemJsBundleConfig: String = inlineJs("assets/systemjs-bundle-config.js") | ||
| 159 | + private val jspmAssetMap: Map[String, String] = | ||
| 160 | + Json.parse(contents("assets/jspm-assets.map")).validate[Map[String, String]] match { | ||
| 161 | + case JsSuccess(m, _) => m | ||
| 162 | + case JsError(_) => Map.empty | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + private val bundleConfigMap: Map[String, List[String]] = | ||
| 166 | + jspmAssetMap.map { case (source, destination) => | ||
| 167 | + (destination.replaceFirst(".js$", ""), List(source.replaceFirst("^javascripts/", "").replaceFirst(".js$", ""))) | ||
| 168 | + } | ||
| 153 | 169 | ||
| 154 | - lazy val systemJsSetupFragment: String = templates.js.systemJsSetup().body | ||
| 170 | + val bundleConfig: String = Json.toJson(bundleConfigMap).toString() | ||
| 155 | 171 | } | |
| 156 | 172 | } | |
| 157 | 173 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments