Showing the changes to assets/javascripts (27 of 293 files). Show all
Unified · Split
| @@ -13,10 +13,12 @@ · 12 unmodified lines | ||
| 13 | 13 | |
| 14 | 14 | @el = $('._app') |
| 15 | 15 | @localStorage = new LocalStorageStore |
| 16 | − @appCache = new app.AppCache if app.AppCache.isEnabled() | |
| 16 | + @serviceWorker = new app.ServiceWorker if app.ServiceWorker.isEnabled() | |
| 17 | 17 | @settings = new app.Settings |
| 18 | 18 | @db = new app.DB() |
| 19 | 19 | |
| 20 | + @settings.initLayout() | |
| 21 | + | |
| 20 | 22 | @docs = new app.collections.Docs |
| 21 | 23 | @disabledDocs = new app.collections.Docs |
| 22 | 24 | @entries = new app.collections.Entries |
| @@ -150,7 +152,7 @@ · 127 unmodified lines | ||
| 150 | 152 | saveDocs: -> |
| 151 | 153 | @settings.setDocs(doc.slug for doc in @docs.all()) |
| 152 | 154 | @db.migrate() |
| 153 | − @appCache?.updateInBackground() | |
| 155 | + @serviceWorker?.updateInBackground() | |
| 154 | 156 | |
| 155 | 157 | welcomeBack: -> |
| 156 | 158 | visitCount = @settings.get('count') |
| @@ -160,17 +162,24 @@ · 3 unmodified lines | ||
| 160 | 162 | new app.views.Updates() |
| 161 | 163 | @updateChecker = new app.UpdateChecker() |
| 162 | 164 | |
| 165 | + reboot: -> | |
| 166 | + if location.pathname isnt '/' and location.pathname isnt '/settings' | |
| 167 | + window.location = "/##{location.pathname}" | |
| 168 | + else | |
| 169 | + window.location = '/' | |
| 170 | + return | |
| 171 | + | |
| 163 | 172 | reload: -> |
| 164 | 173 | @docs.clearCache() |
| 165 | 174 | @disabledDocs.clearCache() |
| 166 | − if @appCache then @appCache.reload() else window.location = '/' | |
| 175 | + if @serviceWorker then @serviceWorker.reload() else @reboot() | |
| 167 | 176 | return |
| 168 | 177 | |
| 169 | 178 | reset: -> |
| 170 | 179 | @localStorage.reset() |
| 171 | 180 | @settings.reset() |
| 172 | 181 | @db?.reset() |
| 173 | − @appCache?.update() | |
| 182 | + @serviceWorker?.update() | |
| 174 | 183 | window.location = '/' |
| 175 | 184 | return |
| 176 | 185 | |
| @@ -189,9 +198,9 @@ · 12 unmodified lines | ||
| 189 | 198 | return |
| 190 | 199 | |
| 191 | 200 | indexHost: -> |
| 192 | − # 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 | |
| 193 | 202 | # enabled because it doesn't support caching URLs that use CORS. |
| 194 | − @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'] | |
| 195 | 204 | |
| 196 | 205 | onBootError: (args...) -> |
| 197 | 206 | @trigger 'bootError' |
| @@ -248,7 +257,7 @@ · 50 unmodified lines | ||
| 248 | 257 | matchMedia: !!window.matchMedia |
| 249 | 258 | insertAdjacentHTML: !!document.body.insertAdjacentHTML |
| 250 | 259 | defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false |
| 251 | − cssGradients: supportsCssGradients() | |
| 260 | + cssVariables: !!CSS?.supports?('(--t: 0)') | |
| 252 | 261 | |
| 253 | 262 | for key, value of features when not value |
| 254 | 263 | Raven.captureMessage "unsupported/#{key}", level: 'info' |
| @@ -271,9 +280,4 @@ · 16 unmodified lines | ||
| 271 | 280 | isInvalidLocation: -> |
| 272 | 281 | @config.env is 'production' and location.host.indexOf(app.config.production_host) isnt 0 |
| 273 | 282 | |
| 274 | −supportsCssGradients = -> | |
| 275 | − el = document.createElement('div') | |
| 276 | − el.style.cssText = "background-image: -webkit-linear-gradient(top, #000, #fff); background-image: linear-gradient(to top, #000, #fff);" | |
| 277 | − el.style.backgroundImage.indexOf('gradient') >= 0 | |
| 278 | − | |
| 279 | 283 | $.extend app, Events |
| @@ -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', -> window.location = '/' | |
| 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,5 @@ app.config = · 12 unmodified lines | ||
| 13 | 13 | version: <%= Time.now.to_i %> |
| 14 | 14 | release: <%= Time.now.utc.httpdate.to_json %> |
| 15 | 15 | mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css' |
| 16 | + service_worker_path: '/service-worker.js' | |
| 17 | + 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,6 +5,7 @@ class app.Settings · 4 unmodified lines | ||
| 5 | 5 | 'manualUpdate' |
| 6 | 6 | 'fastScroll' |
| 7 | 7 | 'arrowScroll' |
| 8 | + 'analyticsConsent' | |
| 8 | 9 | 'docs' |
| 9 | 10 | 'dark' |
| 10 | 11 | 'layout' |
| @@ -20,6 +21,8 @@ class app.Settings · 9 unmodified lines | ||
| 20 | 21 | 'news' |
| 21 | 22 | ] |
| 22 | 23 | |
| 24 | + LAYOUTS: ['_max-width', '_sidebar-hidden', '_native-scrollbars'] | |
| 25 | + | |
| 23 | 26 | @defaults: |
| 24 | 27 | count: 0 |
| 25 | 28 | hideDisabled: false |
| @@ -27,6 +30,7 @@ class app.Settings · 1 unmodified line | ||
| 27 | 30 | news: 0 |
| 28 | 31 | manualUpdate: false |
| 29 | 32 | schema: 1 |
| 33 | + analyticsConsent: false | |
| 30 | 34 | |
| 31 | 35 | constructor: -> |
| 32 | 36 | @store = new CookieStore |
| @@ -39,6 +43,7 @@ class app.Settings · 6 unmodified lines | ||
| 39 | 43 | set: (key, value) -> |
| 40 | 44 | @store.set(key, value) |
| 41 | 45 | delete @cache[key] |
| 46 | + @toggleDark(value) if key == 'dark' | |
| 42 | 47 | return |
| 43 | 48 | |
| 44 | 49 | del: (key) -> |
| @@ -64,6 +69,8 @@ class app.Settings · 19 unmodified lines | ||
| 64 | 69 | return |
| 65 | 70 | |
| 66 | 71 | setLayout: (name, enable) -> |
| 72 | + @toggleLayout(name, enable) | |
| 73 | + | |
| 67 | 74 | layout = (@store.get('layout') || '').split(' ') |
| 68 | 75 | $.arrayDelete(layout, '') |
| 69 | 76 | |
| @@ -105,3 +112,28 @@ class app.Settings · 35 unmodified lines | ||
| 105 | 112 | @store.reset() |
| 106 | 113 | @cache = {} |
| 107 | 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 | 3 | @lastCheck = Date.now() |
| 4 | 4 | |
| 5 | 5 | $.on window, 'focus', @onFocus |
| 6 | − app.appCache.on 'updateready', @onUpdateReady if app.appCache | |
| 6 | + app.serviceWorker?.on 'updateready', @onUpdateReady | |
| 7 | 7 | |
| 8 | 8 | setTimeout @checkDocs, 0 |
| 9 | 9 | |
| 10 | 10 | check: -> |
| 11 | − if app.appCache | |
| 12 | − app.appCache.update() | |
| 11 | + if app.serviceWorker | |
| 12 | + app.serviceWorker.update() | |
| 13 | 13 | else |
| 14 | 14 | ajax |
| 15 | 15 | url: $('script[src*="application"]').getAttribute('src') |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | ### |
| 2 | − * Copyright 2013-2018 Thibaut Courouble and other contributors | |
| 2 | + * Copyright 2013-2019 Thibaut Courouble and other contributors | |
| 3 | 3 | * |
| 4 | 4 | * This source code is licensed under the terms of the Mozilla |
| 5 | 5 | * Public License, v. 2.0, a copy of which may be obtained at: |
| @@ -190,7 +190,7 @@ isSameOrigin = (url) -> · 189 unmodified lines | ||
| 190 | 190 | |
| 191 | 191 | updateCanonicalLink = -> |
| 192 | 192 | @canonicalLink ||= document.head.querySelector('link[rel="canonical"]') |
| 193 | − @canonicalLink.setAttribute('href', "http://#{location.host}#{location.pathname}") | |
| 193 | + @canonicalLink.setAttribute('href', "https://#{location.host}#{location.pathname}") | |
| 194 | 194 | |
| 195 | 195 | trackers = [] |
| 196 | 196 | |
| @@ -199,5 +199,21 @@ page.track = (fn) -> · 2 unmodified lines | ||
| 199 | 199 | return |
| 200 | 200 | |
| 201 | 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] == '_' | |
| 218 | + Cookies.expire(name) | |
| 203 | 219 | return |
| @@ -352,6 +352,10 @@ isIE = null · 351 unmodified lines | ||
| 352 | 352 | $.isIE = -> |
| 353 | 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 | 359 | isAndroid = null |
| 356 | 360 | $.isAndroid = -> |
| 357 | 361 | isAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 |
| @@ -1,5 +1,12 @@ | ||
| 1 | 1 | [ |
| 2 | 2 | [ |
| 3 | + "2019-07-19", | |
| 4 | + "Replaced the AppCache with a Service Worker (which makes DevDocs an installable PWA) and fixed layout preferences on Firefox." | |
| 5 | + ], | |
| 6 | + [ | |
| 7 | + "2018-09-23", | |
| 8 | + "New documentations: <a href=\"/puppeteer/\">Puppeteer</a> and <a href=\"/handlebars/\">Handlebars.js</a>" | |
| 9 | + ], [ | |
| 3 | 10 | "2018-08-12", |
| 4 | 11 | "New documentations: <a href=\"/dart/\">Dart</a> and <a href=\"/qt/\">Qt</a>" |
| 5 | 12 | ], [ |
| @@ -12,8 +12,8 @@ app.templates.notFoundPage = -> · 11 unmodified lines | ||
| 12 | 12 | |
| 13 | 13 | app.templates.pageLoadError = -> |
| 14 | 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> | |
| 16 | − If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """, | |
| 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're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """, | |
| 17 | 17 | """ #{back} · <a href="/##{location.pathname}" target="_top" class="_error-link">Reload</a> |
| 18 | 18 | · <a href="#" class="_error-link" data-retry>Retry</a> """ |
| 19 | 19 | |
| @@ -56,21 +56,18 @@ app.templates.unsupportedBrowser = """ · 36 unmodified lines | ||
| 56 | 56 | <h1 class="_fail-title">Your browser is unsupported, sorry.</h1> |
| 57 | 57 | <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers: |
| 58 | 58 | <ul class="_fail-list"> |
| 59 | − <li>Recent versions of Chrome and Firefox | |
| 60 | − <li>Safari 5.1+ | |
| 61 | − <li>Opera 12.1+ | |
| 62 | − <li>Internet Explorer 10+ | |
| 63 | − <li>iOS 6+ | |
| 64 | − <li>Android 4.1+ | |
| 65 | − <li>Windows Phone 8+ | |
| 59 | + <li>Recent versions of Firefox, Chrome, or Opera | |
| 60 | + <li>Safari 11.1+ | |
| 61 | + <li>Edge 17+ | |
| 62 | + <li>iOS 11.3+ | |
| 66 | 63 | </ul> |
| 67 | 64 | <p class="_fail-text"> |
| 68 | − If you're unable to upgrade, I apologize. | |
| 69 | − I decided to prioritize speed and new features over support for older browsers. | |
| 65 | + If you're unable to upgrade, we apologize. | |
| 66 | + We decided to prioritize speed and new features over support for older browsers. | |
| 70 | 67 | <p class="_fail-text"> |
| 71 | 68 | Note: if you're already using one of the browsers above, check your settings and add-ons. |
| 72 | 69 | The app uses feature detection, not user agent sniffing. |
| 73 | 70 | <p class="_fail-text"> |
| 74 | − — Thibaut <a href="https://twitter.com/DevDocs" class="_fail-link">@DevDocs</a> | |
| 71 | + — <a href="https://twitter.com/DevDocs">@DevDocs</a> | |
| 75 | 72 | </div> |
| 76 | 73 | """ |
| @@ -68,3 +68,9 @@ app.templates.notifShare = -> · 67 unmodified lines | ||
| 68 | 68 | app.templates.notifUpdateDocs = -> |
| 69 | 69 | textNotif """ Documentation updates available. """, |
| 70 | 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> """ | |
| @@ -11,22 +11,18 @@ app.templates.aboutPage = -> """ · 10 unmodified lines | ||
| 11 | 11 | </nav> |
| 12 | 12 | |
| 13 | 13 | <h1 class="_lined-heading">DevDocs: API Documentation Browser</h1> |
| 14 | − <p>DevDocs combines multiple API documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more. | |
| 15 | − <ul> | |
| 16 | − <li>Created and maintained by <a href="https://thibaut.me">Thibaut Courouble</a> | |
| 17 | − <li>Free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a> | |
| 18 | − <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20" tabindex="-1"></iframe> | |
| 19 | − </ul> | |
| 14 | + <p>DevDocs combines multiple developer documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more. | |
| 15 | + <p>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>. It was created by <a href="https://thibaut.me">Thibaut Courouble</a> and is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a>. | |
| 20 | 16 | <p>To keep up-to-date with the latest news: |
| 21 | 17 | <ul> |
| 22 | 18 | <li>Follow <a href="https://twitter.com/DevDocs">@DevDocs</a> on Twitter |
| 23 | − <li>Watch the repository on <a href="https://github.com/freeCodeCamp/devdocs/subscription">GitHub</a> | |
| 24 | − <li>Join the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a> | |
| 19 | + <li>Watch the repository on <a href="https://github.com/freeCodeCamp/devdocs/subscription">GitHub</a> <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20" tabindex="-1"></iframe> | |
| 20 | + <li>Join the <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a> chat room | |
| 25 | 21 | </ul> |
| 26 | 22 | |
| 27 | 23 | <h2 class="_block-heading" id="copyright">Copyright and License</h2> |
| 28 | 24 | <p class="_note"> |
| 29 | − <strong>Copyright 2013–2018 Thibaut Courouble and <a href="https://github.com/freeCodeCamp/devdocs/graphs/contributors">other contributors</a></strong><br> | |
| 25 | + <strong>Copyright 2013–2019 Thibaut Courouble and <a href="https://github.com/freeCodeCamp/devdocs/graphs/contributors">other contributors</a></strong><br> | |
| 30 | 26 | This software is licensed under the terms of the Mozilla Public License v2.0.<br> |
| 31 | 27 | You may obtain a copy of the source code at <a href="https://github.com/freeCodeCamp/devdocs">github.com/freeCodeCamp/devdocs</a>.<br> |
| 32 | 28 | For more information, see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a> |
| @@ -48,11 +44,10 @@ app.templates.aboutPage = -> """ · 15 unmodified lines | ||
| 48 | 44 | <dt>Where can I suggest new docs and features? |
| 49 | 45 | <dd>You can suggest and vote for new docs on the <a href="https://trello.com/b/6BmTulfx/devdocs-documentation">Trello board</a>.<br> |
| 50 | 46 | If you have a specific feature request, add it to the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>.<br> |
| 51 | − Otherwise use the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>. | |
| 47 | + Otherwise, come talk to us in the <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a> chat room. | |
| 52 | 48 | <dt>Where can I report bugs? |
| 53 | 49 | <dd>In the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>. Thanks! |
| 54 | 50 | </dl> |
| 55 | − <p>For anything else, feel free to email me at <a href="mailto:thibaut@devdocs.io">thibaut@devdocs.io</a>. | |
| 56 | 51 | |
| 57 | 52 | <h2 class="_block-heading" id="credits">Credits</h2> |
| 58 | 53 | |
| @@ -76,12 +71,13 @@ app.templates.aboutPage = -> """ · 17 unmodified lines | ||
| 76 | 71 | |
| 77 | 72 | <h2 class="_block-heading" id="privacy">Privacy Policy</h2> |
| 78 | 73 | <ul> |
| 79 | − <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by Thibaut Courouble ("We"). | |
| 80 | − <li>We do not collect personal information. | |
| 81 | − <li>We use Google Analytics, Gauges and Sentry to collect anonymous traffic information and improve the app. | |
| 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. | |
| 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. | |
| 82 | 78 | <li>The app uses cookies to store user preferences. |
| 83 | 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. |
| 84 | − <li>If you have any questions regarding privacy, please email <a href="mailto:thibaut@devdocs.io">thibaut@devdocs.io</a>. | |
| 80 | + <li>If you have any questions regarding privacy, please email <a href="mailto:privacy@freecodecamp.org">privacy@freecodecamp.org</a>. | |
| 85 | 81 | </ul> |
| 86 | 82 | """ |
| 87 | 83 | |
| @@ -102,7 +98,7 @@ credits = [ · 14 unmodified lines | ||
| 102 | 98 | 'https://www.apache.org/licenses/LICENSE-2.0' |
| 103 | 99 | ], [ |
| 104 | 100 | 'Async', |
| 105 | − '2010-2017 Caolan McMahon', | |
| 101 | + '2010-2018 Caolan McMahon', | |
| 106 | 102 | 'MIT', |
| 107 | 103 | 'https://raw.githubusercontent.com/caolan/async/master/LICENSE' |
| 108 | 104 | ], [ |
| @@ -192,7 +188,7 @@ credits = [ · 83 unmodified lines | ||
| 192 | 188 | 'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE' |
| 193 | 189 | ], [ |
| 194 | 190 | 'Cordova', |
| 195 | − '2012-2017 The Apache Software Foundation', | |
| 191 | + '2012-2018 The Apache Software Foundation', | |
| 196 | 192 | 'Apache', |
| 197 | 193 | 'https://raw.githubusercontent.com/apache/cordova-docs/master/LICENSE' |
| 198 | 194 | ], [ |
| @@ -310,6 +306,11 @@ credits = [ · 111 unmodified lines | ||
| 310 | 306 | 'GruntJS Team', |
| 311 | 307 | 'MIT', |
| 312 | 308 | 'https://github.com/gruntjs/grunt-docs/blob/master/package.json#L10' |
| 309 | + ], [ | |
| 310 | + 'Handlebars', | |
| 311 | + '2011-2017 Yehuda Katz', | |
| 312 | + 'MIT', | |
| 313 | + 'https://raw.githubusercontent.com/wycats/handlebars.js/master/LICENSE' | |
| 313 | 314 | ], [ |
| 314 | 315 | 'Haskell', |
| 315 | 316 | 'The University of Glasgow', |
| @@ -367,7 +368,7 @@ credits = [ · 51 unmodified lines | ||
| 367 | 368 | 'https://raw.githubusercontent.com/jquery/api.jqueryui.com/master/LICENSE.txt' |
| 368 | 369 | ], [ |
| 369 | 370 | 'Julia', |
| 370 | − '2009-2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors', | |
| 371 | + '2009-2018 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors', | |
| 371 | 372 | 'MIT', |
| 372 | 373 | 'https://raw.githubusercontent.com/JuliaLang/julia/master/LICENSE.md' |
| 373 | 374 | ], [ |
| @@ -437,7 +438,7 @@ credits = [ · 63 unmodified lines | ||
| 437 | 438 | 'https://daringfireball.net/projects/markdown/license' |
| 438 | 439 | ], [ |
| 439 | 440 | 'Matplotlib', |
| 440 | − '2012-2017 Matplotlib Development Team. All rights reserved.', | |
| 441 | + '2012-2018 Matplotlib Development Team. All rights reserved.', | |
| 441 | 442 | 'Custom', |
| 442 | 443 | 'https://raw.githubusercontent.com/matplotlib/matplotlib/master/LICENSE/LICENSE' |
| 443 | 444 | ], [ |
| @@ -560,6 +561,11 @@ credits = [ · 116 unmodified lines | ||
| 560 | 561 | '1996-2018 The PostgreSQL Global Development Group<br>© 1994 The Regents of the University of California', |
| 561 | 562 | 'PostgreSQL', |
| 562 | 563 | 'https://www.postgresql.org/about/licence/' |
| 564 | + ], [ | |
| 565 | + 'Puppeteer', | |
| 566 | + '2017 Google Inc', | |
| 567 | + 'Apache', | |
| 568 | + 'https://raw.githubusercontent.com/GoogleChrome/puppeteer/master/LICENSE' | |
| 563 | 569 | ], [ |
| 564 | 570 | 'Pygame', |
| 565 | 571 | 'Pygame Developpers', |
| @@ -637,7 +643,7 @@ credits = [ · 71 unmodified lines | ||
| 637 | 643 | 'http://scikit-image.org/docs/dev/license.html' |
| 638 | 644 | ], [ |
| 639 | 645 | 'scikit-learn', |
| 640 | − '2007-2017 The scikit-learn developers', | |
| 646 | + '2007-2018 The scikit-learn developers', | |
| 641 | 647 | 'BSD', |
| 642 | 648 | 'https://raw.githubusercontent.com/scikit-learn/scikit-learn/master/COPYING' |
| 643 | 649 | ], [ |
| @@ -682,9 +688,9 @@ credits = [ · 38 unmodified lines | ||
| 682 | 688 | 'https://raw.githubusercontent.com/hashicorp/terraform-website/master/LICENSE.md' |
| 683 | 689 | ], [ |
| 684 | 690 | 'Twig', |
| 685 | − '2009-2017 The Twig Team', | |
| 691 | + '2009-2018 The Twig Team', | |
| 686 | 692 | 'BSD', |
| 687 | − 'https://twig.sensiolabs.org/license' | |
| 693 | + 'https://twig.symfony.com/license' | |
| 688 | 694 | ], [ |
| 689 | 695 | 'TypeScript', |
| 690 | 696 | 'Microsoft and other contributors', |
| @@ -692,7 +698,7 @@ credits = [ · 1 unmodified line | ||
| 692 | 698 | 'https://raw.githubusercontent.com/Microsoft/TypeScript-Handbook/master/LICENSE' |
| 693 | 699 | ], [ |
| 694 | 700 | 'Underscore.js', |
| 695 | − '2009-2017 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors', | |
| 701 | + '2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors', | |
| 696 | 702 | 'MIT', |
| 697 | 703 | 'https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE' |
| 698 | 704 | ], [ |
| @@ -25,8 +25,8 @@ app.templates.offlinePage = (docs) -> """ · 24 unmodified lines | ||
| 25 | 25 | <h2 class="_block-heading">Questions & Answers</h2> |
| 26 | 26 | <dl> |
| 27 | 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> | |
| 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. | |
| 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://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 | 30 | <dt>Can I close the tab/browser? |
| 31 | 31 | <dd>#{canICloseTheTab()} |
| 32 | 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 | 43 | canICloseTheTab = -> |
| 44 | − if app.AppCache.isEnabled() | |
| 44 | + if app.ServiceWorker.isEnabled() | |
| 45 | 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 | 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 | 53 | The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """ |
| 49 | 54 | |
| 50 | 55 | app.templates.offlineDoc = (doc, status) -> |
| @@ -8,13 +8,13 @@ app.templates.intro = """ · 7 unmodified lines | ||
| 8 | 8 | <p>Thanks for downloading DevDocs. Here are a few things you should know: |
| 9 | 9 | <ol class="_intro-list"> |
| 10 | 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 | 12 | <li>Run <code>thor docs:list</code> to see all available documentations. |
| 13 | 13 | <li>Run <code>thor docs:download <name></code> to download documentations. |
| 14 | 14 | <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations. |
| 15 | 15 | <li>To be notified about new versions, don't forget to <a href="https://github.com/freeCodeCamp/devdocs/subscription">watch the repository</a> on GitHub. |
| 16 | 16 | <li>The <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a> is the preferred channel for bug reports and |
| 17 | − feature requests. For everything else, use the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>. | |
| 17 | + feature requests. For everything else, use <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a>. | |
| 18 | 18 | <li>Contributions are welcome. See the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/CONTRIBUTING.md">guidelines</a>. |
| 19 | 19 | <li>DevDocs is licensed under the terms of the Mozilla Public License v2.0. For more information, |
| 20 | 20 | see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a> and |
| @@ -39,7 +39,7 @@ app.templates.intro = """ · 18 unmodified lines | ||
| 39 | 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 | 40 | <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>. |
| 41 | 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 | 43 | <li>And if you're new to coding, check out <a href="https://www.freecodecamp.org/">freeCodeCamp's open source curriculum</a>. |
| 44 | 44 | </ol> |
| 45 | 45 | <p>Happy coding! |
| @@ -72,14 +72,3 @@ app.templates.androidWarning = """ · 26 unmodified lines | ||
| 72 | 72 | <p>To install DevDocs on your phone, visit <a href="https://devdocs.io" target="_blank" rel="noopener">devdocs.io</a> in Chrome and select "Add to home screen" in the menu. |
| 73 | 73 | </div> |
| 74 | 74 | """ |
| 75 | − | |
| 76 | −app.templates.httpWarning = """ | |
| 77 | − <div class="_intro"><div class="_intro-message"> | |
| 78 | − <h2 class="_intro-title">Hi there!</h2> | |
| 79 | − <p>DevDocs is migrating to HTTPS. | |
| 80 | − <p>Please update your bookmarks to point to <a href="https://devdocs.io">https://devdocs.io</a>. | |
| 81 | − <p>When using the HTTPS version, your preferences will carry over automatically, but your offline data will be reset. Simply re-download documentation in the <a href="https://devdocs.io/offline">Offline area</a>, and you'll be all set to use DevDocs securely offline. | |
| 82 | − <p>Sorry for the inconvenience. This migration is needed because browsers are removing support for certain DOM APIs that power DevDocs's offline mode over non-secure origins. | |
| 83 | − <p>Thanks for using DevDocs, and happy coding! | |
| 84 | − </div></div> | |
| 85 | −""" | |
| @@ -19,6 +19,10 @@ app.templates.settingsPage = (settings) -> """ · 18 unmodified lines | ||
| 19 | 19 | <input type="checkbox" form="settings" name="autoInstall" value="_auto-install"#{if settings.autoInstall then ' checked' else ''}>Automatically download documentation for offline use |
| 20 | 20 | <small>Only enable this when bandwidth isn't a concern to you.</small> |
| 21 | 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> | |
| 22 | 26 | </div> |
| 23 | 27 | </div> |
| 24 | 28 | |
| @@ -1,28 +1,32 @@ | ||
| 1 | 1 | try { |
| 2 | − if (app.config.env == 'production') { | |
| 3 | − (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | |
| 4 | − (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | |
| 5 | − m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | |
| 6 | − })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | |
| 7 | − ga('create', 'UA-5544833-12', 'devdocs.io'); | |
| 8 | − page.track(function() { | |
| 9 | − ga('send', 'pageview', { | |
| 10 | − page: location.pathname + location.search + location.hash, | |
| 11 | − dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version | |
| 2 | + if (app.config.env === 'production') { | |
| 3 | + if (Cookies.get('analyticsConsent') === '1') { | |
| 4 | + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | |
| 5 | + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | |
| 6 | + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | |
| 7 | + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | |
| 8 | + ga('create', 'UA-5544833-12', 'devdocs.io'); | |
| 9 | + page.track(function() { | |
| 10 | + ga('send', 'pageview', { | |
| 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 | − if (window._gauges) | |
| 17 | − _gauges.push(['track']); | |
| 18 | − else | |
| 19 | − (function() { | |
| 20 | − var _gauges=_gauges||[];!function(){var a=document.createElement("script"); | |
| 21 | − a.type="text/javascript",a.async=!0,a.id="gauges-tracker", | |
| 22 | − a.setAttribute("data-site-id","51c15f82613f5d7819000067"), | |
| 23 | − a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0]; | |
| 24 | − b.parentNode.insertBefore(a,b)}(); | |
| 25 | − })(); | |
| 26 | − }); | |
| 16 | + page.track(function() { | |
| 17 | + if (window._gauges) | |
| 18 | + _gauges.push(['track']); | |
| 19 | + else | |
| 20 | + (function() { | |
| 21 | + var _gauges=_gauges||[];!function(){var a=document.createElement("script"); | |
| 22 | + a.type="text/javascript",a.async=!0,a.id="gauges-tracker", | |
| 23 | + a.setAttribute("data-site-id","51c15f82613f5d7819000067"), | |
| 24 | + a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0]; | |
| 25 | + b.parentNode.insertBefore(a,b)}(); | |
| 26 | + })(); | |
| 27 | + }); | |
| 28 | + } else { | |
| 29 | + resetAnalytics(); | |
| 30 | + } | |
| 27 | 31 | } |
| 28 | 32 | } catch(e) { } |
| @@ -123,7 +123,7 @@ class app.views.EntryPage extends app.View · 122 unmodified lines | ||
| 123 | 123 | @render @tmpl('pageLoadError') |
| 124 | 124 | @resetClass() |
| 125 | 125 | @addClass @constructor.errorClass |
| 126 | − app.appCache?.update() | |
| 126 | + app.serviceWorker?.update() | |
| 127 | 127 | return |
| 128 | 128 | |
| 129 | 129 | cache: -> |
| @@ -57,6 +57,7 @@ class app.views.OfflinePage extends app.View · 56 unmodified lines | ||
| 57 | 57 | doc[action](@onInstallSuccess.bind(@, doc), @onInstallError.bind(@, doc), @onInstallProgress.bind(@, doc)) |
| 58 | 58 | el.parentNode.innerHTML = "#{el.textContent.replace(/e$/, '')}ing…" |
| 59 | 59 | else if action = el.getAttribute('data-action-all') |
| 60 | + return unless action isnt 'uninstall' or window.confirm('Uninstall all docs?') | |
| 60 | 61 | app.db.migrate() |
| 61 | 62 | $.click(el) for el in @findAll("[data-action='#{action}']") |
| 62 | 63 | return |
| @@ -19,10 +19,6 @@ class app.views.RootPage extends app.View · 18 unmodified lines | ||
| 19 | 19 | else |
| 20 | 20 | 'intro' |
| 21 | 21 | |
| 22 | − # temporary | |
| 23 | − if location.host is 'devdocs.io' and location.protocol is 'http:' | |
| 24 | − tmpl = 'httpWarning' | |
| 25 | − | |
| 26 | 22 | @append @tmpl(tmpl) |
| 27 | 23 | return |
| 28 | 24 | |
| @@ -1,7 +1,4 @@ | ||
| 1 | 1 | class app.views.SettingsPage extends app.View |
| 2 | − LAYOUTS = ['_max-width', '_sidebar-hidden', '_native-scrollbars'] | |
| 3 | − SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden' | |
| 4 | − | |
| 5 | 2 | @className: '_static' |
| 6 | 3 | |
| 7 | 4 | @events: |
| @@ -18,32 +15,30 @@ class app.views.SettingsPage extends app.View · 10 unmodified lines | ||
| 18 | 15 | settings.smoothScroll = !app.settings.get('fastScroll') |
| 19 | 16 | settings.arrowScroll = app.settings.get('arrowScroll') |
| 20 | 17 | settings.autoInstall = app.settings.get('autoInstall') |
| 21 | − settings[layout] = app.settings.hasLayout(layout) for layout in LAYOUTS | |
| 18 | + settings.analyticsConsent = app.settings.get('analyticsConsent') | |
| 19 | + settings[layout] = app.settings.hasLayout(layout) for layout in app.settings.LAYOUTS | |
| 22 | 20 | settings |
| 23 | 21 | |
| 24 | 22 | getTitle: -> |
| 25 | 23 | 'Preferences' |
| 26 | 24 | |
| 27 | 25 | toggleDark: (enable) -> |
| 28 | − css = $('link[rel="stylesheet"][data-alt]') | |
| 29 | − alt = css.getAttribute('data-alt') | |
| 30 | − css.setAttribute('data-alt', css.getAttribute('href')) | |
| 31 | − css.setAttribute('href', alt) | |
| 32 | 26 | app.settings.set('dark', !!enable) |
| 33 | − app.appCache?.updateInBackground() | |
| 34 | 27 | return |
| 35 | 28 | |
| 36 | 29 | toggleLayout: (layout, enable) -> |
| 37 | − document.body.classList[if enable then 'add' else 'remove'](layout) unless layout is SIDEBAR_HIDDEN_LAYOUT | |
| 38 | − document.body.classList[if $.overlayScrollbarsEnabled() then 'add' else 'remove']('_overlay-scrollbars') | |
| 39 | 30 | app.settings.setLayout(layout, enable) |
| 40 | − app.appCache?.updateInBackground() | |
| 41 | 31 | return |
| 42 | 32 | |
| 43 | 33 | toggleSmoothScroll: (enable) -> |
| 44 | 34 | app.settings.set('fastScroll', !enable) |
| 45 | 35 | return |
| 46 | 36 | |
| 37 | + toggleAnalyticsConsent: (enable) -> | |
| 38 | + app.settings.set('analyticsConsent', if enable then '1' else '0') | |
| 39 | + resetAnalytics() unless enable | |
| 40 | + return | |
| 41 | + | |
| 47 | 42 | toggle: (name, enable) -> |
| 48 | 43 | app.settings.set(name, enable) |
| 49 | 44 | return |
| @@ -87,6 +82,8 @@ class app.views.SettingsPage extends app.View · 37 unmodified lines | ||
| 87 | 82 | @toggleSmoothScroll input.checked |
| 88 | 83 | when 'import' |
| 89 | 84 | @import input.files[0], input |
| 85 | + when 'analyticsConsent' | |
| 86 | + @toggleAnalyticsConsent input.checked | |
| 90 | 87 | else |
| 91 | 88 | @toggle input.name, input.checked |
| 92 | 89 | return |
| @@ -75,9 +75,11 @@ class app.views.Document extends app.View · 74 unmodified lines | ||
| 75 | 75 | return unless target.hasAttribute('data-behavior') |
| 76 | 76 | $.stopEvent(event) |
| 77 | 77 | switch target.getAttribute('data-behavior') |
| 78 | − when 'back' then history.back() | |
| 79 | − when 'reload' then window.location.reload() | |
| 80 | − when 'reboot' then window.location = '/' | |
| 81 | − when 'hard-reload' then app.reload() | |
| 82 | − when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?') | |
| 78 | + when 'back' then history.back() | |
| 79 | + when 'reload' then window.location.reload() | |
| 80 | + when 'reboot' then app.reboot() | |
| 81 | + when 'hard-reload' then app.reload() | |
| 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') && app.reboot() | |
| 84 | + when 'decline-analytics' then Cookies.set('analyticsConsent', '0') && app.reboot() | |
| 83 | 85 | return |
| @@ -11,9 +11,6 @@ class app.views.Resizer extends app.View · 10 unmodified lines | ||
| 11 | 11 | init: -> |
| 12 | 12 | @el.setAttribute('draggable', 'true') |
| 13 | 13 | @appendTo $('._app') |
| 14 | − | |
| 15 | − @style = $('style[data-resizer]') | |
| 16 | − @size = @style.getAttribute('data-size') | |
| 17 | 14 | return |
| 18 | 15 | |
| 19 | 16 | MIN = 260 |
| @@ -24,15 +21,11 @@ class app.views.Resizer extends app.View · 4 unmodified lines | ||
| 24 | 21 | return unless value > 0 |
| 25 | 22 | value = Math.min(Math.max(Math.round(value), MIN), MAX) |
| 26 | 23 | newSize = "#{value}px" |
| 27 | − @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize) | |
| 28 | − @size = newSize | |
| 29 | − if save | |
| 30 | − app.settings.setSize(value) | |
| 31 | − app.appCache?.updateInBackground() | |
| 24 | + document.documentElement.style.setProperty('--sidebarWidth', newSize) | |
| 25 | + app.settings.setSize(value) if save | |
| 32 | 26 | return |
| 33 | 27 | |
| 34 | 28 | onDragStart: (event) => |
| 35 | − @style.removeAttribute('disabled') | |
| 36 | 29 | event.dataTransfer.effectAllowed = 'link' |
| 37 | 30 | event.dataTransfer.setData('Text', '') |
| 38 | 31 | $.on(window, 'dragover', @onDrag) |
| @@ -25,7 +25,6 @@ class app.views.Settings extends app.View · 24 unmodified lines | ||
| 25 | 25 | if super |
| 26 | 26 | @render() |
| 27 | 27 | document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT) |
| 28 | − app.appCache?.on 'progress', @onAppCacheProgress | |
| 29 | 28 | return |
| 30 | 29 | |
| 31 | 30 | deactivate: -> |
| @@ -33,7 +32,6 @@ class app.views.Settings extends app.View · 1 unmodified line | ||
| 33 | 32 | @resetClass() |
| 34 | 33 | @docPicker.detach() |
| 35 | 34 | document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT) |
| 36 | − app.appCache?.off 'progress', @onAppCacheProgress | |
| 37 | 35 | return |
| 38 | 36 | |
| 39 | 37 | render: -> |
| @@ -52,7 +50,7 @@ class app.views.Settings extends app.View · 12 unmodified lines | ||
| 52 | 50 | docs = @docPicker.getSelectedDocs() |
| 53 | 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 | 54 | disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1) |
| 57 | 55 | disabledDocs.uninstall -> |
| 58 | 56 | app.db.migrate() |
| @@ -83,9 +81,3 @@ class app.views.Settings extends app.View · 24 unmodified lines | ||
| 83 | 81 | $.stopEvent(event) |
| 84 | 82 | app.router.show '/' |
| 85 | 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 | 30 | .on 'results', @onResults |
| 31 | 31 | .on 'end', @onEnd |
| 32 | 32 | |
| 33 | + @scope | |
| 34 | + .on 'change', @onScopeChange | |
| 35 | + | |
| 33 | 36 | app.on 'ready', @onReady |
| 34 | 37 | $.on window, 'hashchange', @searchUrl |
| 35 | 38 | $.on window, 'focus', @onWindowFocus |
| @@ -138,6 +141,11 @@ class app.views.Search extends app.View · 102 unmodified lines | ||
| 138 | 141 | $.stopEvent(event) |
| 139 | 142 | return |
| 140 | 143 | |
| 144 | + onScopeChange: => | |
| 145 | + @value = '' | |
| 146 | + @onInput() | |
| 147 | + return | |
| 148 | + | |
| 141 | 149 | afterRoute: (name, context) => |
| 142 | 150 | return if app.shortcuts.eventInProgress?.name is 'escape' |
| 143 | 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 | 6 | tag: '._search-tag' |
| 7 | 7 | |
| 8 | 8 | @events: |
| 9 | + click: 'onClick' | |
| 9 | 10 | keydown: 'onKeydown' |
| 11 | + textInput: 'onTextInput' | |
| 10 | 12 | |
| 11 | 13 | @routes: |
| 12 | 14 | after: 'afterRoute' |
| @@ -87,17 +89,33 @@ class app.views.SearchScope extends app.View · 74 unmodified lines | ||
| 87 | 89 | @trigger 'change', null, previousDoc |
| 88 | 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 | 103 | onKeydown: (event) => |
| 91 | 104 | if event.which is 8 # backspace |
| 92 | − if @doc and not @input.value | |
| 93 | − $.stopEvent(event) | |
| 105 | + if @doc and @input.selectionEnd is 0 | |
| 94 | 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 | 109 | return if event.ctrlKey or event.metaKey or event.altKey or event.shiftKey |
| 97 | 110 | if event.which is 9 or # tab |
| 98 | 111 | (event.which is 32 and app.isMobile()) # space |
| 99 | − @search @input.value[0...@input.selectionStart] | |
| 100 | − $.stopEvent(event) if @doc | |
| 112 | + @doScopeSearch(event) | |
| 113 | + return | |
| 114 | + | |
| 115 | + onTextInput: (event) => | |
| 116 | + return unless $.isChromeForAndroid() | |
| 117 | + if not @doc and @input.value and event.data == ' ' | |
| 118 | + @doScopeSearch(event) | |
| 101 | 119 | return |
| 102 | 120 | |
| 103 | 121 | extractHashValue: -> |
| @@ -28,7 +28,7 @@ class app.views.Sidebar extends app.View · 27 unmodified lines | ||
| 28 | 28 | |
| 29 | 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 | 32 | $.on document.documentElement, 'mouseenter', => @resetDisplay(forceNoHover: false) |
| 33 | 33 | return |
| 34 | 34 | |