diff --git a/.editorconfig b/.editorconfig index 73db316c04..03c17bf8d5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,3 +6,6 @@ indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 489067b936..0c53b8e910 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -24,12 +24,12 @@ Want to contribute? Great. Please review the following guidelines carefully and ## Requesting new features 1. Search for similar feature requests; someone may have already requested it. -2. Make sure your feature fits DevDocs's [vision](https://github.com/freeCodeCamp/devdocs/blob/master/README.md#vision). +2. Make sure your feature fits DevDocs's [vision](../README.md#vision). 3. Provide a clear and detailed explanation of the feature and why it's important to add it. ## Requesting new documentations -Please don't open issues to request new documentations. +Please don't open issues to request new documentations. Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) where everyone can vote. ## Contributing code and features @@ -55,13 +55,12 @@ In addition to the [guidelines for contributing code](#contributing-code-and-fea * Remove as much content and HTML markup as possible, particularly content not associated with any entry (e.g. introduction, changelog, etc.). * Names must be as short as possible and unique across the documentation. * The number of types (categories) should ideally be less than 100. -* Don't modify the icon sprite. I'll do it after your pull request is merged. ## Updating existing documentations -Please don't submit a pull request updating the version number of a documentation, unless a change is required in the scraper and you've verified that it works. +Please don't submit a pull request updating only the version number and/or the attribution of a documentation. Do submit a pull request if a bigger change is required in the scraper and you've verified that it works. -To ask that an existing documentation be updated, please use the [Trello board](https://trello.com/c/2B0hmW7M/52-request-updates-here). +To ask that an existing documentation be updated, first check the latest [documentation versions report](https://github.com/freeCodeCamp/devdocs/issues?utf8=%E2%9C%93&q=Documentation+versions+report+is%3Aissue+author%3Adevdocs-bot+sort%3Acreated-desc). Only create an issue if the documentation has been wrongly marked as up-to-date. ## Coding conventions diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 807f88e02b..0fb624d406 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,6 +2,6 @@ Please read the contributing guidelines before opening an issue: https://github.com/freeCodeCamp/devdocs/blob/master/.github/CONTRIBUTING.md - To request a new documentation, or an update of an existing documentation, go here: + Go here to request a new documentation: https://trello.com/b/6BmTulfx/devdocs-documentation --> diff --git a/.gitignore b/.gitignore index 1060fcf0c7..f89ecb61be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .bundle +log tmp public/assets public/fonts diff --git a/.ruby-version b/.ruby-version index 914ec96711..ec1cf33c3f 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.0 \ No newline at end of file +2.6.3 diff --git a/.slugignore b/.slugignore index 6a7070f5ec..9daeafb986 100644 --- a/.slugignore +++ b/.slugignore @@ -1,2 +1 @@ -public/icons -test \ No newline at end of file +test diff --git a/.travis.yml b/.travis.yml index d5e5a6aa34..6c59f0be8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,26 @@ language: ruby +addons: + apt: + packages: + - libcurl4-openssl-dev + cache: bundler -before_script: +before_install: + - "echo 'gem: --no-document' > ~/.gemrc" - gem update --system - gem install bundler + +script: + - if [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then bundle exec rake; fi + - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then bundle exec thor updates:check --github-token $GH_TOKEN --upload; fi + +deploy: + provider: heroku + app: devdocs + on: + branch: master + condition: $TRAVIS_EVENT_TYPE != cron + api_key: + secure: "4p1klvWJZSOImzFcKOduILjP93hlOlAhceWlYMKS4tU+TCFE8qTBzdKdFPSCsCgjB+YR9pBss+L0lJpVVMjSwFHXqpKe6EeUSltO2k7DFHfW7kXLUM/L0AfqXz+YXk76XUyZMhvOEbldPfaMaj10e8vgDOQCSHABDyK/4CU+hnI=" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..26f8ab09e8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,2 @@ + +> Our Code of Conduct is available here: diff --git a/Dockerfile b/Dockerfile index 77443edd44..b033427a90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,12 @@ -FROM ruby:2.6.0 +FROM ruby:2.6.3 ENV LANG=C.UTF-8 +ENV ENABLE_SERVICE_WORKER=true WORKDIR /devdocs RUN apt-get update && \ - apt-get -y install git nodejs && \ + apt-get -y install git nodejs libcurl4 && \ gem install bundler && \ rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile-alpine b/Dockerfile-alpine index 5bd25afee7..33a06b0e82 100644 --- a/Dockerfile-alpine +++ b/Dockerfile-alpine @@ -1,12 +1,13 @@ -FROM ruby:2.6.0-alpine +FROM ruby:2.6.3-alpine ENV LANG=C.UTF-8 +ENV ENABLE_SERVICE_WORKER=true WORKDIR /devdocs COPY . /devdocs -RUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev && \ +RUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev libcurl && \ gem install bundler && \ bundle install --system --without test && \ thor docs:download --all && \ diff --git a/Gemfile b/Gemfile index 31b57064fc..3c9ce68b06 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' -ruby '2.6.0' +ruby '2.6.3' gem 'rake' gem 'thor' @@ -22,6 +22,8 @@ group :app do gem 'browser' gem 'sass' gem 'coffee-script' + gem 'chunky_png' + gem 'sprockets-sass' end group :production do @@ -40,6 +42,7 @@ group :docs do gem 'unix_utils', require: false gem 'tty-pager', require: false gem 'net-sftp', '>= 2.1.3.rc2', require: false + gem 'terminal-table', require: false end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index d968a27b9a..d1abb66bcd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,49 +1,50 @@ GEM remote: https://rubygems.org/ specs: - activesupport (5.2.2) + activesupport (5.2.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - backports (3.11.4) - better_errors (2.5.0) + backports (3.15.0) + better_errors (2.5.1) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) - browser (2.5.3) + browser (2.6.1) + chunky_png (1.3.11) coderay (1.1.2) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.5) daemons (1.3.1) erubi (1.8.0) ethon (0.12.0) ffi (>= 1.3.0) eventmachine (1.2.7) execjs (2.7.0) - exifr (1.3.5) - ffi (1.10.0) - fspath (3.1.0) - highline (2.0.0) - html-pipeline (2.10.0) + exifr (1.3.6) + ffi (1.11.1) + fspath (3.1.2) + highline (2.0.2) + html-pipeline (2.11.1) activesupport (>= 2) nokogiri (>= 1.4) - i18n (1.5.2) + i18n (1.6.0) concurrent-ruby (~> 1.0) - image_optim (0.26.3) + image_optim (0.26.5) exifr (~> 1.2, >= 1.2.2) fspath (~> 3.0) image_size (>= 1.5, < 3) in_threads (~> 1.3) progress (~> 3.0, >= 3.0.1) - image_optim_pack (0.5.1.20190105) + image_optim_pack (0.5.6) fspath (>= 2.1, < 4) image_optim (~> 0.19) - image_size (2.0.0) - in_threads (1.5.1) + image_size (2.0.2) + in_threads (1.5.3) method_source (0.9.2) mini_portile2 (2.4.0) minitest (5.11.3) @@ -51,19 +52,19 @@ GEM mustermann (1.0.3) net-sftp (3.0.0.beta1) net-ssh (>= 5.0.0, < 6.0.0) - net-ssh (5.1.0) - newrelic_rpm (5.7.0.350) - nokogiri (1.10.1) + net-ssh (5.2.0) + newrelic_rpm (6.5.0.357) + nokogiri (1.10.3) mini_portile2 (~> 2.4.0) options (2.3.2) - progress (3.5.0) + progress (3.5.2) progress_bar (1.3.0) highline (>= 1.6, < 3) options (~> 2.3.0) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) - rack (2.0.6) + rack (2.0.7) rack-protection (2.0.5) rack rack-ssl-enforcer (0.2.9) @@ -74,7 +75,7 @@ GEM rb-inotify (0.10.0) ffi (~> 1.0) rr (1.2.1) - sass (3.7.3) + sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -96,11 +97,15 @@ GEM rack (> 1, < 3) sprockets-helpers (1.2.1) sprockets (>= 2.2) - strings (0.1.4) - strings-ansi (~> 0.1.0) - unicode-display_width (~> 1.4.0) - unicode_utils (~> 1.4.0) + sprockets-sass (2.0.0.beta2) + sprockets (>= 2.0, < 4.0) + strings (0.1.5) + strings-ansi (~> 0.1) + unicode-display_width (~> 1.5) + unicode_utils (~> 1.4) strings-ansi (0.1.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) thin (1.7.2) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) @@ -108,19 +113,19 @@ GEM thor (0.20.3) thread_safe (0.3.6) tilt (2.0.9) - tty-pager (0.12.0) + tty-pager (0.12.1) strings (~> 0.1.4) tty-screen (~> 0.6) tty-which (~> 0.4) - tty-screen (0.6.5) - tty-which (0.4.0) + tty-screen (0.7.0) + tty-which (0.4.1) typhoeus (1.3.1) ethon (>= 0.9.0) tzinfo (1.2.5) thread_safe (~> 0.1) uglifier (4.1.20) execjs (>= 0.3.0, < 3) - unicode-display_width (1.4.1) + unicode-display_width (1.6.0) unicode_utils (1.4.0) unix_utils (0.0.15) yajl-ruby (1.4.1) @@ -132,6 +137,7 @@ DEPENDENCIES activesupport (~> 5.2) better_errors browser + chunky_png coffee-script erubi html-pipeline @@ -153,6 +159,8 @@ DEPENDENCIES sinatra-contrib sprockets sprockets-helpers + sprockets-sass + terminal-table thin thor tty-pager @@ -162,7 +170,7 @@ DEPENDENCIES yajl-ruby RUBY VERSION - ruby 2.6.0p0 + ruby 2.6.3p62 BUNDLED WITH - 1.17.2 + 2.0.2 diff --git a/README.md b/README.md index 1020d41188..630142d408 100644 --- a/README.md +++ b/README.md @@ -59,14 +59,14 @@ The web app is all client-side JavaScript, written in [CoffeeScript](http://coff Many of the code's design decisions were driven by the fact that the app uses XHR to load content directly into the main frame. This includes stripping the original documents of most of their HTML markup (e.g. scripts and stylesheets) to avoid polluting the main frame, and prefixing all CSS class names with an underscore to prevent conflicts. -Another driving factor is performance and the fact that everything happens in the browser. `applicationCache` (which comes with its own set of constraints) and `localStorage` are used to speed up the boot time, while memory consumption is kept in check by allowing the user to pick his/her own set of documentations. The search algorithm is kept simple because it needs to be fast even searching through 100,000 strings. +Another driving factor is performance and the fact that everything happens in the browser. A service worker (which comes with its own set of constraints) and `localStorage` are used to speed up the boot time, while memory consumption is kept in check by allowing the user to pick his/her own set of documentations. The search algorithm is kept simple because it needs to be fast even searching through 100,000 strings. DevDocs being a developer tool, the browser requirements are high: * Recent versions of Firefox, Chrome, or Opera -* Safari 9.1+ -* Edge 16+ -* iOS 10+ +* Safari 11.1+ +* Edge 17+ +* iOS 11.3+ This allows the code to take advantage of the latest DOM and HTML5 APIs and make developing DevDocs a lot more fun! @@ -89,7 +89,7 @@ These modifications are applied via a set of filters using the [HTML::Pipeline]( The end result is a set of normalized HTML partials and two JSON files (index + offline data). Because the index files are loaded separately by the [app](#app) following the user's preferences, the scraper also creates a JSON manifest file containing information about the documentations currently available on the system (such as their name, version, update date, etc.). -More information about [scrapers](https://github.com/freeCodeCamp/devdocs/blob/master/docs/Scraper-Reference.md) and [filters](https://github.com/freeCodeCamp/devdocs/blob/master/docs/Filter-Reference.md) is available in the `docs` folder. +More information about [scrapers](./docs/scraper-reference.md) and [filters](./docs/filter-reference.md) is available in the `docs` folder. ## Available Commands @@ -129,7 +129,7 @@ If multiple versions of Ruby are installed on your system, commands must be run ## Contributing -Contributions are welcome. Please read the [contributing guidelines](https://github.com/freeCodeCamp/devdocs/blob/master/.github/CONTRIBUTING.md). +Contributions are welcome. Please read the [contributing guidelines](./.github/CONTRIBUTING.md). ## Documentation @@ -144,7 +144,7 @@ Contributions are welcome. Please read the [contributing guidelines](https://git * [Ubuntu Touch app](https://uappexplorer.com/app/devdocsunofficial.berkes) * [Sublime Text plugin](https://sublime.wbond.net/packages/DevDocs) * [Atom plugin](https://atom.io/packages/devdocs) -* [Brackets extension](https://github.com/gruehle/dev-docs-viewer) +* [Brackets extension](https://github.com/gruehle/dev-docs-viewer) * [Fluid](http://fluidapp.com) for turning DevDocs into a real OS X app * [GTK shell / Vim integration](https://github.com/naquad/devdocs-shell) * [Emacs lookup](https://github.com/skeeto/devdocs-lookup) @@ -156,13 +156,13 @@ Contributions are welcome. Please read the [contributing guidelines](https://git * [Doc Browser](https://github.com/qwfy/doc-browser) is a native Linux app that supports DevDocs docsets * [GNOME Application](https://github.com/hardpixel/devdocs-desktop) GTK3 application with search integrated in headerbar * [macOS Application](https://github.com/dteoh/devdocs-macos) -* [Android Application](https://github.com/Merith-TK/devdocs_webapp_kotlin) is a fully working, advanced WebView with AppCache enabled +* [Android Application](https://github.com/Merith-TK/devdocs_webapp_kotlin) is a fully working, advanced WebView ## Copyright / License Copyright 2013-2019 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors) -This software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT) and [LICENSE](https://github.com/freeCodeCamp/devdocs/blob/master/LICENSE) files. +This software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](./COPYRIGHT) and [LICENSE](./LICENSE) files. Please do not use the name DevDocs to endorse or promote products derived from this software without the maintainers' permission, except as may be necessary to comply with the notice/attribution requirements. diff --git a/assets/images/.gitignore b/assets/images/.gitignore new file mode 100644 index 0000000000..4a72ec741e --- /dev/null +++ b/assets/images/.gitignore @@ -0,0 +1 @@ +sprites/**/* diff --git a/assets/images/docs-1.png b/assets/images/docs-1.png deleted file mode 100644 index c5bfb6b8d9..0000000000 Binary files a/assets/images/docs-1.png and /dev/null differ diff --git a/assets/images/docs-1@2x.png b/assets/images/docs-1@2x.png deleted file mode 100644 index 1f81ecd969..0000000000 Binary files a/assets/images/docs-1@2x.png and /dev/null differ diff --git a/assets/images/docs-2.png b/assets/images/docs-2.png deleted file mode 100644 index bd9d44a8d4..0000000000 Binary files a/assets/images/docs-2.png and /dev/null differ diff --git a/assets/images/docs-2@2x.png b/assets/images/docs-2@2x.png deleted file mode 100644 index 512ea7a5d0..0000000000 Binary files a/assets/images/docs-2@2x.png and /dev/null differ diff --git a/assets/javascripts/app/app.coffee b/assets/javascripts/app/app.coffee index ab67a92a97..b55e552c8b 100644 --- a/assets/javascripts/app/app.coffee +++ b/assets/javascripts/app/app.coffee @@ -13,10 +13,12 @@ @el = $('._app') @localStorage = new LocalStorageStore - @appCache = new app.AppCache if app.AppCache.isEnabled() + @serviceWorker = new app.ServiceWorker if app.ServiceWorker.isEnabled() @settings = new app.Settings @db = new app.DB() + @settings.initLayout() + @docs = new app.collections.Docs @disabledDocs = new app.collections.Docs @entries = new app.collections.Entries @@ -76,7 +78,7 @@ .install() @previousErrorHandler = onerror window.onerror = @onWindowError.bind(@) - CookieStore.onBlocked = @onCookieBlocked + CookiesStore.onBlocked = @onCookieBlocked return bootOne: -> @@ -138,7 +140,10 @@ @docs.sort() @initDoc(doc) @saveDocs() - _onSuccess() + if app.settings.get('autoInstall') + doc.install(_onSuccess, onError) + else + _onSuccess() return doc.load onSuccess, onError, writeCache: true @@ -147,7 +152,7 @@ saveDocs: -> @settings.setDocs(doc.slug for doc in @docs.all()) @db.migrate() - @appCache?.updateInBackground() + @serviceWorker?.updateInBackground() welcomeBack: -> visitCount = @settings.get('count') @@ -167,14 +172,14 @@ reload: -> @docs.clearCache() @disabledDocs.clearCache() - if @appCache then @appCache.reload() else @reboot() + if @serviceWorker then @serviceWorker.reload() else @reboot() return reset: -> @localStorage.reset() @settings.reset() @db?.reset() - @appCache?.update() + @serviceWorker?.update() window.location = '/' return @@ -193,9 +198,9 @@ return indexHost: -> - # Can't load the index files from the host/CDN when applicationCache is + # Can't load the index files from the host/CDN when service worker is # enabled because it doesn't support caching URLs that use CORS. - @config[if @appCache and @settings.hasDocs() then 'index_path' else 'docs_origin'] + @config[if @serviceWorker and @settings.hasDocs() then 'index_path' else 'docs_origin'] onBootError: (args...) -> @trigger 'bootError' @@ -252,7 +257,7 @@ matchMedia: !!window.matchMedia insertAdjacentHTML: !!document.body.insertAdjacentHTML defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false - cssVariables: CSS.supports and CSS.supports('(--t: 0)') + cssVariables: !!CSS?.supports?('(--t: 0)') for key, value of features when not value Raven.captureMessage "unsupported/#{key}", level: 'info' diff --git a/assets/javascripts/app/appcache.coffee b/assets/javascripts/app/appcache.coffee deleted file mode 100644 index 235cae0211..0000000000 --- a/assets/javascripts/app/appcache.coffee +++ /dev/null @@ -1,42 +0,0 @@ -class app.AppCache - $.extend @prototype, Events - - @isEnabled: -> - try - applicationCache and applicationCache.status isnt applicationCache.UNCACHED - catch - - constructor: -> - @cache = applicationCache - @notifyUpdate = true - @onUpdateReady() if @cache.status is @cache.UPDATEREADY - - $.on @cache, 'progress', @onProgress - $.on @cache, 'updateready', @onUpdateReady - - update: -> - @notifyUpdate = true - @notifyProgress = true - try @cache.update() catch - return - - updateInBackground: -> - @notifyUpdate = false - @notifyProgress = false - try @cache.update() catch - return - - reload: -> - $.on @cache, 'updateready noupdate error', -> app.reboot() - @notifyUpdate = false - @notifyProgress = true - try @cache.update() catch - return - - onProgress: (event) => - @trigger 'progress', event if @notifyProgress - return - - onUpdateReady: => - @trigger 'updateready' if @notifyUpdate - return diff --git a/assets/javascripts/app/config.coffee.erb b/assets/javascripts/app/config.coffee.erb index ec26b69737..a822f7e223 100644 --- a/assets/javascripts/app/config.coffee.erb +++ b/assets/javascripts/app/config.coffee.erb @@ -13,3 +13,6 @@ app.config = version: <%= Time.now.to_i %> release: <%= Time.now.utc.httpdate.to_json %> mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css' + favicon_spritesheet: '<%= image_path('sprites/docs.png') %>' + service_worker_path: '/service-worker.js' + service_worker_enabled: <%= App.environment == :production || ENV['ENABLE_SERVICE_WORKER'] == 'true' %> diff --git a/assets/javascripts/app/serviceworker.coffee b/assets/javascripts/app/serviceworker.coffee new file mode 100644 index 0000000000..4023556658 --- /dev/null +++ b/assets/javascripts/app/serviceworker.coffee @@ -0,0 +1,49 @@ +class app.ServiceWorker + $.extend @prototype, Events + + @isEnabled: -> + !!navigator.serviceWorker and app.config.service_worker_enabled + + constructor: -> + @registration = null + @notifyUpdate = true + + navigator.serviceWorker.register(app.config.service_worker_path, {scope: '/'}) + .then( + (registration) => @updateRegistration(registration), + (error) -> console.error('Could not register service worker:', error) + ) + + update: -> + return unless @registration + @notifyUpdate = true + return @registration.update().catch(->) + + updateInBackground: -> + return unless @registration + @notifyUpdate = false + return @registration.update().catch(->) + + reload: -> + return @updateInBackground().then(() -> app.reboot()) + + updateRegistration: (registration) -> + @registration = registration + $.on @registration, 'updatefound', @onUpdateFound + return + + onUpdateFound: => + $.off @installingRegistration, 'statechange', @onStateChange() if @installingRegistration + @installingRegistration = @registration.installing + $.on @installingRegistration, 'statechange', @onStateChange + return + + onStateChange: => + if @installingRegistration and @installingRegistration.state == 'installed' and navigator.serviceWorker.controller + @installingRegistration = null + @onUpdateReady() + return + + onUpdateReady: -> + @trigger 'updateready' if @notifyUpdate + return diff --git a/assets/javascripts/app/settings.coffee b/assets/javascripts/app/settings.coffee index 8d309c4174..350a08c0ae 100644 --- a/assets/javascripts/app/settings.coffee +++ b/assets/javascripts/app/settings.coffee @@ -5,11 +5,13 @@ class app.Settings 'manualUpdate' 'fastScroll' 'arrowScroll' + 'analyticsConsent' 'docs' 'dark' 'layout' 'size' 'tips' + 'autoInstall' ] INTERNAL_KEYS = [ @@ -19,6 +21,8 @@ class app.Settings 'news' ] + LAYOUTS: ['_max-width', '_sidebar-hidden', '_native-scrollbars'] + @defaults: count: 0 hideDisabled: false @@ -26,9 +30,10 @@ class app.Settings news: 0 manualUpdate: false schema: 1 + analyticsConsent: false constructor: -> - @store = new CookieStore + @store = new CookiesStore @cache = {} get: (key) -> @@ -38,6 +43,7 @@ class app.Settings set: (key, value) -> @store.set(key, value) delete @cache[key] + @toggleDark(value) if key == 'dark' return del: (key) -> @@ -63,6 +69,8 @@ class app.Settings return setLayout: (name, enable) -> + @toggleLayout(name, enable) + layout = (@store.get('layout') || '').split(' ') $.arrayDelete(layout, '') @@ -104,3 +112,28 @@ class app.Settings @store.reset() @cache = {} return + + initLayout: -> + @toggleDark(@get('dark') is 1) + @toggleLayout(layout, @hasLayout(layout)) for layout in @LAYOUTS + @initSidebarWidth() + return + + toggleDark: (enable) -> + classList = document.documentElement.classList + classList.toggle('_theme-default', !enable) + classList.toggle('_theme-dark', enable) + color = getComputedStyle(document.documentElement).getPropertyValue('--headerBackground').trim() + $('meta[name=theme-color]').setAttribute('content', color) + return + + toggleLayout: (layout, enable) -> + classList = document.body.classList + classList.toggle(layout, enable) unless layout is '_sidebar-hidden' + classList.toggle('_overlay-scrollbars', $.overlayScrollbarsEnabled()) + return + + initSidebarWidth: -> + size = @get('size') + document.documentElement.style.setProperty('--sidebarWidth', size + 'px') if size + return diff --git a/assets/javascripts/app/update_checker.coffee b/assets/javascripts/app/update_checker.coffee index 5630b4889c..3558d6bc4e 100644 --- a/assets/javascripts/app/update_checker.coffee +++ b/assets/javascripts/app/update_checker.coffee @@ -3,13 +3,13 @@ class app.UpdateChecker @lastCheck = Date.now() $.on window, 'focus', @onFocus - app.appCache.on 'updateready', @onUpdateReady if app.appCache + app.serviceWorker?.on 'updateready', @onUpdateReady setTimeout @checkDocs, 0 check: -> - if app.appCache - app.appCache.update() + if app.serviceWorker + app.serviceWorker.update() else ajax url: $('script[src*="application"]').getAttribute('src') diff --git a/assets/javascripts/lib/cookie_store.coffee b/assets/javascripts/lib/cookies_store.coffee similarity index 76% rename from assets/javascripts/lib/cookie_store.coffee rename to assets/javascripts/lib/cookies_store.coffee index 3b3405717b..eaf1bd4f7c 100644 --- a/assets/javascripts/lib/cookie_store.coffee +++ b/assets/javascripts/lib/cookies_store.coffee @@ -1,4 +1,8 @@ -class @CookieStore +class @CookiesStore + # Intentionally called CookiesStore instead of CookieStore + # Calling it CookieStore causes issues when the Experimental Web Platform features flag is enabled in Chrome + # Related issue: https://github.com/freeCodeCamp/devdocs/issues/932 + INT = /^\d+$/ @onBlocked: -> diff --git a/assets/javascripts/lib/favicon.coffee b/assets/javascripts/lib/favicon.coffee new file mode 100644 index 0000000000..2f2d9f93c5 --- /dev/null +++ b/assets/javascripts/lib/favicon.coffee @@ -0,0 +1,64 @@ +defaultUrl = null +currentSlug = null + +imageCache = {} +urlCache = {} + +withImage = (url, action) -> + if imageCache[url] + action(imageCache[url]) + else + img = new Image() + img.crossOrigin = 'anonymous' + img.src = url + img.onload = () => + imageCache[url] = img + action(img) + +@setFaviconForDoc = (doc) -> + return if currentSlug == doc.slug + + favicon = $('link[rel="icon"]') + + if defaultUrl == null + defaultUrl = favicon.href + + if urlCache[doc.slug] + favicon.href = urlCache[doc.slug] + currentSlug = doc.slug + return + + styles = window.getComputedStyle($("._icon-#{doc.slug.split('~')[0]}"), ':before') + + bgUrl = app.config.favicon_spritesheet + sourceSize = 16 + sourceX = Math.abs(parseInt(styles['background-position-x'].slice(0, -2))) + sourceY = Math.abs(parseInt(styles['background-position-y'].slice(0, -2))) + + withImage(bgUrl, (docImg) -> + withImage(defaultUrl, (defaultImg) -> + size = defaultImg.width + + canvas = document.createElement('canvas') + ctx = canvas.getContext('2d') + + canvas.width = size + canvas.height = size + ctx.drawImage(defaultImg, 0, 0) + + docIconPercentage = 65 + destinationCoords = size / 100 * (100 - docIconPercentage) + destinationSize = size / 100 * docIconPercentage + ctx.drawImage(docImg, sourceX, sourceY, sourceSize, sourceSize, destinationCoords, destinationCoords, destinationSize, destinationSize) + + urlCache[doc.slug] = canvas.toDataURL() + favicon.href = urlCache[doc.slug] + + currentSlug = doc.slug + ) + ) + +@resetFavicon = () -> + if defaultUrl != null and currentSlug != null + $('link[rel="icon"]').href = defaultUrl + currentSlug = null diff --git a/assets/javascripts/lib/page.coffee b/assets/javascripts/lib/page.coffee index 5d3f6c884c..7a5329f6a8 100644 --- a/assets/javascripts/lib/page.coffee +++ b/assets/javascripts/lib/page.coffee @@ -199,5 +199,23 @@ page.track = (fn) -> return track = -> - tracker.call() for tracker in trackers + return unless app.config.env == 'production' + + consentGiven = Cookies.get('analyticsConsent') + consentAsked = Cookies.get('analyticsConsentAsked') + + if consentGiven == '1' + tracker.call() for tracker in trackers + else if consentGiven == undefined and consentAsked == undefined + # Only ask for consent once per browser session + Cookies.set('analyticsConsentAsked', '1') + + new app.views.Notif 'AnalyticsConsent', autoHide: null + return + +@resetAnalytics = -> + for cookie in document.cookie.split(/;\s?/) + name = cookie.split('=')[0] + if name[0] == '_' && name[1] != '_' + Cookies.expire(name) return diff --git a/assets/javascripts/lib/util.coffee b/assets/javascripts/lib/util.coffee index 058106fdff..001b13dee7 100644 --- a/assets/javascripts/lib/util.coffee +++ b/assets/javascripts/lib/util.coffee @@ -352,6 +352,10 @@ isIE = null $.isIE = -> isIE ?= navigator.userAgent?.indexOf('MSIE') >= 0 || navigator.userAgent?.indexOf('rv:11.0') >= 0 +isChromeForAndroid = null +$.isChromeForAndroid = -> + isChromeForAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 && /Chrome\/([.0-9])+ Mobile/.test(navigator.userAgent) + isAndroid = null $.isAndroid = -> isAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 diff --git a/assets/javascripts/models/doc.coffee b/assets/javascripts/models/doc.coffee index 2d5f7e4ac7..c51e13fae4 100644 --- a/assets/javascripts/models/doc.coffee +++ b/assets/javascripts/models/doc.coffee @@ -142,4 +142,6 @@ class app.models.Doc extends app.Model return isOutdated: (status) -> - status and status.installed and @mtime isnt status.mtime + return false if not status + isInstalled = status.installed or app.settings.get('autoInstall') + isInstalled and @mtime isnt status.mtime diff --git a/assets/javascripts/news.json b/assets/javascripts/news.json index 32da738ece..0266dea396 100644 --- a/assets/javascripts/news.json +++ b/assets/javascripts/news.json @@ -1,4 +1,12 @@ [ + [ + "2019-07-21", + "Fixed several bugs, added an option to automatically download documentation and more." + ], + [ + "2019-07-19", + "Replaced the AppCache with a Service Worker (which makes DevDocs an installable PWA) and fixed layout preferences on Firefox." + ], [ "2018-09-23", "New documentations: Puppeteer and Handlebars.js" diff --git a/assets/javascripts/templates/error_tmpl.coffee b/assets/javascripts/templates/error_tmpl.coffee index c4bf40ecbd..9cca1f9d32 100644 --- a/assets/javascripts/templates/error_tmpl.coffee +++ b/assets/javascripts/templates/error_tmpl.coffee @@ -12,8 +12,8 @@ app.templates.notFoundPage = -> app.templates.pageLoadError = -> error """ The page failed to load. """, - """ It may be missing from the server (try reloading the app) or you could be offline.
- If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """, + """ It may be missing from the server (try reloading the app) or you could be offline (try installing the documentation for offline usage when online again).
+ If you're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """, """ #{back} · Reload · Retry """ @@ -57,9 +57,9 @@ app.templates.unsupportedBrowser = """

DevDocs is an API documentation browser which supports the following browsers:

If you're unable to upgrade, we apologize. diff --git a/assets/javascripts/templates/notif_tmpl.coffee b/assets/javascripts/templates/notif_tmpl.coffee index 93611a5c12..0821036e77 100644 --- a/assets/javascripts/templates/notif_tmpl.coffee +++ b/assets/javascripts/templates/notif_tmpl.coffee @@ -68,3 +68,9 @@ app.templates.notifShare = -> app.templates.notifUpdateDocs = -> textNotif """ Documentation updates available. """, """ Install them as soon as possible to avoid broken pages. """ + +app.templates.notifAnalyticsConsent = -> + textNotif """ Tracking cookies """, + """ We would like to gather usage data about how DevDocs is used through Google Analytics and Gauges. We only collect anonymous traffic information. + Please confirm if you accept our tracking cookies. You can always change your decision in the settings. +
Accept or Decline """ diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee index 5d99740368..5fc27d3cab 100644 --- a/assets/javascripts/templates/pages/about_tmpl.coffee +++ b/assets/javascripts/templates/pages/about_tmpl.coffee @@ -73,7 +73,8 @@ app.templates.aboutPage = -> """