diff --git a/.editorconfig b/.editorconfig index 73db316c..03c17bf8 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 489067b9..0c53b8e9 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 807f88e0..0fb624d4 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 27f04dd4..53bbb745 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 914ec967..ec1cf33c 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 6a7070f5..9daeafb9 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 d5e5a6aa..6c59f0be 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 00000000..26f8ab09 --- /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 77443edd..b033427a 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 5bd25afe..33a06b0e 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 31b57064..3c9ce68b 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 d968a27b..d1abb66b 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 1020d411..630142d4 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 00000000..4a72ec74 --- /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 c5bfb6b8..00000000 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 1f81ecd9..00000000 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 bd9d44a8..00000000 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 512ea7a5..00000000 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 ab67a92a..b55e552c 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 235cae02..00000000 --- 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 ec26b697..a822f7e2 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 00000000..40235566 --- /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 8d309c41..350a08c0 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 5630b488..3558d6bc 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 3b340571..eaf1bd4f 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 00000000..2f2d9f93 --- /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 5d3f6c88..91d73432 100644 --- a/assets/javascripts/lib/page.coffee +++ b/assets/javascripts/lib/page.coffee @@ -199,5 +199,21 @@ page.track = (fn) -> return track = -> - tracker.call() for tracker in trackers + 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 058106fd..001b13de 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 2d5f7e4a..c51e13fa 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 32da738e..0266dea3 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 c4bf40ec..9cca1f9d 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 93611a5c..0821036e 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 5d997403..c3c0fdec 100644 --- a/assets/javascripts/templates/pages/about_tmpl.coffee +++ b/assets/javascripts/templates/pages/about_tmpl.coffee @@ -73,7 +73,8 @@ app.templates.aboutPage = -> """