Unified · Split
| @@ -156,7 +156,7 @@ Contributions are welcome. Please read the [contributing guidelines](./.github/C · 155 unmodified lines | |||
| 156 | * [Doc Browser](https://github.com/qwfy/doc-browser) is a native Linux app that supports DevDocs docsets | 156 | * [Doc Browser](https://github.com/qwfy/doc-browser) is a native Linux app that supports DevDocs docsets |
| 157 | * [GNOME Application](https://github.com/hardpixel/devdocs-desktop) GTK3 application with search integrated in headerbar | 157 | * [GNOME Application](https://github.com/hardpixel/devdocs-desktop) GTK3 application with search integrated in headerbar |
| 158 | * [macOS Application](https://github.com/dteoh/devdocs-macos) | 158 | * [macOS Application](https://github.com/dteoh/devdocs-macos) |
| 159 | * [Android Application](https://github.com/Merith-TK/devdocs_webapp_kotlin) is a fully working, advanced WebView with AppCache enabled | 159 | * [Android Application](https://github.com/Merith-TK/devdocs_webapp_kotlin) is a fully working, advanced WebView |
| 160 | 160 | ||
| 161 | ## Copyright / License | 161 | ## Copyright / License |
| 162 | 162 | ||
| @@ -13,7 +13,7 @@ · 12 unmodified lines | |||
| 13 | 13 | ||
| 14 | @el = $('._app') | 14 | @el = $('._app') |
| 15 | @localStorage = new LocalStorageStore | 15 | @localStorage = new LocalStorageStore |
| 16 | @appCache = new app.AppCache if app.AppCache.isEnabled() | 16 | @serviceWorker = new app.ServiceWorker if app.ServiceWorker.isEnabled() |
| 17 | @settings = new app.Settings | 17 | @settings = new app.Settings |
| 18 | @db = new app.DB() | 18 | @db = new app.DB() |
| 19 | 19 | ||
| @@ -149,7 +149,7 @@ · 129 unmodified lines | |||
| 149 | saveDocs: -> | 149 | saveDocs: -> |
| 150 | @settings.setDocs(doc.slug for doc in @docs.all()) | 150 | @settings.setDocs(doc.slug for doc in @docs.all()) |
| 151 | @db.migrate() | 151 | @db.migrate() |
| 152 | @appCache?.updateInBackground() | 152 | @serviceWorker?.updateInBackground() |
| 153 | 153 | ||
| 154 | welcomeBack: -> | 154 | welcomeBack: -> |
| 155 | visitCount = @settings.get('count') | 155 | visitCount = @settings.get('count') |
| @@ -169,14 +169,14 @@ · 13 unmodified lines | |||
| 169 | reload: -> | 169 | reload: -> |
| 170 | @docs.clearCache() | 170 | @docs.clearCache() |
| 171 | @disabledDocs.clearCache() | 171 | @disabledDocs.clearCache() |
| 172 | if @appCache then @appCache.reload() else @reboot() | 172 | if @serviceWorker then @serviceWorker.reload() else @reboot() |
| 173 | return | 173 | return |
| 174 | 174 | ||
| 175 | reset: -> | 175 | reset: -> |
| 176 | @localStorage.reset() | 176 | @localStorage.reset() |
| 177 | @settings.reset() | 177 | @settings.reset() |
| 178 | @db?.reset() | 178 | @db?.reset() |
| 179 | @appCache?.update() | 179 | @serviceWorker?.update() |
| 180 | window.location = '/' | 180 | window.location = '/' |
| 181 | return | 181 | return |
| 182 | 182 | ||
| @@ -195,9 +195,9 @@ · 12 unmodified lines | |||
| 195 | return | 195 | return |
| 196 | 196 | ||
| 197 | indexHost: -> | 197 | indexHost: -> |
| 198 | # Can't load the index files from the host/CDN when applicationCache is | 198 | # Can't load the index files from the host/CDN when service worker is |
| 199 | # enabled because it doesn't support caching URLs that use CORS. | 199 | # enabled because it doesn't support caching URLs that use CORS. |
| 200 | @config[if @appCache and @settings.hasDocs() then 'index_path' else 'docs_origin'] | 200 | @config[if @serviceWorker and @settings.hasDocs() then 'index_path' else 'docs_origin'] |
| 201 | 201 | ||
| 202 | onBootError: (args...) -> | 202 | onBootError: (args...) -> |
| 203 | @trigger 'bootError' | 203 | @trigger 'bootError' |
| @@ -1,42 +0,0 @@ | |||
| 1 | class app.AppCache | ||
| 2 | $.extend @prototype, Events | ||
| 3 | |||
| 4 | @isEnabled: -> | ||
| 5 | try | ||
| 6 | applicationCache and applicationCache.status isnt applicationCache.UNCACHED | ||
| 7 | catch | ||
| 8 | |||
| 9 | constructor: -> | ||
| 10 | @cache = applicationCache | ||
| 11 | @notifyUpdate = true | ||
| 12 | @onUpdateReady() if @cache.status is @cache.UPDATEREADY | ||
| 13 | |||
| 14 | $.on @cache, 'progress', @onProgress | ||
| 15 | $.on @cache, 'updateready', @onUpdateReady | ||
| 16 | |||
| 17 | update: -> | ||
| 18 | @notifyUpdate = true | ||
| 19 | @notifyProgress = true | ||
| 20 | try @cache.update() catch | ||
| 21 | return | ||
| 22 | |||
| 23 | updateInBackground: -> | ||
| 24 | @notifyUpdate = false | ||
| 25 | @notifyProgress = false | ||
| 26 | try @cache.update() catch | ||
| 27 | return | ||
| 28 | |||
| 29 | reload: -> | ||
| 30 | $.on @cache, 'updateready noupdate error', -> app.reboot() | ||
| 31 | @notifyUpdate = false | ||
| 32 | @notifyProgress = true | ||
| 33 | try @cache.update() catch | ||
| 34 | return | ||
| 35 | |||
| 36 | onProgress: (event) => | ||
| 37 | @trigger 'progress', event if @notifyProgress | ||
| 38 | return | ||
| 39 | |||
| 40 | onUpdateReady: => | ||
| 41 | @trigger 'updateready' if @notifyUpdate | ||
| 42 | return | ||
| @@ -13,3 +13,4 @@ app.config = · 12 unmodified lines | |||
| 13 | version: <%= Time.now.to_i %> | 13 | version: <%= Time.now.to_i %> |
| 14 | release: <%= Time.now.utc.httpdate.to_json %> | 14 | release: <%= Time.now.utc.httpdate.to_json %> |
| 15 | mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css' | 15 | mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css' |
| 16 | service_worker_path: '/service-worker.js' | ||
| @@ -0,0 +1,55 @@ | |||
| 1 | class app.ServiceWorker | ||
| 2 | $.extend @prototype, Events | ||
| 3 | |||
| 4 | @isEnabled: -> | ||
| 5 | !!navigator.serviceWorker | ||
| 6 | |||
| 7 | constructor: -> | ||
| 8 | @registration = null | ||
| 9 | @installingRegistration = null | ||
| 10 | @notifyUpdate = true | ||
| 11 | |||
| 12 | navigator.serviceWorker.register(app.config.service_worker_path, {scope: '/'}) | ||
| 13 | .then((registration) => @updateRegistration(registration)) | ||
| 14 | .catch((error) -> console.error 'Could not register service worker:', error) | ||
| 15 | |||
| 16 | update: -> | ||
| 17 | return unless @registration | ||
| 18 | @notifyUpdate = true | ||
| 19 | return @doUpdate() | ||
| 20 | |||
| 21 | updateInBackground: -> | ||
| 22 | return unless @registration | ||
| 23 | @notifyUpdate = false | ||
| 24 | return @doUpdate() | ||
| 25 | |||
| 26 | reload: -> | ||
| 27 | return @updateInBackground().then(() -> app.reboot()) | ||
| 28 | |||
| 29 | doUpdate: -> | ||
| 30 | return @registration.update().catch(->) | ||
| 31 | |||
| 32 | updateRegistration: (registration) -> | ||
| 33 | $.off @registration, 'updatefound', @onUpdateFound if @registration | ||
| 34 | $.off @installingRegistration, 'statechange', @onStateChange if @installingRegistration | ||
| 35 | |||
| 36 | @registration = registration | ||
| 37 | @installingRegistration = null | ||
| 38 | |||
| 39 | $.on @registration, 'updatefound', @onUpdateFound | ||
| 40 | return | ||
| 41 | |||
| 42 | onUpdateFound: () => | ||
| 43 | @installingRegistration = @registration.installing | ||
| 44 | $.on @installingRegistration, 'statechange', @onStateChange | ||
| 45 | return | ||
| 46 | |||
| 47 | onStateChange: () => | ||
| 48 | if @installingRegistration.state == 'installed' and navigator.serviceWorker.controller | ||
| 49 | @updateRegistration(@installingRegistration) | ||
| 50 | @onUpdateReady() | ||
| 51 | return | ||
| 52 | |||
| 53 | onUpdateReady: -> | ||
| 54 | @trigger 'updateready' if @notifyUpdate | ||
| 55 | return | ||
| @@ -3,13 +3,13 @@ class app.UpdateChecker · 2 unmodified lines | |||
| 3 | @lastCheck = Date.now() | 3 | @lastCheck = Date.now() |
| 4 | 4 | ||
| 5 | $.on window, 'focus', @onFocus | 5 | $.on window, 'focus', @onFocus |
| 6 | app.appCache.on 'updateready', @onUpdateReady if app.appCache | 6 | app.serviceWorker.on 'updateready', @onUpdateReady if app.serviceWorker |
| 7 | 7 | ||
| 8 | setTimeout @checkDocs, 0 | 8 | setTimeout @checkDocs, 0 |
| 9 | 9 | ||
| 10 | check: -> | 10 | check: -> |
| 11 | if app.appCache | 11 | if app.serviceWorker |
| 12 | app.appCache.update() | 12 | app.serviceWorker.update() |
| 13 | else | 13 | else |
| 14 | ajax | 14 | ajax |
| 15 | url: $('script[src*="application"]').getAttribute('src') | 15 | url: $('script[src*="application"]').getAttribute('src') |
| @@ -26,7 +26,7 @@ app.templates.offlinePage = (docs) -> """ · 25 unmodified lines | |||
| 26 | <dl> | 26 | <dl> |
| 27 | <dt>How does this work? | 27 | <dt>How does this work? |
| 28 | <dd>Each page is cached as a key-value pair in <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> (downloaded from a single file).<br> | 28 | <dd>Each page is cached as a key-value pair in <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> (downloaded from a single file).<br> |
| 29 | The app also uses <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache">AppCache</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a> to cache the assets and index files. | 29 | The app also uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers">Service Workers</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a> to cache the assets and index files. |
| 30 | <dt>Can I close the tab/browser? | 30 | <dt>Can I close the tab/browser? |
| 31 | <dd>#{canICloseTheTab()} | 31 | <dd>#{canICloseTheTab()} |
| 32 | <dt>What if I don't update a documentation? | 32 | <dt>What if I don't update a documentation? |
| @@ -41,10 +41,10 @@ app.templates.offlinePage = (docs) -> """ · 8 unmodified lines | |||
| 41 | """ | 41 | """ |
| 42 | 42 | ||
| 43 | canICloseTheTab = -> | 43 | canICloseTheTab = -> |
| 44 | if app.AppCache.isEnabled() | 44 | if app.ServiceWorker.isEnabled() |
| 45 | """ Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). """ | 45 | """ Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). """ |
| 46 | else | 46 | else |
| 47 | """ No. AppCache isn't available in your browser (or is disabled), so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br> | 47 | """ No. Service Workers aren't available in your browser (or are disabled), so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br> |
| 48 | The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """ | 48 | The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """ |
| 49 | 49 | ||
| 50 | app.templates.offlineDoc = (doc, status) -> | 50 | app.templates.offlineDoc = (doc, status) -> |
| @@ -123,7 +123,7 @@ class app.views.EntryPage extends app.View · 122 unmodified lines | |||
| 123 | @render @tmpl('pageLoadError') | 123 | @render @tmpl('pageLoadError') |
| 124 | @resetClass() | 124 | @resetClass() |
| 125 | @addClass @constructor.errorClass | 125 | @addClass @constructor.errorClass |
| 126 | app.appCache?.update() | 126 | app.serviceWorker?.update() |
| 127 | return | 127 | return |
| 128 | 128 | ||
| 129 | cache: -> | 129 | cache: -> |
| @@ -22,12 +22,10 @@ class app.views.SettingsPage extends app.View · 21 unmodified lines | |||
| 22 | 22 | ||
| 23 | toggleDark: (enable) -> | 23 | toggleDark: (enable) -> |
| 24 | app.settings.set('dark', !!enable) | 24 | app.settings.set('dark', !!enable) |
| 25 | app.appCache?.updateInBackground() | ||
| 26 | return | 25 | return |
| 27 | 26 | ||
| 28 | toggleLayout: (layout, enable) -> | 27 | toggleLayout: (layout, enable) -> |
| 29 | app.settings.setLayout(layout, enable) | 28 | app.settings.setLayout(layout, enable) |
| 30 | app.appCache?.updateInBackground() | ||
| 31 | return | 29 | return |
| 32 | 30 | ||
| 33 | toggleSmoothScroll: (enable) -> | 31 | toggleSmoothScroll: (enable) -> |
| @@ -26,9 +26,7 @@ class app.views.Resizer extends app.View · 25 unmodified lines | |||
| 26 | newSize = "#{value}px" | 26 | newSize = "#{value}px" |
| 27 | @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize) | 27 | @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize) |
| 28 | @size = newSize | 28 | @size = newSize |
| 29 | if save | 29 | app.settings.setSize(value) if save |
| 30 | app.settings.setSize(value) | ||
| 31 | app.appCache?.updateInBackground() | ||
| 32 | return | 30 | return |
| 33 | 31 | ||
| 34 | onDragStart: (event) => | 32 | onDragStart: (event) => |
| @@ -25,7 +25,6 @@ class app.views.Settings extends app.View · 24 unmodified lines | |||
| 25 | if super | 25 | if super |
| 26 | @render() | 26 | @render() |
| 27 | document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT) | 27 | document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT) |
| 28 | app.appCache?.on 'progress', @onAppCacheProgress | ||
| 29 | return | 28 | return |
| 30 | 29 | ||
| 31 | deactivate: -> | 30 | deactivate: -> |
| @@ -33,7 +32,6 @@ class app.views.Settings extends app.View · 1 unmodified line | |||
| 33 | @resetClass() | 32 | @resetClass() |
| 34 | @docPicker.detach() | 33 | @docPicker.detach() |
| 35 | document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT) | 34 | document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT) |
| 36 | app.appCache?.off 'progress', @onAppCacheProgress | ||
| 37 | return | 35 | return |
| 38 | 36 | ||
| 39 | render: -> | 37 | render: -> |
| @@ -52,7 +50,7 @@ class app.views.Settings extends app.View · 12 unmodified lines | |||
| 52 | docs = @docPicker.getSelectedDocs() | 50 | docs = @docPicker.getSelectedDocs() |
| 53 | app.settings.setDocs(docs) | 51 | app.settings.setDocs(docs) |
| 54 | 52 | ||
| 55 | @saveBtn.textContent = if app.appCache then 'Downloading\u2026' else 'Saving\u2026' | 53 | @saveBtn.textContent = 'Saving\u2026' |
| 56 | disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1) | 54 | disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1) |
| 57 | disabledDocs.uninstall -> | 55 | disabledDocs.uninstall -> |
| 58 | app.db.migrate() | 56 | app.db.migrate() |
| @@ -83,9 +81,3 @@ class app.views.Settings extends app.View · 24 unmodified lines | |||
| 83 | $.stopEvent(event) | 81 | $.stopEvent(event) |
| 84 | app.router.show '/' | 82 | app.router.show '/' |
| 85 | return | 83 | return |
| 86 | |||
| 87 | onAppCacheProgress: (event) => | ||
| 88 | if event.lengthComputable | ||
| 89 | percentage = Math.round event.loaded * 100 / event.total | ||
| 90 | @saveBtn.textContent = "Downloading\u2026 (#{percentage}%)" | ||
| 91 | return | ||
| @@ -220,7 +220,7 @@ class App < Sinatra::Application · 219 unmodified lines | |||
| 220 | app_theme == 'dark' | 220 | app_theme == 'dark' |
| 221 | end | 221 | end |
| 222 | 222 | ||
| 223 | def redirect_via_js(path) # courtesy of HTML5 App Cache | 223 | def redirect_via_js(path) |
| 224 | response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/' | 224 | response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/' |
| 225 | redirect '/', 302 | 225 | redirect '/', 302 |
| 226 | end | 226 | end |
| @@ -243,15 +243,15 @@ class App < Sinatra::Application · 16 unmodified lines | |||
| 243 | end | 243 | end |
| 244 | end | 244 | end |
| 245 | 245 | ||
| 246 | get '/manifest.appcache' do | 246 | get '/service-worker.js' do |
| 247 | content_type 'text/cache-manifest' | 247 | content_type 'application/javascript' |
| 248 | expires 0, :'no-cache' | 248 | expires 0, :'no-cache' |
| 249 | erb :manifest | 249 | erb :'service-worker.js' |
| 250 | end | 250 | end |
| 251 | 251 | ||
| 252 | get '/' do | 252 | get '/' do |
| 253 | return redirect "/#q=#{params[:q]}" if params[:q] | 253 | return redirect "/#q=#{params[:q]}" if params[:q] |
| 254 | return redirect '/' unless request.query_string.empty? # courtesy of HTML5 App Cache | 254 | return redirect '/' unless request.query_string.empty? |
| 255 | response.headers['Content-Security-Policy'] = settings.csp if settings.csp | 255 | response.headers['Content-Security-Policy'] = settings.csp if settings.csp |
| 256 | erb :index | 256 | erb :index |
| 257 | end | 257 | end |
| @@ -106,58 +106,6 @@ class AppTest < MiniTest::Spec · 105 unmodified lines | |||
| 106 | end | 106 | end |
| 107 | end | 107 | end |
| 108 | 108 | ||
| 109 | describe "/manifest.appcache" do | ||
| 110 | it "works" do | ||
| 111 | get '/manifest.appcache' | ||
| 112 | assert last_response.ok? | ||
| 113 | end | ||
| 114 | |||
| 115 | it "works with cookie" do | ||
| 116 | set_cookie('docs=css/html~5') | ||
| 117 | get '/manifest.appcache' | ||
| 118 | assert last_response.ok? | ||
| 119 | assert_includes last_response.body, '/css/index.json?1420139788' | ||
| 120 | assert_includes last_response.body, '/html~5/index.json?1420139791' | ||
| 121 | end | ||
| 122 | |||
| 123 | it "ignores invalid docs in the cookie" do | ||
| 124 | set_cookie('docs=foo') | ||
| 125 | get '/manifest.appcache' | ||
| 126 | assert last_response.ok? | ||
| 127 | refute_includes last_response.body, 'foo' | ||
| 128 | end | ||
| 129 | |||
| 130 | it "has the word 'default' when no 'dark' cookie is set" do | ||
| 131 | get '/manifest.appcache' | ||
| 132 | assert_includes last_response.body, '# default' | ||
| 133 | refute_includes last_response.body, '# dark' | ||
| 134 | end | ||
| 135 | |||
| 136 | it "has the word 'dark' when the cookie is set" do | ||
| 137 | set_cookie('dark=1') | ||
| 138 | get '/manifest.appcache' | ||
| 139 | assert_includes last_response.body, '# dark' | ||
| 140 | refute_includes last_response.body, '# default' | ||
| 141 | end | ||
| 142 | |||
| 143 | it "sets default size" do | ||
| 144 | get '/manifest.appcache' | ||
| 145 | assert_includes last_response.body, '20rem' | ||
| 146 | end | ||
| 147 | |||
| 148 | it "sets size from cookie" do | ||
| 149 | set_cookie('size=42') | ||
| 150 | get '/manifest.appcache' | ||
| 151 | assert_includes last_response.body, '42px' | ||
| 152 | end | ||
| 153 | |||
| 154 | it "sets layout from cookie" do | ||
| 155 | set_cookie('layout=foo_layout') | ||
| 156 | get '/manifest.appcache' | ||
| 157 | assert_includes last_response.body, 'foo_layout' | ||
| 158 | end | ||
| 159 | end | ||
| 160 | |||
| 161 | describe "/[doc]" do | 109 | describe "/[doc]" do |
| 162 | it "renders when the doc exists and isn't enabled" do | 110 | it "renders when the doc exists and isn't enabled" do |
| 163 | set_cookie('docs=html~5') | 111 | set_cookie('docs=html~5') |
| @@ -1,5 +1,5 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html<%= ' manifest="/manifest.appcache"' if App.production? %> prefix="og: http://ogp.me/ns#" lang="en" class="_booting _theme-<%= app_theme %>"> | 2 | <html prefix="og: http://ogp.me/ns#" lang="en" class="_booting _theme-<%= app_theme %>"> |
| 3 | <head> | 3 | <head> |
| 4 | <meta charset="utf-8"> | 4 | <meta charset="utf-8"> |
| 5 | <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no"> | 5 | <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no"> |
| @@ -1,14 +0,0 @@ | |||
| 1 | CACHE MANIFEST | ||
| 2 | # <%= app_theme %> <%= app_size %> <%= app_layout %> | ||
| 3 | |||
| 4 | CACHE: | ||
| 5 | / | ||
| 6 | <%= manifest_asset_urls.join "\n" %> | ||
| 7 | <%= doc_index_urls.join "\n" %> | ||
| 8 | |||
| 9 | NETWORK: | ||
| 10 | /s/ | ||
| 11 | * | ||
| 12 | |||
| 13 | FALLBACK: | ||
| 14 | / / | ||
| @@ -0,0 +1,64 @@ | |||
| 1 | <%# Use the hash of the application.js file as cache name, or 'app' if not running in production %> | ||
| 2 | <%# This ensures that the cache is always updated if the hash of the application.js file changes %> | ||
| 3 | const cacheName = '<%= javascript_path('application', asset_host: false).scan(/application-([^\.]+)\.js/).last&.first || 'app' %>'; | ||
| 4 | |||
| 5 | <%# Paths to cache when the service worker is installed %> | ||
| 6 | const cachePaths = [ | ||
| 7 | '/', | ||
| 8 | '/favicon.ico', | ||
| 9 | '/manifest.json', | ||
| 10 | '/images/webapp-icon-32.png', | ||
| 11 | '/images/webapp-icon-60.png', | ||
| 12 | '/images/webapp-icon-80.png', | ||
| 13 | '/images/webapp-icon-128.png', | ||
| 14 | '/images/webapp-icon-256.png', | ||
| 15 | '/images/webapp-icon-512.png', | ||
| 16 | '<%= manifest_asset_urls.join "',\n '" %>', | ||
| 17 | '<%= doc_index_urls.join "',\n '" %>', | ||
| 18 | ]; | ||
| 19 | |||
| 20 | <%# Set-up the cache %> | ||
| 21 | self.addEventListener('install', event => { | ||
| 22 | self.skipWaiting(); | ||
| 23 | |||
| 24 | event.waitUntil( | ||
| 25 | caches.open(cacheName).then(cache => cache.addAll(cachePaths)), | ||
| 26 | ); | ||
| 27 | }); | ||
| 28 | |||
| 29 | <%# Remove old caches %> | ||
| 30 | self.addEventListener('activate', event => { | ||
| 31 | event.waitUntil( | ||
| 32 | caches.keys().then(keys => Promise.all( | ||
| 33 | keys.map(key => { | ||
| 34 | if (key !== cacheName) { | ||
| 35 | return caches.delete(key); | ||
| 36 | } | ||
| 37 | }) | ||
| 38 | )) | ||
| 39 | ); | ||
| 40 | }); | ||
| 41 | |||
| 42 | <%# Handle HTTP requests %> | ||
| 43 | self.addEventListener('fetch', event => { | ||
| 44 | event.respondWith( | ||
| 45 | caches.match(event.request).then(response => { | ||
| 46 | if (response) { | ||
| 47 | return response; | ||
| 48 | } | ||
| 49 | |||
| 50 | return fetch(event.request) | ||
| 51 | .catch(err => { | ||
| 52 | const url = new URL(event.request.url); | ||
| 53 | |||
| 54 | <%# Return the index page from the cache if the user is visiting a url like devdocs.io/javascript/global_objects/array/find %> | ||
| 55 | <%# The index page will make sure the correct documentation or a proper offline page is shown %> | ||
| 56 | if (url.origin === location.origin && !url.pathname.includes('.')) { | ||
| 57 | return caches.match('/').then(response => response || err); | ||
| 58 | } | ||
| 59 | |||
| 60 | return err; | ||
| 61 | }); | ||
| 62 | }) | ||
| 63 | ); | ||
| 64 | }); | ||
| @@ -11,9 +11,9 @@ · 10 unmodified lines | |||
| 11 | <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:</p> | 11 | <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:</p> |
| 12 | <ul class="_fail-list"> | 12 | <ul class="_fail-list"> |
| 13 | <li>Recent versions of Firefox, Chrome, or Opera</li> | 13 | <li>Recent versions of Firefox, Chrome, or Opera</li> |
| 14 | <li>Safari 9.1+</li> | 14 | <li>Safari 11.1+</li> |
| 15 | <li>Edge 16+</li> | 15 | <li>Edge 16+</li> |
| 16 | <li>iOS 10+</li> | 16 | <li>iOS 11.3+</li> |
| 17 | </ul> | 17 | </ul> |
| 18 | <p class="_fail-text"> | 18 | <p class="_fail-text"> |
| 19 | If you're unable to upgrade, we apologize. | 19 | If you're unable to upgrade, we apologize. |