Showing the changes to assets/javascripts (31 of 280 files). Show all
Unified · Split
| @@ -13,10 +13,12 @@ · 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 | ||
| 20 | @settings.initLayout() | ||
| 21 | |||
| 20 | @docs = new app.collections.Docs | 22 | @docs = new app.collections.Docs |
| 21 | @disabledDocs = new app.collections.Docs | 23 | @disabledDocs = new app.collections.Docs |
| 22 | @entries = new app.collections.Entries | 24 | @entries = new app.collections.Entries |
| @@ -76,7 +78,7 @@ · 53 unmodified lines | |||
| 76 | .install() | 78 | .install() |
| 77 | @previousErrorHandler = onerror | 79 | @previousErrorHandler = onerror |
| 78 | window.onerror = @onWindowError.bind(@) | 80 | window.onerror = @onWindowError.bind(@) |
| 79 | CookieStore.onBlocked = @onCookieBlocked | 81 | CookiesStore.onBlocked = @onCookieBlocked |
| 80 | return | 82 | return |
| 81 | 83 | ||
| 82 | bootOne: -> | 84 | bootOne: -> |
| @@ -138,7 +140,10 @@ · 55 unmodified lines | |||
| 138 | @docs.sort() | 140 | @docs.sort() |
| 139 | @initDoc(doc) | 141 | @initDoc(doc) |
| 140 | @saveDocs() | 142 | @saveDocs() |
| 141 | _onSuccess() | 143 | if app.settings.get('autoInstall') |
| 144 | doc.install(_onSuccess, onError) | ||
| 145 | else | ||
| 146 | _onSuccess() | ||
| 142 | return | 147 | return |
| 143 | 148 | ||
| 144 | doc.load onSuccess, onError, writeCache: true | 149 | doc.load onSuccess, onError, writeCache: true |
| @@ -147,7 +152,7 @@ · 2 unmodified lines | |||
| 147 | saveDocs: -> | 152 | saveDocs: -> |
| 148 | @settings.setDocs(doc.slug for doc in @docs.all()) | 153 | @settings.setDocs(doc.slug for doc in @docs.all()) |
| 149 | @db.migrate() | 154 | @db.migrate() |
| 150 | @appCache?.updateInBackground() | 155 | @serviceWorker?.updateInBackground() |
| 151 | 156 | ||
| 152 | welcomeBack: -> | 157 | welcomeBack: -> |
| 153 | visitCount = @settings.get('count') | 158 | visitCount = @settings.get('count') |
| @@ -167,14 +172,14 @@ · 13 unmodified lines | |||
| 167 | reload: -> | 172 | reload: -> |
| 168 | @docs.clearCache() | 173 | @docs.clearCache() |
| 169 | @disabledDocs.clearCache() | 174 | @disabledDocs.clearCache() |
| 170 | if @appCache then @appCache.reload() else @reboot() | 175 | if @serviceWorker then @serviceWorker.reload() else @reboot() |
| 171 | return | 176 | return |
| 172 | 177 | ||
| 173 | reset: -> | 178 | reset: -> |
| 174 | @localStorage.reset() | 179 | @localStorage.reset() |
| 175 | @settings.reset() | 180 | @settings.reset() |
| 176 | @db?.reset() | 181 | @db?.reset() |
| 177 | @appCache?.update() | 182 | @serviceWorker?.update() |
| 178 | window.location = '/' | 183 | window.location = '/' |
| 179 | return | 184 | return |
| 180 | 185 | ||
| @@ -193,9 +198,9 @@ · 12 unmodified lines | |||
| 193 | return | 198 | return |
| 194 | 199 | ||
| 195 | indexHost: -> | 200 | indexHost: -> |
| 196 | # Can't load the index files from the host/CDN when applicationCache is | 201 | # Can't load the index files from the host/CDN when service worker is |
| 197 | # enabled because it doesn't support caching URLs that use CORS. | 202 | # enabled because it doesn't support caching URLs that use CORS. |
| 198 | @config[if @appCache and @settings.hasDocs() then 'index_path' else 'docs_origin'] | 203 | @config[if @serviceWorker and @settings.hasDocs() then 'index_path' else 'docs_origin'] |
| 199 | 204 | ||
| 200 | onBootError: (args...) -> | 205 | onBootError: (args...) -> |
| 201 | @trigger 'bootError' | 206 | @trigger 'bootError' |
| @@ -252,7 +257,7 @@ · 50 unmodified lines | |||
| 252 | matchMedia: !!window.matchMedia | 257 | matchMedia: !!window.matchMedia |
| 253 | insertAdjacentHTML: !!document.body.insertAdjacentHTML | 258 | insertAdjacentHTML: !!document.body.insertAdjacentHTML |
| 254 | defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false | 259 | defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false |
| 255 | cssVariables: CSS.supports and CSS.supports('(--t: 0)') | 260 | cssVariables: !!CSS?.supports?('(--t: 0)') |
| 256 | 261 | ||
| 257 | for key, value of features when not value | 262 | for key, value of features when not value |
| 258 | Raven.captureMessage "unsupported/#{key}", level: 'info' | 263 | Raven.captureMessage "unsupported/#{key}", level: 'info' |
| @@ -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,6 @@ 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 | favicon_spritesheet: '<%= image_path('sprites/docs.png') %>' | ||
| 17 | service_worker_path: '/service-worker.js' | ||
| 18 | service_worker_enabled: <%= App.environment == :production || ENV['ENABLE_SERVICE_WORKER'] == 'true' %> | ||
| @@ -0,0 +1,49 @@ | |||
| 1 | class app.ServiceWorker | ||
| 2 | $.extend @prototype, Events | ||
| 3 | |||
| 4 | @isEnabled: -> | ||
| 5 | !!navigator.serviceWorker and app.config.service_worker_enabled | ||
| 6 | |||
| 7 | constructor: -> | ||
| 8 | @registration = null | ||
| 9 | @notifyUpdate = true | ||
| 10 | |||
| 11 | navigator.serviceWorker.register(app.config.service_worker_path, {scope: '/'}) | ||
| 12 | .then( | ||
| 13 | (registration) => @updateRegistration(registration), | ||
| 14 | (error) -> console.error('Could not register service worker:', error) | ||
| 15 | ) | ||
| 16 | |||
| 17 | update: -> | ||
| 18 | return unless @registration | ||
| 19 | @notifyUpdate = true | ||
| 20 | return @registration.update().catch(->) | ||
| 21 | |||
| 22 | updateInBackground: -> | ||
| 23 | return unless @registration | ||
| 24 | @notifyUpdate = false | ||
| 25 | return @registration.update().catch(->) | ||
| 26 | |||
| 27 | reload: -> | ||
| 28 | return @updateInBackground().then(() -> app.reboot()) | ||
| 29 | |||
| 30 | updateRegistration: (registration) -> | ||
| 31 | @registration = registration | ||
| 32 | $.on @registration, 'updatefound', @onUpdateFound | ||
| 33 | return | ||
| 34 | |||
| 35 | onUpdateFound: => | ||
| 36 | $.off @installingRegistration, 'statechange', @onStateChange() if @installingRegistration | ||
| 37 | @installingRegistration = @registration.installing | ||
| 38 | $.on @installingRegistration, 'statechange', @onStateChange | ||
| 39 | return | ||
| 40 | |||
| 41 | onStateChange: => | ||
| 42 | if @installingRegistration and @installingRegistration.state == 'installed' and navigator.serviceWorker.controller | ||
| 43 | @installingRegistration = null | ||
| 44 | @onUpdateReady() | ||
| 45 | return | ||
| 46 | |||
| 47 | onUpdateReady: -> | ||
| 48 | @trigger 'updateready' if @notifyUpdate | ||
| 49 | return | ||
| @@ -5,11 +5,13 @@ class app.Settings · 4 unmodified lines | |||
| 5 | 'manualUpdate' | 5 | 'manualUpdate' |
| 6 | 'fastScroll' | 6 | 'fastScroll' |
| 7 | 'arrowScroll' | 7 | 'arrowScroll' |
| 8 | 'analyticsConsent' | ||
| 8 | 'docs' | 9 | 'docs' |
| 9 | 'dark' | 10 | 'dark' |
| 10 | 'layout' | 11 | 'layout' |
| 11 | 'size' | 12 | 'size' |
| 12 | 'tips' | 13 | 'tips' |
| 14 | 'autoInstall' | ||
| 13 | ] | 15 | ] |
| 14 | 16 | ||
| 15 | INTERNAL_KEYS = [ | 17 | INTERNAL_KEYS = [ |
| @@ -19,6 +21,8 @@ class app.Settings · 3 unmodified lines | |||
| 19 | 'news' | 21 | 'news' |
| 20 | ] | 22 | ] |
| 21 | 23 | ||
| 24 | LAYOUTS: ['_max-width', '_sidebar-hidden', '_native-scrollbars'] | ||
| 25 | |||
| 22 | @defaults: | 26 | @defaults: |
| 23 | count: 0 | 27 | count: 0 |
| 24 | hideDisabled: false | 28 | hideDisabled: false |
| @@ -26,9 +30,10 @@ class app.Settings · 1 unmodified line | |||
| 26 | news: 0 | 30 | news: 0 |
| 27 | manualUpdate: false | 31 | manualUpdate: false |
| 28 | schema: 1 | 32 | schema: 1 |
| 33 | analyticsConsent: false | ||
| 29 | 34 | ||
| 30 | constructor: -> | 35 | constructor: -> |
| 31 | @store = new CookieStore | 36 | @store = new CookiesStore |
| 32 | @cache = {} | 37 | @cache = {} |
| 33 | 38 | ||
| 34 | get: (key) -> | 39 | get: (key) -> |
| @@ -38,6 +43,7 @@ class app.Settings · 3 unmodified lines | |||
| 38 | set: (key, value) -> | 43 | set: (key, value) -> |
| 39 | @store.set(key, value) | 44 | @store.set(key, value) |
| 40 | delete @cache[key] | 45 | delete @cache[key] |
| 46 | @toggleDark(value) if key == 'dark' | ||
| 41 | return | 47 | return |
| 42 | 48 | ||
| 43 | del: (key) -> | 49 | del: (key) -> |
| @@ -63,6 +69,8 @@ class app.Settings · 19 unmodified lines | |||
| 63 | return | 69 | return |
| 64 | 70 | ||
| 65 | setLayout: (name, enable) -> | 71 | setLayout: (name, enable) -> |
| 72 | @toggleLayout(name, enable) | ||
| 73 | |||
| 66 | layout = (@store.get('layout') || '').split(' ') | 74 | layout = (@store.get('layout') || '').split(' ') |
| 67 | $.arrayDelete(layout, '') | 75 | $.arrayDelete(layout, '') |
| 68 | 76 | ||
| @@ -104,3 +112,28 @@ class app.Settings · 35 unmodified lines | |||
| 104 | @store.reset() | 112 | @store.reset() |
| 105 | @cache = {} | 113 | @cache = {} |
| 106 | return | 114 | return |
| 115 | |||
| 116 | initLayout: -> | ||
| 117 | @toggleDark(@get('dark') is 1) | ||
| 118 | @toggleLayout(layout, @hasLayout(layout)) for layout in @LAYOUTS | ||
| 119 | @initSidebarWidth() | ||
| 120 | return | ||
| 121 | |||
| 122 | toggleDark: (enable) -> | ||
| 123 | classList = document.documentElement.classList | ||
| 124 | classList.toggle('_theme-default', !enable) | ||
| 125 | classList.toggle('_theme-dark', enable) | ||
| 126 | color = getComputedStyle(document.documentElement).getPropertyValue('--headerBackground').trim() | ||
| 127 | $('meta[name=theme-color]').setAttribute('content', color) | ||
| 128 | return | ||
| 129 | |||
| 130 | toggleLayout: (layout, enable) -> | ||
| 131 | classList = document.body.classList | ||
| 132 | classList.toggle(layout, enable) unless layout is '_sidebar-hidden' | ||
| 133 | classList.toggle('_overlay-scrollbars', $.overlayScrollbarsEnabled()) | ||
| 134 | return | ||
| 135 | |||
| 136 | initSidebarWidth: -> | ||
| 137 | size = @get('size') | ||
| 138 | document.documentElement.style.setProperty('--sidebarWidth', size + 'px') if size | ||
| 139 | 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 |
| 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') |
| @@ -1,4 +1,8 @@ | |||
| 1 | class @CookieStore | 1 | class @CookiesStore |
| 2 | # Intentionally called CookiesStore instead of CookieStore | ||
| 3 | # Calling it CookieStore causes issues when the Experimental Web Platform features flag is enabled in Chrome | ||
| 4 | # Related issue: https://github.com/freeCodeCamp/devdocs/issues/932 | ||
| 5 | |||
| 2 | INT = /^\d+$/ | 6 | INT = /^\d+$/ |
| 3 | 7 | ||
| 4 | @onBlocked: -> | 8 | @onBlocked: -> |
| @@ -0,0 +1,64 @@ | |||
| 1 | defaultUrl = null | ||
| 2 | currentSlug = null | ||
| 3 | |||
| 4 | imageCache = {} | ||
| 5 | urlCache = {} | ||
| 6 | |||
| 7 | withImage = (url, action) -> | ||
| 8 | if imageCache[url] | ||
| 9 | action(imageCache[url]) | ||
| 10 | else | ||
| 11 | img = new Image() | ||
| 12 | img.crossOrigin = 'anonymous' | ||
| 13 | img.src = url | ||
| 14 | img.onload = () => | ||
| 15 | imageCache[url] = img | ||
| 16 | action(img) | ||
| 17 | |||
| 18 | @setFaviconForDoc = (doc) -> | ||
| 19 | return if currentSlug == doc.slug | ||
| 20 | |||
| 21 | favicon = $('link[rel="icon"]') | ||
| 22 | |||
| 23 | if defaultUrl == null | ||
| 24 | defaultUrl = favicon.href | ||
| 25 | |||
| 26 | if urlCache[doc.slug] | ||
| 27 | favicon.href = urlCache[doc.slug] | ||
| 28 | currentSlug = doc.slug | ||
| 29 | return | ||
| 30 | |||
| 31 | styles = window.getComputedStyle($("._icon-#{doc.slug.split('~')[0]}"), ':before') | ||
| 32 | |||
| 33 | bgUrl = app.config.favicon_spritesheet | ||
| 34 | sourceSize = 16 | ||
| 35 | sourceX = Math.abs(parseInt(styles['background-position-x'].slice(0, -2))) | ||
| 36 | sourceY = Math.abs(parseInt(styles['background-position-y'].slice(0, -2))) | ||
| 37 | |||
| 38 | withImage(bgUrl, (docImg) -> | ||
| 39 | withImage(defaultUrl, (defaultImg) -> | ||
| 40 | size = defaultImg.width | ||
| 41 | |||
| 42 | canvas = document.createElement('canvas') | ||
| 43 | ctx = canvas.getContext('2d') | ||
| 44 | |||
| 45 | canvas.width = size | ||
| 46 | canvas.height = size | ||
| 47 | ctx.drawImage(defaultImg, 0, 0) | ||
| 48 | |||
| 49 | docIconPercentage = 65 | ||
| 50 | destinationCoords = size / 100 * (100 - docIconPercentage) | ||
| 51 | destinationSize = size / 100 * docIconPercentage | ||
| 52 | ctx.drawImage(docImg, sourceX, sourceY, sourceSize, sourceSize, destinationCoords, destinationCoords, destinationSize, destinationSize) | ||
| 53 | |||
| 54 | urlCache[doc.slug] = canvas.toDataURL() | ||
| 55 | favicon.href = urlCache[doc.slug] | ||
| 56 | |||
| 57 | currentSlug = doc.slug | ||
| 58 | ) | ||
| 59 | ) | ||
| 60 | |||
| 61 | @resetFavicon = () -> | ||
| 62 | if defaultUrl != null and currentSlug != null | ||
| 63 | $('link[rel="icon"]').href = defaultUrl | ||
| 64 | currentSlug = null | ||
| @@ -199,5 +199,21 @@ page.track = (fn) -> · 198 unmodified lines | |||
| 199 | return | 199 | return |
| 200 | 200 | ||
| 201 | track = -> | 201 | track = -> |
| 202 | tracker.call() for tracker in trackers | 202 | consentGiven = Cookies.get('analyticsConsent') |
| 203 | consentAsked = Cookies.get('analyticsConsentAsked') | ||
| 204 | |||
| 205 | if consentGiven == '1' | ||
| 206 | tracker.call() for tracker in trackers | ||
| 207 | else if consentGiven == undefined and consentAsked == undefined | ||
| 208 | # Only ask for consent once per browser session | ||
| 209 | Cookies.set('analyticsConsentAsked', '1') | ||
| 210 | |||
| 211 | new app.views.Notif 'AnalyticsConsent', autoHide: null | ||
| 212 | return | ||
| 213 | |||
| 214 | @resetAnalytics = -> | ||
| 215 | for cookie in document.cookie.split(/;\s?/) | ||
| 216 | name = cookie.split('=')[0] | ||
| 217 | if name[0] == '_' && name[1] != '_' | ||
| 218 | Cookies.expire(name) | ||
| 203 | return | 219 | return |
| @@ -352,6 +352,10 @@ isIE = null · 351 unmodified lines | |||
| 352 | $.isIE = -> | 352 | $.isIE = -> |
| 353 | isIE ?= navigator.userAgent?.indexOf('MSIE') >= 0 || navigator.userAgent?.indexOf('rv:11.0') >= 0 | 353 | isIE ?= navigator.userAgent?.indexOf('MSIE') >= 0 || navigator.userAgent?.indexOf('rv:11.0') >= 0 |
| 354 | 354 | ||
| 355 | isChromeForAndroid = null | ||
| 356 | $.isChromeForAndroid = -> | ||
| 357 | isChromeForAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 && /Chrome\/([.0-9])+ Mobile/.test(navigator.userAgent) | ||
| 358 | |||
| 355 | isAndroid = null | 359 | isAndroid = null |
| 356 | $.isAndroid = -> | 360 | $.isAndroid = -> |
| 357 | isAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 | 361 | isAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 |
| @@ -142,4 +142,6 @@ class app.models.Doc extends app.Model · 141 unmodified lines | |||
| 142 | return | 142 | return |
| 143 | 143 | ||
| 144 | isOutdated: (status) -> | 144 | isOutdated: (status) -> |
| 145 | status and status.installed and @mtime isnt status.mtime | 145 | return false if not status |
| 146 | isInstalled = status.installed or app.settings.get('autoInstall') | ||
| 147 | isInstalled and @mtime isnt status.mtime | ||
| @@ -1,4 +1,12 @@ | |||
| 1 | [ | 1 | [ |
| 2 | [ | ||
| 3 | "2019-07-21", | ||
| 4 | "Fixed several bugs, added an option to automatically download documentation and <a href=\"https://github.com/freeCodeCamp/devdocs/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+sort%3Aupdated-desc+closed%3A%3E2019-07-18+977+988+986+870+886+1024+979+975+941+831+1005+848+942\" target=\"_blank\">more</a>." | ||
| 5 | ], | ||
| 6 | [ | ||
| 7 | "2019-07-19", | ||
| 8 | "Replaced the AppCache with a Service Worker (which makes DevDocs an installable PWA) and fixed layout preferences on Firefox." | ||
| 9 | ], | ||
| 2 | [ | 10 | [ |
| 3 | "2018-09-23", | 11 | "2018-09-23", |
| 4 | "New documentations: <a href=\"/puppeteer/\">Puppeteer</a> and <a href=\"/handlebars/\">Handlebars.js</a>" | 12 | "New documentations: <a href=\"/puppeteer/\">Puppeteer</a> and <a href=\"/handlebars/\">Handlebars.js</a>" |
| @@ -12,8 +12,8 @@ app.templates.notFoundPage = -> · 11 unmodified lines | |||
| 12 | 12 | ||
| 13 | app.templates.pageLoadError = -> | 13 | app.templates.pageLoadError = -> |
| 14 | error """ The page failed to load. """, | 14 | error """ The page failed to load. """, |
| 15 | """ It may be missing from the server (try reloading the app) or you could be offline.<br> | 15 | """ It may be missing from the server (try reloading the app) or you could be offline (try <a href="/offline">installing the documentation for offline usage</a> when online again).<br> |
| 16 | If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """, | 16 | If you're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """, |
| 17 | """ #{back} · <a href="/##{location.pathname}" target="_top" class="_error-link">Reload</a> | 17 | """ #{back} · <a href="/##{location.pathname}" target="_top" class="_error-link">Reload</a> |
| 18 | · <a href="#" class="_error-link" data-retry>Retry</a> """ | 18 | · <a href="#" class="_error-link" data-retry>Retry</a> """ |
| 19 | 19 | ||
| @@ -57,9 +57,9 @@ app.templates.unsupportedBrowser = """ · 37 unmodified lines | |||
| 57 | <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers: | 57 | <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers: |
| 58 | <ul class="_fail-list"> | 58 | <ul class="_fail-list"> |
| 59 | <li>Recent versions of Firefox, Chrome, or Opera | 59 | <li>Recent versions of Firefox, Chrome, or Opera |
| 60 | <li>Safari 9.1+ | 60 | <li>Safari 11.1+ |
| 61 | <li>Edge 16+ | 61 | <li>Edge 17+ |
| 62 | <li>iOS 10+ | 62 | <li>iOS 11.3+ |
| 63 | </ul> | 63 | </ul> |
| 64 | <p class="_fail-text"> | 64 | <p class="_fail-text"> |
| 65 | If you're unable to upgrade, we apologize. | 65 | If you're unable to upgrade, we apologize. |
| @@ -68,3 +68,9 @@ app.templates.notifShare = -> · 67 unmodified lines | |||
| 68 | app.templates.notifUpdateDocs = -> | 68 | app.templates.notifUpdateDocs = -> |
| 69 | textNotif """ Documentation updates available. """, | 69 | textNotif """ Documentation updates available. """, |
| 70 | """ <a href="/offline">Install them</a> as soon as possible to avoid broken pages. """ | 70 | """ <a href="/offline">Install them</a> as soon as possible to avoid broken pages. """ |
| 71 | |||
| 72 | app.templates.notifAnalyticsConsent = -> | ||
| 73 | textNotif """ Tracking cookies """, | ||
| 74 | """ We would like to gather usage data about how DevDocs is used through Google Analytics and Gauges. We only collect anonymous traffic information. | ||
| 75 | Please confirm if you accept our tracking cookies. You can always change your decision in the settings. | ||
| 76 | <br><span class="_notif-right"><a href="#" data-behavior="accept-analytics">Accept</a> or <a href="#" data-behavior="decline-analytics">Decline</a></span> """ | ||
| @@ -73,7 +73,8 @@ app.templates.aboutPage = -> """ · 72 unmodified lines | |||
| 73 | <ul> | 73 | <ul> |
| 74 | <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a> ("We"). | 74 | <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a> ("We"). |
| 75 | <li>We do not collect personal information through the app. | 75 | <li>We do not collect personal information through the app. |
| 76 | <li>We use Google Analytics, Gauges and Sentry to collect anonymous traffic information and improve the app. | 76 | <li>We use Google Analytics and Gauges to collect anonymous traffic information if you have given consent to this. You can change your decision in the <a href="/settings">settings</a>. |
| 77 | <li>We use Sentry to collect crash data and improve the app. | ||
| 77 | <li>The app uses cookies to store user preferences. | 78 | <li>The app uses cookies to store user preferences. |
| 78 | <li>By using the app, you signify your acceptance of this policy. If you do not agree to this policy, please do not use the app. | 79 | <li>By using the app, you signify your acceptance of this policy. If you do not agree to this policy, please do not use the app. |
| 79 | <li>If you have any questions regarding privacy, please email <a href="mailto:privacy@freecodecamp.org">privacy@freecodecamp.org</a>. | 80 | <li>If you have any questions regarding privacy, please email <a href="mailto:privacy@freecodecamp.org">privacy@freecodecamp.org</a>. |
| @@ -87,7 +88,7 @@ credits = [ · 7 unmodified lines | |||
| 87 | 'https://creativecommons.org/licenses/by/4.0/' | 88 | 'https://creativecommons.org/licenses/by/4.0/' |
| 88 | ], [ | 89 | ], [ |
| 89 | 'Ansible', | 90 | 'Ansible', |
| 90 | '2012-2018 Michael DeHaan<br>© 2018 Red Hat, Inc.', | 91 | '2012-2018 Michael DeHaan<br>© 2018–2019 Red Hat, Inc.', |
| 91 | 'GPLv3', | 92 | 'GPLv3', |
| 92 | 'https://raw.githubusercontent.com/ansible/ansible/devel/COPYING' | 93 | 'https://raw.githubusercontent.com/ansible/ansible/devel/COPYING' |
| 93 | ], [ | 94 | ], [ |
| @@ -122,7 +123,7 @@ credits = [ · 28 unmodified lines | |||
| 122 | 'https://raw.githubusercontent.com/petkaantonov/bluebird/master/LICENSE' | 123 | 'https://raw.githubusercontent.com/petkaantonov/bluebird/master/LICENSE' |
| 123 | ], [ | 124 | ], [ |
| 124 | 'Bootstrap', | 125 | 'Bootstrap', |
| 125 | '2011-2018 Twitter, Inc.<br>2011-2018 The Bootstrap Authors', | 126 | '2011-2019 Twitter, Inc.<br>2011-2019 The Bootstrap Authors', |
| 126 | 'CC BY', | 127 | 'CC BY', |
| 127 | 'https://creativecommons.org/licenses/by/3.0/' | 128 | 'https://creativecommons.org/licenses/by/3.0/' |
| 128 | ], [ | 129 | ], [ |
| @@ -162,7 +163,7 @@ credits = [ · 33 unmodified lines | |||
| 162 | 'https://github.com/clojure/clojure/blob/master/epl-v10.html' | 163 | 'https://github.com/clojure/clojure/blob/master/epl-v10.html' |
| 163 | ], [ | 164 | ], [ |
| 164 | 'CMake', | 165 | 'CMake', |
| 165 | '2000-2018 Kitware, Inc. and Contributors', | 166 | '2000-2019 Kitware, Inc. and Contributors', |
| 166 | 'BSD', | 167 | 'BSD', |
| 167 | 'https://cmake.org/licensing/' | 168 | 'https://cmake.org/licensing/' |
| 168 | ], [ | 169 | ], [ |
| @@ -185,6 +186,11 @@ credits = [ · 16 unmodified lines | |||
| 185 | '2009-2018 Jeremy Ashkenas', | 186 | '2009-2018 Jeremy Ashkenas', |
| 186 | 'MIT', | 187 | 'MIT', |
| 187 | 'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE' | 188 | 'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE' |
| 189 | ], [ | ||
| 190 | 'Composer', | ||
| 191 | 'Nils Adermann, Jordi Boggiano', | ||
| 192 | 'MIT', | ||
| 193 | 'https://raw.githubusercontent.com/composer/composer/master/LICENSE' | ||
| 188 | ], [ | 194 | ], [ |
| 189 | 'Cordova', | 195 | 'Cordova', |
| 190 | '2012-2018 The Apache Software Foundation', | 196 | '2012-2018 The Apache Software Foundation', |
| @@ -201,6 +207,12 @@ credits = [ · 10 unmodified lines | |||
| 201 | 'Apache', | 207 | 'Apache', |
| 202 | 'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE' | 208 | 'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE' |
| 203 | ], [ | 209 | ], [ |
| 210 | 'Cypress', | ||
| 211 | '2017 Cypress.io', | ||
| 212 | 'MIT', | ||
| 213 | 'https://raw.githubusercontent.com/cypress-io/cypress-documentation/develop/LICENSE.md' | ||
| 214 | ], | ||
| 215 | [ | ||
| 204 | 'D', | 216 | 'D', |
| 205 | '1999-2018 The D Language Foundation', | 217 | '1999-2018 The D Language Foundation', |
| 206 | 'Boost', | 218 | 'Boost', |
| @@ -222,7 +234,7 @@ credits = [ · 15 unmodified lines | |||
| 222 | 'https://raw.githubusercontent.com/django/django/master/LICENSE' | 234 | 'https://raw.githubusercontent.com/django/django/master/LICENSE' |
| 223 | ], [ | 235 | ], [ |
| 224 | 'Docker', | 236 | 'Docker', |
| 225 | '2017 Docker, Inc.<br>Docker and the Docker logo are trademarks of Docker, Inc.', | 237 | '2019 Docker, Inc.<br>Docker and the Docker logo are trademarks of Docker, Inc.', |
| 226 | 'Apache', | 238 | 'Apache', |
| 227 | 'https://raw.githubusercontent.com/docker/docker.github.io/master/LICENSE' | 239 | 'https://raw.githubusercontent.com/docker/docker.github.io/master/LICENSE' |
| 228 | ], [ | 240 | ], [ |
| @@ -387,7 +399,7 @@ credits = [ · 158 unmodified lines | |||
| 387 | 'https://raw.githubusercontent.com/koajs/koa/master/LICENSE' | 399 | 'https://raw.githubusercontent.com/koajs/koa/master/LICENSE' |
| 388 | ], [ | 400 | ], [ |
| 389 | 'Kotlin', | 401 | 'Kotlin', |
| 390 | '2010-2018 JetBrains s.r.o.', | 402 | '2010-2019 JetBrains s.r.o.', |
| 391 | 'Apache', | 403 | 'Apache', |
| 392 | 'https://raw.githubusercontent.com/JetBrains/kotlin/master/license/LICENSE.txt' | 404 | 'https://raw.githubusercontent.com/JetBrains/kotlin/master/license/LICENSE.txt' |
| 393 | ], [ | 405 | ], [ |
| @@ -397,7 +409,7 @@ credits = [ · 3 unmodified lines | |||
| 397 | 'https://raw.githubusercontent.com/laravel/framework/master/LICENSE.txt' | 409 | 'https://raw.githubusercontent.com/laravel/framework/master/LICENSE.txt' |
| 398 | ], [ | 410 | ], [ |
| 399 | 'Leaflet', | 411 | 'Leaflet', |
| 400 | '2010-2018 Vladimir Agafonkin<br>© 2010-2011, CloudMade<br>Maps © OpenStreetMap contributors.', | 412 | '2010-2019 Vladimir Agafonkin<br>© 2010-2011, CloudMade<br>Maps © OpenStreetMap contributors.', |
| 401 | 'BSD', | 413 | 'BSD', |
| 402 | 'https://raw.githubusercontent.com/Leaflet/Leaflet/master/LICENSE' | 414 | 'https://raw.githubusercontent.com/Leaflet/Leaflet/master/LICENSE' |
| 403 | ], [ | 415 | ], [ |
| @@ -477,7 +489,7 @@ credits = [ · 73 unmodified lines | |||
| 477 | 'https://github.com/LearnBoost/mongoose/blob/master/README.md#license' | 489 | 'https://github.com/LearnBoost/mongoose/blob/master/README.md#license' |
| 478 | ], [ | 490 | ], [ |
| 479 | 'nginx', | 491 | 'nginx', |
| 480 | '2002-2018 Igor Sysoev<br>© 2011-2018 Nginx, Inc.', | 492 | '2002-2019 Igor Sysoev<br>© 2011-2019 Nginx, Inc.', |
| 481 | 'BSD', | 493 | 'BSD', |
| 482 | 'http://nginx.org/LICENSE' | 494 | 'http://nginx.org/LICENSE' |
| 483 | ], [ | 495 | ], [ |
| @@ -522,7 +534,7 @@ credits = [ · 38 unmodified lines | |||
| 522 | 'https://raw.githubusercontent.com/OpenTSDB/opentsdb.net/gh-pages/COPYING.LESSER' | 534 | 'https://raw.githubusercontent.com/OpenTSDB/opentsdb.net/gh-pages/COPYING.LESSER' |
| 523 | ], [ | 535 | ], [ |
| 524 | 'Padrino', | 536 | 'Padrino', |
| 525 | '2010-2016 Padrino', | 537 | '2010-2019 Padrino', |
| 526 | 'MIT', | 538 | 'MIT', |
| 527 | 'https://raw.githubusercontent.com/padrino/padrino-framework/master/padrino/LICENSE.txt' | 539 | 'https://raw.githubusercontent.com/padrino/padrino-framework/master/padrino/LICENSE.txt' |
| 528 | ], [ | 540 | ], [ |
| @@ -572,7 +584,7 @@ credits = [ · 43 unmodified lines | |||
| 572 | 'https://raw.githubusercontent.com/GoogleChrome/puppeteer/master/LICENSE' | 584 | 'https://raw.githubusercontent.com/GoogleChrome/puppeteer/master/LICENSE' |
| 573 | ], [ | 585 | ], [ |
| 574 | 'Pygame', | 586 | 'Pygame', |
| 575 | 'Pygame Developpers', | 587 | 'Pygame Developers', |
| 576 | 'LGPLv2.1', | 588 | 'LGPLv2.1', |
| 577 | 'https://raw.githubusercontent.com/pygame/pygame/master/LICENSE' | 589 | 'https://raw.githubusercontent.com/pygame/pygame/master/LICENSE' |
| 578 | ], [ | 590 | ], [ |
| @@ -636,11 +648,23 @@ credits = [ · 57 unmodified lines | |||
| 636 | 'MIT', | 648 | 'MIT', |
| 637 | 'https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT' | 649 | 'https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT' |
| 638 | ], [ | 650 | ], [ |
| 651 | 'Salt Stack', | ||
| 652 | '2019 SaltStack', | ||
| 653 | 'Apache', | ||
| 654 | 'https://raw.githubusercontent.com/saltstack/salt/develop/LICENSE' | ||
| 655 | ], | ||
| 656 | [ | ||
| 639 | 'Sass', | 657 | 'Sass', |
| 640 | '2006-2016 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein', | 658 | '2006-2016 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein', |
| 641 | 'MIT', | 659 | 'MIT', |
| 642 | 'https://raw.githubusercontent.com/sass/sass/stable/MIT-LICENSE' | 660 | 'https://raw.githubusercontent.com/sass/sass/stable/MIT-LICENSE' |
| 643 | ], [ | 661 | ], [ |
| 662 | 'Scala', | ||
| 663 | '2002-2019 EPFL, with contributions from Lightbend', | ||
| 664 | 'Apache', | ||
| 665 | 'https://raw.githubusercontent.com/scala/scala-lang/master/license.md' | ||
| 666 | ], | ||
| 667 | [ | ||
| 644 | 'scikit-image', | 668 | 'scikit-image', |
| 645 | '2011 the scikit-image team', | 669 | '2011 the scikit-image team', |
| 646 | 'BSD', | 670 | 'BSD', |
| @@ -715,6 +739,11 @@ credits = [ · 68 unmodified lines | |||
| 715 | '2013-2018 Evan You, Vue.js contributors', | 739 | '2013-2018 Evan You, Vue.js contributors', |
| 716 | 'MIT', | 740 | 'MIT', |
| 717 | 'https://raw.githubusercontent.com/vuejs/vue/master/LICENSE' | 741 | 'https://raw.githubusercontent.com/vuejs/vue/master/LICENSE' |
| 742 | ], [ | ||
| 743 | 'Vue Router', | ||
| 744 | '2013-present Evan You', | ||
| 745 | 'MIT', | ||
| 746 | 'https://raw.githubusercontent.com/vuejs/vue-router/dev/LICENSE' | ||
| 718 | ], [ | 747 | ], [ |
| 719 | 'Vulkan', | 748 | 'Vulkan', |
| 720 | '2014-2017 Khronos Group Inc.<br>Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc.', | 749 | '2014-2017 Khronos Group Inc.<br>Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc.', |
| @@ -726,6 +755,12 @@ credits = [ · 5 unmodified lines | |||
| 726 | 'CC BY', | 755 | 'CC BY', |
| 727 | 'https://creativecommons.org/licenses/by/4.0/' | 756 | 'https://creativecommons.org/licenses/by/4.0/' |
| 728 | ], [ | 757 | ], [ |
| 758 | 'Wordpress', | ||
| 759 | '2003-2019 WordPress Foundation', | ||
| 760 | 'GPLv2+', | ||
| 761 | 'https://wordpress.org/about/license/' | ||
| 762 | ], | ||
| 763 | [ | ||
| 729 | 'Yarn', | 764 | 'Yarn', |
| 730 | '2016-present Yarn Contributors', | 765 | '2016-present Yarn Contributors', |
| 731 | 'BSD', | 766 | 'BSD', |
| @@ -25,8 +25,8 @@ app.templates.offlinePage = (docs) -> """ · 24 unmodified lines | |||
| 25 | <h2 class="_block-heading">Questions & Answers</h2> | 25 | <h2 class="_block-heading">Questions & Answers</h2> |
| 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://devdocs.io/dom/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://devdocs.io/dom/service_worker_api/using_service_workers">Service Workers</a> and <a href="https://devdocs.io/dom/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,15 @@ 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 | reason = "aren't available in your browser (or are disabled)" |
| 48 | |||
| 49 | if app.config.env != 'production' | ||
| 50 | reason = "are disabled in your development instance of DevDocs (enable them by setting the <code>ENABLE_SERVICE_WORKER</code> environment variable to <code>true</code>)" | ||
| 51 | |||
| 52 | """ No. Service Workers #{reason}, 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). """ | 53 | The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """ |
| 49 | 54 | ||
| 50 | app.templates.offlineDoc = (doc, status) -> | 55 | app.templates.offlineDoc = (doc, status) -> |
| @@ -8,7 +8,7 @@ app.templates.intro = """ · 7 unmodified lines | |||
| 8 | <p>Thanks for downloading DevDocs. Here are a few things you should know: | 8 | <p>Thanks for downloading DevDocs. Here are a few things you should know: |
| 9 | <ol class="_intro-list"> | 9 | <ol class="_intro-list"> |
| 10 | <li>Your local version of DevDocs won't self-update. Unless you're modifying the code, | 10 | <li>Your local version of DevDocs won't self-update. Unless you're modifying the code, |
| 11 | I recommend using the hosted version at <a href="https://devdocs.io">devdocs.io</a>. | 11 | we recommend using the hosted version at <a href="https://devdocs.io">devdocs.io</a>. |
| 12 | <li>Run <code>thor docs:list</code> to see all available documentations. | 12 | <li>Run <code>thor docs:list</code> to see all available documentations. |
| 13 | <li>Run <code>thor docs:download <name></code> to download documentations. | 13 | <li>Run <code>thor docs:download <name></code> to download documentations. |
| 14 | <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations. | 14 | <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations. |
| @@ -39,7 +39,7 @@ app.templates.intro = """ · 24 unmodified lines | |||
| 39 | <li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed on <a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome</a>. | 39 | <li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed on <a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome</a>. |
| 40 | <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>. | 40 | <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>. |
| 41 | <li>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>. | 41 | <li>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>. |
| 42 | <iframe class="_github-btn" src="//ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe> | 42 | <object data="https://img.shields.io/github/stars/freeCodeCamp/devdocs.svg?style=social" type="image/svg+xml" aria-hidden="true"></object> |
| 43 | <li>And if you're new to coding, check out <a href="https://www.freecodecamp.org/">freeCodeCamp's open source curriculum</a>. | 43 | <li>And if you're new to coding, check out <a href="https://www.freecodecamp.org/">freeCodeCamp's open source curriculum</a>. |
| 44 | </ol> | 44 | </ol> |
| 45 | <p>Happy coding! | 45 | <p>Happy coding! |
| @@ -15,6 +15,14 @@ app.templates.settingsPage = (settings) -> """ · 14 unmodified lines | |||
| 15 | <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar | 15 | <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar |
| 16 | <small>Tip: drag the edge of the sidebar to resize it.</small> | 16 | <small>Tip: drag the edge of the sidebar to resize it.</small> |
| 17 | </label> | 17 | </label> |
| 18 | <label class="_settings-label"> | ||
| 19 | <input type="checkbox" form="settings" name="autoInstall" value="_auto-install"#{if settings.autoInstall then ' checked' else ''}>Automatically download documentation for offline use | ||
| 20 | <small>Only enable this when bandwidth isn't a concern to you.</small> | ||
| 21 | </label> | ||
| 22 | <label class="_settings-label"> | ||
| 23 | <input type="checkbox" form="settings" name="analyticsConsent"#{if settings.analyticsConsent then ' checked' else ''}>Enable tracking cookies | ||
| 24 | <small>With this checked, we enable Google Analytics and Gauges to collect anonymous traffic information.</small> | ||
| 25 | </label> | ||
| 18 | </div> | 26 | </div> |
| 19 | </div> | 27 | </div> |
| 20 | 28 | ||
| @@ -1,28 +1,32 @@ | |||
| 1 | try { | 1 | try { |
| 2 | if (app.config.env == 'production') { | 2 | if (app.config.env === 'production') { |
| 3 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | 3 | if (Cookies.get('analyticsConsent') === '1') { |
| 4 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | 4 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
| 5 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | 5 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
| 6 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | 6 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
| 7 | ga('create', 'UA-5544833-12', 'devdocs.io'); | 7 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); |
| 8 | page.track(function() { | 8 | ga('create', 'UA-5544833-12', 'devdocs.io'); |
| 9 | ga('send', 'pageview', { | 9 | page.track(function() { |
| 10 | page: location.pathname + location.search + location.hash, | 10 | ga('send', 'pageview', { |
| 11 | dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version | 11 | page: location.pathname + location.search + location.hash, |
| 12 | dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version | ||
| 13 | }); | ||
| 12 | }); | 14 | }); |
| 13 | }); | ||
| 14 | 15 | ||
| 15 | page.track(function() { | 16 | page.track(function() { |
| 16 | if (window._gauges) | 17 | if (window._gauges) |
| 17 | _gauges.push(['track']); | 18 | _gauges.push(['track']); |
| 18 | else | 19 | else |
| 19 | (function() { | 20 | (function() { |
| 20 | var _gauges=_gauges||[];!function(){var a=document.createElement("script"); | 21 | var _gauges=_gauges||[];!function(){var a=document.createElement("script"); |
| 21 | a.type="text/javascript",a.async=!0,a.id="gauges-tracker", | 22 | a.type="text/javascript",a.async=!0,a.id="gauges-tracker", |
| 22 | a.setAttribute("data-site-id","51c15f82613f5d7819000067"), | 23 | a.setAttribute("data-site-id","51c15f82613f5d7819000067"), |
| 23 | a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0]; | 24 | a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0]; |
| 24 | b.parentNode.insertBefore(a,b)}(); | 25 | b.parentNode.insertBefore(a,b)}(); |
| 25 | })(); | 26 | })(); |
| 26 | }); | 27 | }); |
| 28 | } else { | ||
| 29 | resetAnalytics(); | ||
| 30 | } | ||
| 27 | } | 31 | } |
| 28 | } catch(e) { } | 32 | } catch(e) { } |
| @@ -413,7 +413,7 @@ · 412 unmodified lines | |||
| 413 | // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched | 413 | // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched |
| 414 | // with the same identifier as the touch event that previously triggered the click that triggered the alert. | 414 | // with the same identifier as the touch event that previously triggered the click that triggered the alert. |
| 415 | // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an | 415 | // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an |
| 416 | // immediately preceeding touch event (issue #52), so this fix is unavailable on that platform. | 416 | // immediately preceding touch event (issue #52), so this fix is unavailable on that platform. |
| 417 | // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string, | 417 | // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string, |
| 418 | // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long, | 418 | // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long, |
| 419 | // random integers, it's safe to to continue if the identifier is 0 here. | 419 | // random integers, it's safe to to continue if the identifier is 0 here. |
| @@ -805,7 +805,7 @@ · 385 unmodified lines | |||
| 805 | } | 805 | } |
| 806 | } | 806 | } |
| 807 | 807 | ||
| 808 | // IE11: prefixed -ms-touch-action is no longer supported and it's recomended to use non-prefixed version | 808 | // IE11: prefixed -ms-touch-action is no longer supported and it's recommended to use non-prefixed version |
| 809 | // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx | 809 | // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx |
| 810 | if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') { | 810 | if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') { |
| 811 | return true; | 811 | return true; |
| @@ -856,7 +856,7 @@ Raven.prototype = { · 855 unmodified lines | |||
| 856 | }, | 856 | }, |
| 857 | 857 | ||
| 858 | _triggerEvent: function(eventType, options) { | 858 | _triggerEvent: function(eventType, options) { |
| 859 | // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it | 859 | // NOTE: `event` is a native browser thing, so let's avoid conflicting with it |
| 860 | var evt, key; | 860 | var evt, key; |
| 861 | 861 | ||
| 862 | if (!this._hasDocument) return; | 862 | if (!this._hasDocument) return; |
| @@ -153,6 +153,9 @@ class app.views.Content extends app.View · 152 unmodified lines | |||
| 153 | return | 153 | return |
| 154 | 154 | ||
| 155 | afterRoute: (route, context) => | 155 | afterRoute: (route, context) => |
| 156 | if route != 'entry' and route != 'type' | ||
| 157 | resetFavicon() | ||
| 158 | |||
| 156 | switch route | 159 | switch route |
| 157 | when 'root' | 160 | when 'root' |
| 158 | @show @rootPage | 161 | @show @rootPage |
| @@ -40,6 +40,7 @@ class app.views.EntryPage extends app.View · 39 unmodified lines | |||
| 40 | if app.disabledDocs.findBy 'slug', @entry.doc.slug | 40 | if app.disabledDocs.findBy 'slug', @entry.doc.slug |
| 41 | @hiddenView = new app.views.HiddenPage @el, @entry | 41 | @hiddenView = new app.views.HiddenPage @el, @entry |
| 42 | 42 | ||
| 43 | setFaviconForDoc(@entry.doc) | ||
| 43 | @delay @polyfillMathML | 44 | @delay @polyfillMathML |
| 44 | @trigger 'loaded' | 45 | @trigger 'loaded' |
| 45 | return | 46 | return |
| @@ -123,7 +124,7 @@ class app.views.EntryPage extends app.View · 77 unmodified lines | |||
| 123 | @render @tmpl('pageLoadError') | 124 | @render @tmpl('pageLoadError') |
| 124 | @resetClass() | 125 | @resetClass() |
| 125 | @addClass @constructor.errorClass | 126 | @addClass @constructor.errorClass |
| 126 | app.appCache?.update() | 127 | app.serviceWorker?.update() |
| 127 | return | 128 | return |
| 128 | 129 | ||
| 129 | cache: -> | 130 | cache: -> |
| @@ -1,7 +1,4 @@ | |||
| 1 | class app.views.SettingsPage extends app.View | 1 | class app.views.SettingsPage extends app.View |
| 2 | LAYOUTS = ['_max-width', '_sidebar-hidden', '_native-scrollbars'] | ||
| 3 | SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden' | ||
| 4 | |||
| 5 | @className: '_static' | 2 | @className: '_static' |
| 6 | 3 | ||
| 7 | @events: | 4 | @events: |
| @@ -17,31 +14,31 @@ class app.views.SettingsPage extends app.View · 9 unmodified lines | |||
| 17 | settings.dark = app.settings.get('dark') | 14 | settings.dark = app.settings.get('dark') |
| 18 | settings.smoothScroll = !app.settings.get('fastScroll') | 15 | settings.smoothScroll = !app.settings.get('fastScroll') |
| 19 | settings.arrowScroll = app.settings.get('arrowScroll') | 16 | settings.arrowScroll = app.settings.get('arrowScroll') |
| 20 | settings[layout] = app.settings.hasLayout(layout) for layout in LAYOUTS | 17 | settings.autoInstall = app.settings.get('autoInstall') |
| 18 | settings.analyticsConsent = app.settings.get('analyticsConsent') | ||
| 19 | settings[layout] = app.settings.hasLayout(layout) for layout in app.settings.LAYOUTS | ||
| 21 | settings | 20 | settings |
| 22 | 21 | ||
| 23 | getTitle: -> | 22 | getTitle: -> |
| 24 | 'Preferences' | 23 | 'Preferences' |
| 25 | 24 | ||
| 26 | toggleDark: (enable) -> | 25 | toggleDark: (enable) -> |
| 27 | html = document.documentElement | ||
| 28 | html.classList.toggle('_theme-default') | ||
| 29 | html.classList.toggle('_theme-dark') | ||
| 30 | app.settings.set('dark', !!enable) | 26 | app.settings.set('dark', !!enable) |
| 31 | app.appCache?.updateInBackground() | ||
| 32 | return | 27 | return |
| 33 | 28 | ||
| 34 | toggleLayout: (layout, enable) -> | 29 | toggleLayout: (layout, enable) -> |
| 35 | document.body.classList[if enable then 'add' else 'remove'](layout) unless layout is SIDEBAR_HIDDEN_LAYOUT | ||
| 36 | document.body.classList[if $.overlayScrollbarsEnabled() then 'add' else 'remove']('_overlay-scrollbars') | ||
| 37 | app.settings.setLayout(layout, enable) | 30 | app.settings.setLayout(layout, enable) |
| 38 | app.appCache?.updateInBackground() | ||
| 39 | return | 31 | return |
| 40 | 32 | ||
| 41 | toggleSmoothScroll: (enable) -> | 33 | toggleSmoothScroll: (enable) -> |
| 42 | app.settings.set('fastScroll', !enable) | 34 | app.settings.set('fastScroll', !enable) |
| 43 | return | 35 | return |
| 44 | 36 | ||
| 37 | toggleAnalyticsConsent: (enable) -> | ||
| 38 | app.settings.set('analyticsConsent', if enable then '1' else '0') | ||
| 39 | resetAnalytics() unless enable | ||
| 40 | return | ||
| 41 | |||
| 45 | toggle: (name, enable) -> | 42 | toggle: (name, enable) -> |
| 46 | app.settings.set(name, enable) | 43 | app.settings.set(name, enable) |
| 47 | return | 44 | return |
| @@ -85,6 +82,8 @@ class app.views.SettingsPage extends app.View · 37 unmodified lines | |||
| 85 | @toggleSmoothScroll input.checked | 82 | @toggleSmoothScroll input.checked |
| 86 | when 'import' | 83 | when 'import' |
| 87 | @import input.files[0], input | 84 | @import input.files[0], input |
| 85 | when 'analyticsConsent' | ||
| 86 | @toggleAnalyticsConsent input.checked | ||
| 88 | else | 87 | else |
| 89 | @toggle input.name, input.checked | 88 | @toggle input.name, input.checked |
| 90 | return | 89 | return |
| @@ -9,6 +9,7 @@ class app.views.TypePage extends app.View · 8 unmodified lines | |||
| 9 | 9 | ||
| 10 | render: (@type) -> | 10 | render: (@type) -> |
| 11 | @html @tmpl('typePage', @type) | 11 | @html @tmpl('typePage', @type) |
| 12 | setFaviconForDoc(@type.doc) | ||
| 12 | return | 13 | return |
| 13 | 14 | ||
| 14 | getTitle: -> | 15 | getTitle: -> |
| @@ -75,9 +75,11 @@ class app.views.Document extends app.View · 74 unmodified lines | |||
| 75 | return unless target.hasAttribute('data-behavior') | 75 | return unless target.hasAttribute('data-behavior') |
| 76 | $.stopEvent(event) | 76 | $.stopEvent(event) |
| 77 | switch target.getAttribute('data-behavior') | 77 | switch target.getAttribute('data-behavior') |
| 78 | when 'back' then history.back() | 78 | when 'back' then history.back() |
| 79 | when 'reload' then window.location.reload() | 79 | when 'reload' then window.location.reload() |
| 80 | when 'reboot' then app.reboot() | 80 | when 'reboot' then app.reboot() |
| 81 | when 'hard-reload' then app.reload() | 81 | when 'hard-reload' then app.reload() |
| 82 | when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?') | 82 | when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?') |
| 83 | when 'accept-analytics' then Cookies.set('analyticsConsent', '1', expires: 1e8) && app.reboot() | ||
| 84 | when 'decline-analytics' then Cookies.set('analyticsConsent', '0', expires: 1e8) && app.reboot() | ||
| 83 | return | 85 | return |
| @@ -11,9 +11,6 @@ class app.views.Resizer extends app.View · 10 unmodified lines | |||
| 11 | init: -> | 11 | init: -> |
| 12 | @el.setAttribute('draggable', 'true') | 12 | @el.setAttribute('draggable', 'true') |
| 13 | @appendTo $('._app') | 13 | @appendTo $('._app') |
| 14 | |||
| 15 | @style = $('style[data-resizer]') | ||
| 16 | @size = @style.getAttribute('data-size') | ||
| 17 | return | 14 | return |
| 18 | 15 | ||
| 19 | MIN = 260 | 16 | MIN = 260 |
| @@ -24,15 +21,11 @@ class app.views.Resizer extends app.View · 4 unmodified lines | |||
| 24 | return unless value > 0 | 21 | return unless value > 0 |
| 25 | value = Math.min(Math.max(Math.round(value), MIN), MAX) | 22 | value = Math.min(Math.max(Math.round(value), MIN), MAX) |
| 26 | newSize = "#{value}px" | 23 | newSize = "#{value}px" |
| 27 | @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize) | 24 | document.documentElement.style.setProperty('--sidebarWidth', newSize) |
| 28 | @size = newSize | 25 | app.settings.setSize(value) if save |
| 29 | if save | ||
| 30 | app.settings.setSize(value) | ||
| 31 | app.appCache?.updateInBackground() | ||
| 32 | return | 26 | return |
| 33 | 27 | ||
| 34 | onDragStart: (event) => | 28 | onDragStart: (event) => |
| 35 | @style.removeAttribute('disabled') | ||
| 36 | event.dataTransfer.effectAllowed = 'link' | 29 | event.dataTransfer.effectAllowed = 'link' |
| 37 | event.dataTransfer.setData('Text', '') | 30 | event.dataTransfer.setData('Text', '') |
| 38 | $.on(window, 'dragover', @onDrag) | 31 | $.on(window, 'dragover', @onDrag) |
| @@ -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 | ||
| @@ -30,6 +30,9 @@ class app.views.Search extends app.View · 29 unmodified lines | |||
| 30 | .on 'results', @onResults | 30 | .on 'results', @onResults |
| 31 | .on 'end', @onEnd | 31 | .on 'end', @onEnd |
| 32 | 32 | ||
| 33 | @scope | ||
| 34 | .on 'change', @onScopeChange | ||
| 35 | |||
| 33 | app.on 'ready', @onReady | 36 | app.on 'ready', @onReady |
| 34 | $.on window, 'hashchange', @searchUrl | 37 | $.on window, 'hashchange', @searchUrl |
| 35 | $.on window, 'focus', @onWindowFocus | 38 | $.on window, 'focus', @onWindowFocus |
| @@ -138,6 +141,11 @@ class app.views.Search extends app.View · 102 unmodified lines | |||
| 138 | $.stopEvent(event) | 141 | $.stopEvent(event) |
| 139 | return | 142 | return |
| 140 | 143 | ||
| 144 | onScopeChange: => | ||
| 145 | @value = '' | ||
| 146 | @onInput() | ||
| 147 | return | ||
| 148 | |||
| 141 | afterRoute: (name, context) => | 149 | afterRoute: (name, context) => |
| 142 | return if app.shortcuts.eventInProgress?.name is 'escape' | 150 | return if app.shortcuts.eventInProgress?.name is 'escape' |
| 143 | @reset(true) if not context.init and app.router.isIndex() | 151 | @reset(true) if not context.init and app.router.isIndex() |
| @@ -6,7 +6,9 @@ class app.views.SearchScope extends app.View · 5 unmodified lines | |||
| 6 | tag: '._search-tag' | 6 | tag: '._search-tag' |
| 7 | 7 | ||
| 8 | @events: | 8 | @events: |
| 9 | click: 'onClick' | ||
| 9 | keydown: 'onKeydown' | 10 | keydown: 'onKeydown' |
| 11 | textInput: 'onTextInput' | ||
| 10 | 12 | ||
| 11 | @routes: | 13 | @routes: |
| 12 | after: 'afterRoute' | 14 | after: 'afterRoute' |
| @@ -87,17 +89,33 @@ class app.views.SearchScope extends app.View · 74 unmodified lines | |||
| 87 | @trigger 'change', null, previousDoc | 89 | @trigger 'change', null, previousDoc |
| 88 | return | 90 | return |
| 89 | 91 | ||
| 92 | doScopeSearch: (event) => | ||
| 93 | @search @input.value[0...@input.selectionStart] | ||
| 94 | $.stopEvent(event) if @doc | ||
| 95 | return | ||
| 96 | |||
| 97 | onClick: (event) => | ||
| 98 | if event.target is @tag | ||
| 99 | @reset() | ||
| 100 | $.stopEvent(event) | ||
| 101 | return | ||
| 102 | |||
| 90 | onKeydown: (event) => | 103 | onKeydown: (event) => |
| 91 | if event.which is 8 # backspace | 104 | if event.which is 8 # backspace |
| 92 | if @doc and not @input.value | 105 | if @doc and @input.selectionEnd is 0 |
| 93 | $.stopEvent(event) | ||
| 94 | @reset() | 106 | @reset() |
| 95 | else if not @doc and @input.value | 107 | $.stopEvent(event) |
| 108 | else if not @doc and @input.value and not $.isChromeForAndroid() | ||
| 96 | return if event.ctrlKey or event.metaKey or event.altKey or event.shiftKey | 109 | return if event.ctrlKey or event.metaKey or event.altKey or event.shiftKey |
| 97 | if event.which is 9 or # tab | 110 | if event.which is 9 or # tab |
| 98 | (event.which is 32 and app.isMobile()) # space | 111 | (event.which is 32 and app.isMobile()) # space |
| 99 | @search @input.value[0...@input.selectionStart] | 112 | @doScopeSearch(event) |
| 100 | $.stopEvent(event) if @doc | 113 | return |
| 114 | |||
| 115 | onTextInput: (event) => | ||
| 116 | return unless $.isChromeForAndroid() | ||
| 117 | if not @doc and @input.value and event.data == ' ' | ||
| 118 | @doScopeSearch(event) | ||
| 101 | return | 119 | return |
| 102 | 120 | ||
| 103 | extractHashValue: -> | 121 | extractHashValue: -> |
| @@ -28,7 +28,7 @@ class app.views.Sidebar extends app.View · 27 unmodified lines | |||
| 28 | 28 | ||
| 29 | app.on 'ready', @onReady | 29 | app.on 'ready', @onReady |
| 30 | 30 | ||
| 31 | $.on document.documentElement, 'mouseleave', (event) => @display() if event.clientX < 10 | 31 | $.on document.documentElement, 'mouseleave', (event) => @display() unless event.clientX <= 0 |
| 32 | $.on document.documentElement, 'mouseenter', => @resetDisplay(forceNoHover: false) | 32 | $.on document.documentElement, 'mouseenter', => @resetDisplay(forceNoHover: false) |
| 33 | return | 33 | return |
| 34 | 34 | ||