History

Merge branch 'master' into bash-50

Jasper van Merle committed 7 years ago

Commit b3b286f2 parents cd81a8c 7f0feee222 files · +1893 −517Browse files

Unified · Split

222 files
  1. .editorconfig +3 −0
  2. .github/CONTRIBUTING.md +4 −5
  3. .github/ISSUE_TEMPLATE.md +1 −1
  4. .gitignore +1 −0
  5. .ruby-version +1 −1
  6. .slugignore +1 −2
  7. .travis.yml +20 −1
  8. CODE_OF_CONDUCT.md +2 −0
  9. Dockerfile +2 −1
  10. Dockerfile-alpine +2 −1
  11. Gemfile +4 −1
  12. Gemfile.lock +39 −31
  13. PULL_REQUEST_TEMPLATE.md +11 −0
  14. README.md +11 −10
  15. assets/images/.gitignore +1 −0
  16. assets/images/docs-1.png +0 −0
  17. assets/images/docs-1@2x.png +0 −0
  18. assets/images/docs-2.png +0 −0
  19. assets/images/docs-2@2x.png +0 −0
  20. assets/javascripts/app/app.coffee +13 −8
  21. assets/javascripts/app/appcache.coffee +0 −42
  22. assets/javascripts/app/config.coffee.erb +2 −0
  23. assets/javascripts/app/serviceworker.coffee +49 −0
  24. assets/javascripts/app/settings.coffee +33 −0
  25. assets/javascripts/app/update_checker.coffee +3 −3
  26. assets/javascripts/lib/page.coffee +17 −1
  27. assets/javascripts/lib/util.coffee +4 −0
  28. assets/javascripts/models/doc.coffee +3 −1
  29. assets/javascripts/news.json +4 −0
  30. assets/javascripts/templates/error_tmpl.coffee +5 −5
  31. assets/javascripts/templates/notif_tmpl.coffee +6 −0
  32. assets/javascripts/templates/pages/about_tmpl.coffee +2 −1
  33. assets/javascripts/templates/pages/offline_tmpl.coffee +9 −4
  34. assets/javascripts/templates/pages/root_tmpl.coffee.erb +2 −2
  35. assets/javascripts/templates/pages/settings_tmpl.coffee +8 −0
  36. assets/javascripts/tracking.js +27 −23
  37. assets/javascripts/views/content/entry_page.coffee +1 −1
  38. assets/javascripts/views/content/settings_page.coffee +10 −11
  39. assets/javascripts/views/layout/document.coffee +7 −5
  40. assets/javascripts/views/layout/resizer.coffee +2 −9
  41. assets/javascripts/views/layout/settings.coffee +1 −9
  42. assets/javascripts/views/search/search.coffee +8 −0
  43. assets/javascripts/views/search/search_scope.coffee +23 −5
  44. assets/javascripts/views/sidebar/sidebar.coffee +1 −1
  45. assets/stylesheets/application.css.scss +3 −4
  46. assets/stylesheets/components/_header.scss +1 −0
  47. assets/stylesheets/components/_notif.scss +4 −0
  48. assets/stylesheets/global/_icons.scss +0 −182
  49. assets/stylesheets/global/_icons.scss.erb +43 −0
  50. docs/adding-docs.md +5 −3
  51. docs/filter-reference.md +0 −0
  52. docs/maintainers.md +7 −7
  53. docs/scraper-reference.md +41 −0
  54. lib/app.rb +42 −24
  55. lib/docs/core/doc.rb +99 −1
  56. lib/docs/core/scrapers/file_scraper.rb +2 −2
  57. lib/docs/scrapers/angular.rb +4 −0
  58. lib/docs/scrapers/angularjs.rb +4 −0
  59. lib/docs/scrapers/ansible.rb +5 −0
  60. lib/docs/scrapers/apache.rb +5 −0
  61. lib/docs/scrapers/apache_pig.rb +5 −0
  62. lib/docs/scrapers/async.rb +5 −0
  63. lib/docs/scrapers/babel.rb +5 −0
  64. lib/docs/scrapers/backbone.rb +5 −0
  65. lib/docs/scrapers/bash.rb +5 −0
  66. lib/docs/scrapers/bluebird.rb +4 −0
  67. lib/docs/scrapers/bootstrap.rb +5 −0
  68. lib/docs/scrapers/bottle.rb +6 −0
  69. lib/docs/scrapers/bower.rb +4 −0
  70. lib/docs/scrapers/c.rb +7 −0
  71. lib/docs/scrapers/cakephp.rb +5 −0
  72. lib/docs/scrapers/chai.rb +4 −0
  73. lib/docs/scrapers/chef.rb +5 −0
  74. lib/docs/scrapers/clojure.rb +5 −0
  75. lib/docs/scrapers/cmake.rb +6 −0
  76. lib/docs/scrapers/codeception.rb +5 −0
  77. lib/docs/scrapers/codeceptjs.rb +4 −0
  78. lib/docs/scrapers/codeigniter.rb +6 −0
  79. lib/docs/scrapers/coffeescript.rb +4 −0
  80. lib/docs/scrapers/cordova.rb +10 −0
  81. lib/docs/scrapers/cpp.rb +8 −0
  82. lib/docs/scrapers/crystal.rb +5 −0
  83. lib/docs/scrapers/d.rb +5 −0
  84. lib/docs/scrapers/d3.rb +4 −0
  85. lib/docs/scrapers/dart.rb +6 −0
  86. lib/docs/scrapers/django.rb +5 −0
  87. lib/docs/scrapers/docker.rb +6 −0
  88. lib/docs/scrapers/dojo.rb +5 −0
  89. lib/docs/scrapers/drupal.rb +5 −0
  90. lib/docs/scrapers/electron.rb +5 −0
  91. lib/docs/scrapers/elixir.rb +5 −0
  92. lib/docs/scrapers/ember.rb +5 −0
  93. lib/docs/scrapers/erlang.rb +5 −0
  94. lib/docs/scrapers/eslint.rb +4 −0
  95. lib/docs/scrapers/express.rb +4 −0
  96. lib/docs/scrapers/falcon.rb +5 −0
  97. lib/docs/scrapers/fish.rb +5 −0
  98. lib/docs/scrapers/flow.rb +4 −0
  99. lib/docs/scrapers/git.rb +5 −0
  100. lib/docs/scrapers/gnu/gcc.rb +6 −0
  101. lib/docs/scrapers/gnu/gnu_fortran.rb +6 −0
  102. lib/docs/scrapers/go.rb +5 −0
  103. lib/docs/scrapers/godot.rb +5 −0
  104. lib/docs/scrapers/graphite.rb +5 −0
  105. lib/docs/scrapers/grunt.rb +4 −0
  106. lib/docs/scrapers/handlebars.rb +4 −0
  107. lib/docs/scrapers/haskell.rb +8 −1
  108. lib/docs/scrapers/haxe.rb +6 −0
  109. lib/docs/scrapers/homebrew.rb +4 −0
  110. lib/docs/scrapers/http.rb +2 −0
  111. lib/docs/scrapers/immutable.rb +4 −0
  112. lib/docs/scrapers/influxdata.rb +6 −0
  113. lib/docs/scrapers/jasmine.rb +4 −0
  114. lib/docs/scrapers/jekyll.rb +5 −0
  115. lib/docs/scrapers/jest.rb +5 −0
  116. lib/docs/scrapers/jquery/jquery_core.rb +4 −0
  117. lib/docs/scrapers/jquery/jquery_mobile.rb +5 −0
  118. lib/docs/scrapers/jquery/jquery_ui.rb +4 −0
  119. lib/docs/scrapers/jsdoc.rb +4 −0
  120. lib/docs/scrapers/julia.rb +4 −0
  121. lib/docs/scrapers/knockout.rb +4 −0
  122. lib/docs/scrapers/koa.rb +4 −0
  123. lib/docs/scrapers/kotlin.rb +4 −0
  124. lib/docs/scrapers/laravel.rb +4 −0
  125. lib/docs/scrapers/leaflet.rb +5 −0
  126. lib/docs/scrapers/less.rb +6 −0
  127. lib/docs/scrapers/liquid.rb +5 −0
  128. lib/docs/scrapers/lodash.rb +5 −0
  129. lib/docs/scrapers/love.rb +5 −0
  130. lib/docs/scrapers/lua.rb +5 −0
  131. lib/docs/scrapers/marionette.rb +4 −0
  132. lib/docs/scrapers/markdown.rb +4 −0
  133. lib/docs/scrapers/matplotlib.rb +4 −0
  134. lib/docs/scrapers/mdn/css.rb +2 −0
  135. lib/docs/scrapers/mdn/dom.rb +2 −0
  136. lib/docs/scrapers/mdn/dom_events.rb +2 −0
  137. lib/docs/scrapers/mdn/html.rb +2 −0
  138. lib/docs/scrapers/mdn/javascript.rb +2 −0
  139. lib/docs/scrapers/mdn/mdn.rb +5 −0
  140. lib/docs/scrapers/mdn/svg.rb +2 −0
  141. lib/docs/scrapers/mdn/xslt_xpath.rb +2 −0
  142. lib/docs/scrapers/meteor.rb +5 −0
  143. lib/docs/scrapers/mocha.rb +4 −0
  144. lib/docs/scrapers/modernizr.rb +4 −0
  145. lib/docs/scrapers/moment.rb +5 −0
  146. lib/docs/scrapers/mongoose.rb +6 −0
  147. lib/docs/scrapers/nginx.rb +6 −0
  148. lib/docs/scrapers/nginx_lua_module.rb +9 −0
  149. lib/docs/scrapers/nim.rb +5 −0
  150. lib/docs/scrapers/node.rb +5 −0
  151. lib/docs/scrapers/nokogiri2.rb +4 −0
  152. lib/docs/scrapers/npm.rb +4 −0
  153. lib/docs/scrapers/numpy.rb +4 −0
  154. lib/docs/scrapers/openjdk.rb +20 −0
  155. lib/docs/scrapers/opentsdb.rb +4 −0
  156. lib/docs/scrapers/padrino.rb +4 −0
  157. lib/docs/scrapers/pandas.rb +6 −0
  158. lib/docs/scrapers/perl.rb +6 −0
  159. lib/docs/scrapers/phalcon.rb +5 −0
  160. lib/docs/scrapers/phaser.rb +4 −0
  161. lib/docs/scrapers/phoenix.rb +5 −0
  162. lib/docs/scrapers/php.rb +6 −0
  163. lib/docs/scrapers/phpunit.rb +6 −0
  164. lib/docs/scrapers/postgresql.rb +6 −0
  165. lib/docs/scrapers/pug.rb +4 −0
  166. lib/docs/scrapers/puppeteer.rb +5 −0
  167. lib/docs/scrapers/pygame.rb +5 −0
  168. lib/docs/scrapers/python.rb +5 −0
  169. lib/docs/scrapers/q.rb +4 −0
  170. lib/docs/scrapers/qt.rb +5 −0
  171. lib/docs/scrapers/ramda.rb +5 −0
  172. lib/docs/scrapers/rdoc/minitest.rb +5 −0
  173. lib/docs/scrapers/rdoc/rails.rb +4 −0
  174. lib/docs/scrapers/rdoc/ruby.rb +11 −0
  175. lib/docs/scrapers/react.rb +5 −0
  176. lib/docs/scrapers/react_native.rb +5 −0
  177. lib/docs/scrapers/redis.rb +6 −0
  178. lib/docs/scrapers/redux.rb +4 −0
  179. lib/docs/scrapers/relay.rb +5 −0
  180. lib/docs/scrapers/requirejs.rb +4 −0
  181. lib/docs/scrapers/rethinkdb.rb +4 −0
  182. lib/docs/scrapers/rust.rb +6 −0
  183. lib/docs/scrapers/sass.rb +4 −0
  184. lib/docs/scrapers/scikit_image.rb +5 −0
  185. lib/docs/scrapers/scikit_learn.rb +4 −0
  186. lib/docs/scrapers/sinon.rb +5 −0
  187. lib/docs/scrapers/socketio.rb +4 −0
  188. lib/docs/scrapers/sqlite.rb +5 −0
  189. lib/docs/scrapers/statsmodels.rb +4 −0
  190. lib/docs/scrapers/support_tables.rb +6 −0
  191. lib/docs/scrapers/symfony.rb +4 −0
  192. lib/docs/scrapers/tcl_tk.rb +5 −0
  193. lib/docs/scrapers/tensorflow.rb +4 −0
  194. lib/docs/scrapers/terraform.rb +5 −0
  195. lib/docs/scrapers/twig.rb +5 −0
  196. lib/docs/scrapers/typescript.rb +4 −0
  197. lib/docs/scrapers/underscore.rb +5 −0
  198. lib/docs/scrapers/vagrant.rb +5 −0
  199. lib/docs/scrapers/vue.rb +4 −0
  200. lib/docs/scrapers/vulkan.rb +5 −0
  201. lib/docs/scrapers/webpack.rb +4 −0
  202. lib/docs/scrapers/yarn.rb +4 −0
  203. lib/docs/scrapers/yii.rb +4 −0
  204. lib/tasks/assets.thor +3 −0
  205. lib/tasks/sprites.thor +211 −0
  206. lib/tasks/updates.thor +315 −0
  207. public/docs/docs.json +0 −1
  208. public/icons/docs-1.pxm +0 −0
  209. public/icons/docs-1@2x.pxm +0 −0
  210. public/icons/docs-2.pxm +0 −0
  211. public/icons/docs-2@2x.pxm +0 −0
  212. public/icons/docs/bluebird/16@2x.png +0 −0
  213. public/images/webapp-icon-192.png +0 −0
  214. public/manifest.json +6 −0
  215. test/app_test.rb +0 −81
  216. test/lib/docs/core/scrapers/file_scraper_test.rb +2 −2
  217. views/app.erb +1 −13
  218. views/index.erb +3 −2
  219. views/manifest.erb +0 −14
  220. views/other.erb +3 −2
  221. views/service-worker.js.erb +49 −0
  222. views/unsupported.erb +3 −3
.editorconfig+3−0
@@ -6,3 +6,6 @@ indent_style = space · 5 unmodified lines
6indent_size = 26indent_size = 2
7insert_final_newline = true7insert_final_newline = true
8trim_trailing_whitespace = true8trim_trailing_whitespace = true
9
10[*.md]
11trim_trailing_whitespace = false
.github/CONTRIBUTING.md+4−5
@@ -24,12 +24,12 @@ Want to contribute? Great. Please review the following guidelines carefully and · 23 unmodified lines
24## Requesting new features24## Requesting new features
25 25
261. Search for similar feature requests; someone may have already requested it.261. Search for similar feature requests; someone may have already requested it.
272. Make sure your feature fits DevDocs's [vision](https://github.com/freeCodeCamp/devdocs/blob/master/README.md#vision).272. Make sure your feature fits DevDocs's [vision](../README.md#vision).
283. Provide a clear and detailed explanation of the feature and why it's important to add it.283. Provide a clear and detailed explanation of the feature and why it's important to add it.
29 29
30## Requesting new documentations30## Requesting new documentations
31 31
32Please don't open issues to request new documentations. 32Please don't open issues to request new documentations.
33Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) where everyone can vote.33Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) where everyone can vote.
34 34
35## Contributing code and features35## Contributing code and features
@@ -44,7 +44,7 @@ Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) wher · 8 unmodified lines
44 44
45## Contributing new documentations45## Contributing new documentations
46 46
47See the [wiki](https://github.com/freeCodeCamp/devdocs/wiki) to learn how to add new documentations.47See the [`docs` folder](https://github.com/freeCodeCamp/devdocs/tree/master/docs) to learn how to add new documentations.
48 48
49**Important:** the documentation's license must permit alteration, redistribution and commercial use, and the documented software must be released under an open source license. Feel free to get in touch if you're not sure if a documentation meets those requirements.49**Important:** the documentation's license must permit alteration, redistribution and commercial use, and the documented software must be released under an open source license. Feel free to get in touch if you're not sure if a documentation meets those requirements.
50 50
@@ -55,13 +55,12 @@ In addition to the [guidelines for contributing code](#contributing-code-and-fea · 4 unmodified lines
55* Remove as much content and HTML markup as possible, particularly content not associated with any entry (e.g. introduction, changelog, etc.).55* Remove as much content and HTML markup as possible, particularly content not associated with any entry (e.g. introduction, changelog, etc.).
56* Names must be as short as possible and unique across the documentation.56* Names must be as short as possible and unique across the documentation.
57* The number of types (categories) should ideally be less than 100.57* The number of types (categories) should ideally be less than 100.
58* Don't modify the icon sprite. I'll do it after your pull request is merged.
59 58
60## Updating existing documentations59## Updating existing documentations
61 60
62Please 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.61Please 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.
63 62
64To ask that an existing documentation be updated, please use the [Trello board](https://trello.com/c/2B0hmW7M/52-request-updates-here).63To ask that an existing documentation be updated, first check the last two [documentation versions reports](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.
65 64
66## Coding conventions65## Coding conventions
67 66
.github/ISSUE_TEMPLATE.md+1−1
@@ -2,6 +2,6 @@ · 1 unmodified line
2 Please read the contributing guidelines before opening an issue:2 Please read the contributing guidelines before opening an issue:
3 https://github.com/freeCodeCamp/devdocs/blob/master/.github/CONTRIBUTING.md3 https://github.com/freeCodeCamp/devdocs/blob/master/.github/CONTRIBUTING.md
4 4
5 To request a new documentation, or an update of an existing documentation, go here:5 Go here to request a new documentation:
6 https://trello.com/b/6BmTulfx/devdocs-documentation6 https://trello.com/b/6BmTulfx/devdocs-documentation
7-->7-->
.gitignore+1−0
@@ -1,5 +1,6 @@
1.DS_Store1.DS_Store
2.bundle2.bundle
3log
3tmp4tmp
4public/assets5public/assets
5public/fonts6public/fonts
.ruby-version+1−1
@@ -1 +1 @@
12.6.012.6.3
.slugignore+1−2
@@ -1,2 +1 @@
1public/icons1test
2test
.travis.yml+20−1
@@ -1,7 +1,26 @@
1language: ruby1language: ruby
2 2
3addons:
4 apt:
5 packages:
6 - libcurl4-openssl-dev
7
3cache: bundler8cache: bundler
4 9
5before_script:10before_install:
11 - "echo 'gem: --no-document' > ~/.gemrc"
6 - gem update --system12 - gem update --system
7 - gem install bundler13 - gem install bundler
14
15script:
16 - if [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then bundle exec rake; fi
17 - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then bundle exec thor updates:check --github-token $GH_TOKEN --upload; fi
18
19deploy:
20 provider: heroku
21 app: devdocs
22 on:
23 branch: master
24 condition: $TRAVIS_EVENT_TYPE != cron
25 api_key:
26 secure: "4p1klvWJZSOImzFcKOduILjP93hlOlAhceWlYMKS4tU+TCFE8qTBzdKdFPSCsCgjB+YR9pBss+L0lJpVVMjSwFHXqpKe6EeUSltO2k7DFHfW7kXLUM/L0AfqXz+YXk76XUyZMhvOEbldPfaMaj10e8vgDOQCSHABDyK/4CU+hnI="
CODE_OF_CONDUCT.md+2−0
@@ -0,0 +1,2 @@
1
2> Our Code of Conduct is available here: <https://code-of-conduct.freecodecamp.org/>
Dockerfile+2−1
@@ -1,6 +1,7 @@
1FROM ruby:2.6.01FROM ruby:2.6.3
2 2
3ENV LANG=C.UTF-83ENV LANG=C.UTF-8
4ENV ENABLE_SERVICE_WORKER=true
4 5
5WORKDIR /devdocs6WORKDIR /devdocs
6 7
Dockerfile-alpine+2−1
@@ -1,6 +1,7 @@
1FROM ruby:2.6.0-alpine1FROM ruby:2.6.3-alpine
2 2
3ENV LANG=C.UTF-83ENV LANG=C.UTF-8
4ENV ENABLE_SERVICE_WORKER=true
4 5
5WORKDIR /devdocs6WORKDIR /devdocs
6 7
Gemfile+4−1
@@ -1,5 +1,5 @@
1source 'https://rubygems.org'1source 'https://rubygems.org'
2ruby '2.6.0'2ruby '2.6.3'
3 3
4gem 'rake'4gem 'rake'
5gem 'thor'5gem 'thor'
@@ -22,6 +22,8 @@ group :app do · 16 unmodified lines
22 gem 'browser'22 gem 'browser'
23 gem 'sass'23 gem 'sass'
24 gem 'coffee-script'24 gem 'coffee-script'
25 gem 'chunky_png'
26 gem 'sprockets-sass'
25end27end
26 28
27group :production do29group :production do
@@ -40,6 +42,7 @@ group :docs do · 12 unmodified lines
40 gem 'unix_utils', require: false42 gem 'unix_utils', require: false
41 gem 'tty-pager', require: false43 gem 'tty-pager', require: false
42 gem 'net-sftp', '>= 2.1.3.rc2', require: false44 gem 'net-sftp', '>= 2.1.3.rc2', require: false
45 gem 'terminal-table', require: false
43end46end
44 47
45group :test do48group :test do
Gemfile.lock+39−31
@@ -1,49 +1,50 @@
1GEM1GEM
2 remote: https://rubygems.org/2 remote: https://rubygems.org/
3 specs:3 specs:
4 activesupport (5.2.2)4 activesupport (5.2.3)
5 concurrent-ruby (~> 1.0, >= 1.0.2)5 concurrent-ruby (~> 1.0, >= 1.0.2)
6 i18n (>= 0.7, < 2)6 i18n (>= 0.7, < 2)
7 minitest (~> 5.1)7 minitest (~> 5.1)
8 tzinfo (~> 1.1)8 tzinfo (~> 1.1)
9 backports (3.11.4)9 backports (3.15.0)
10 better_errors (2.5.0)10 better_errors (2.5.1)
11 coderay (>= 1.0.0)11 coderay (>= 1.0.0)
12 erubi (>= 1.0.0)12 erubi (>= 1.0.0)
13 rack (>= 0.9.0)13 rack (>= 0.9.0)
14 browser (2.5.3)14 browser (2.6.1)
15 chunky_png (1.3.11)
15 coderay (1.1.2)16 coderay (1.1.2)
16 coffee-script (2.4.1)17 coffee-script (2.4.1)
17 coffee-script-source18 coffee-script-source
18 execjs19 execjs
19 coffee-script-source (1.12.2)20 coffee-script-source (1.12.2)
20 concurrent-ruby (1.1.4)21 concurrent-ruby (1.1.5)
21 daemons (1.3.1)22 daemons (1.3.1)
22 erubi (1.8.0)23 erubi (1.8.0)
23 ethon (0.12.0)24 ethon (0.12.0)
24 ffi (>= 1.3.0)25 ffi (>= 1.3.0)
25 eventmachine (1.2.7)26 eventmachine (1.2.7)
26 execjs (2.7.0)27 execjs (2.7.0)
27 exifr (1.3.5)28 exifr (1.3.6)
28 ffi (1.10.0)29 ffi (1.11.1)
29 fspath (3.1.0)30 fspath (3.1.2)
30 highline (2.0.0)31 highline (2.0.2)
31 html-pipeline (2.10.0)32 html-pipeline (2.11.1)
32 activesupport (>= 2)33 activesupport (>= 2)
33 nokogiri (>= 1.4)34 nokogiri (>= 1.4)
34 i18n (1.5.2)35 i18n (1.6.0)
35 concurrent-ruby (~> 1.0)36 concurrent-ruby (~> 1.0)
36 image_optim (0.26.3)37 image_optim (0.26.5)
37 exifr (~> 1.2, >= 1.2.2)38 exifr (~> 1.2, >= 1.2.2)
38 fspath (~> 3.0)39 fspath (~> 3.0)
39 image_size (>= 1.5, < 3)40 image_size (>= 1.5, < 3)
40 in_threads (~> 1.3)41 in_threads (~> 1.3)
41 progress (~> 3.0, >= 3.0.1)42 progress (~> 3.0, >= 3.0.1)
42 image_optim_pack (0.5.1.20190105)43 image_optim_pack (0.5.6)
43 fspath (>= 2.1, < 4)44 fspath (>= 2.1, < 4)
44 image_optim (~> 0.19)45 image_optim (~> 0.19)
45 image_size (2.0.0)46 image_size (2.0.2)
46 in_threads (1.5.1)47 in_threads (1.5.3)
47 method_source (0.9.2)48 method_source (0.9.2)
48 mini_portile2 (2.4.0)49 mini_portile2 (2.4.0)
49 minitest (5.11.3)50 minitest (5.11.3)
@@ -51,19 +52,19 @@ GEM · 1 unmodified line
51 mustermann (1.0.3)52 mustermann (1.0.3)
52 net-sftp (3.0.0.beta1)53 net-sftp (3.0.0.beta1)
53 net-ssh (>= 5.0.0, < 6.0.0)54 net-ssh (>= 5.0.0, < 6.0.0)
54 net-ssh (5.1.0)55 net-ssh (5.2.0)
55 newrelic_rpm (5.7.0.350)56 newrelic_rpm (6.5.0.357)
56 nokogiri (1.10.1)57 nokogiri (1.10.3)
57 mini_portile2 (~> 2.4.0)58 mini_portile2 (~> 2.4.0)
58 options (2.3.2)59 options (2.3.2)
59 progress (3.5.0)60 progress (3.5.2)
60 progress_bar (1.3.0)61 progress_bar (1.3.0)
61 highline (>= 1.6, < 3)62 highline (>= 1.6, < 3)
62 options (~> 2.3.0)63 options (~> 2.3.0)
63 pry (0.12.2)64 pry (0.12.2)
64 coderay (~> 1.1.0)65 coderay (~> 1.1.0)
65 method_source (~> 0.9.0)66 method_source (~> 0.9.0)
66 rack (2.0.6)67 rack (2.0.7)
67 rack-protection (2.0.5)68 rack-protection (2.0.5)
68 rack69 rack
69 rack-ssl-enforcer (0.2.9)70 rack-ssl-enforcer (0.2.9)
@@ -74,7 +75,7 @@ GEM · 4 unmodified lines
74 rb-inotify (0.10.0)75 rb-inotify (0.10.0)
75 ffi (~> 1.0)76 ffi (~> 1.0)
76 rr (1.2.1)77 rr (1.2.1)
77 sass (3.7.3)78 sass (3.7.4)
78 sass-listen (~> 4.0.0)79 sass-listen (~> 4.0.0)
79 sass-listen (4.0.0)80 sass-listen (4.0.0)
80 rb-fsevent (~> 0.9, >= 0.9.4)81 rb-fsevent (~> 0.9, >= 0.9.4)
@@ -96,11 +97,15 @@ GEM · 15 unmodified lines
96 rack (> 1, < 3)97 rack (> 1, < 3)
97 sprockets-helpers (1.2.1)98 sprockets-helpers (1.2.1)
98 sprockets (>= 2.2)99 sprockets (>= 2.2)
99 strings (0.1.4)100 sprockets-sass (2.0.0.beta2)
100 strings-ansi (~> 0.1.0)101 sprockets (>= 2.0, < 4.0)
101 unicode-display_width (~> 1.4.0)102 strings (0.1.5)
102 unicode_utils (~> 1.4.0)103 strings-ansi (~> 0.1)
104 unicode-display_width (~> 1.5)
105 unicode_utils (~> 1.4)
103 strings-ansi (0.1.0)106 strings-ansi (0.1.0)
107 terminal-table (1.8.0)
108 unicode-display_width (~> 1.1, >= 1.1.1)
104 thin (1.7.2)109 thin (1.7.2)
105 daemons (~> 1.0, >= 1.0.9)110 daemons (~> 1.0, >= 1.0.9)
106 eventmachine (~> 1.0, >= 1.0.4)111 eventmachine (~> 1.0, >= 1.0.4)
@@ -108,19 +113,19 @@ GEM · 1 unmodified line
108 thor (0.20.3)113 thor (0.20.3)
109 thread_safe (0.3.6)114 thread_safe (0.3.6)
110 tilt (2.0.9)115 tilt (2.0.9)
111 tty-pager (0.12.0)116 tty-pager (0.12.1)
112 strings (~> 0.1.4)117 strings (~> 0.1.4)
113 tty-screen (~> 0.6)118 tty-screen (~> 0.6)
114 tty-which (~> 0.4)119 tty-which (~> 0.4)
115 tty-screen (0.6.5)120 tty-screen (0.7.0)
116 tty-which (0.4.0)121 tty-which (0.4.1)
117 typhoeus (1.3.1)122 typhoeus (1.3.1)
118 ethon (>= 0.9.0)123 ethon (>= 0.9.0)
119 tzinfo (1.2.5)124 tzinfo (1.2.5)
120 thread_safe (~> 0.1)125 thread_safe (~> 0.1)
121 uglifier (4.1.20)126 uglifier (4.1.20)
122 execjs (>= 0.3.0, < 3)127 execjs (>= 0.3.0, < 3)
123 unicode-display_width (1.4.1)128 unicode-display_width (1.6.0)
124 unicode_utils (1.4.0)129 unicode_utils (1.4.0)
125 unix_utils (0.0.15)130 unix_utils (0.0.15)
126 yajl-ruby (1.4.1)131 yajl-ruby (1.4.1)
@@ -132,6 +137,7 @@ DEPENDENCIES · 5 unmodified lines
132 activesupport (~> 5.2)137 activesupport (~> 5.2)
133 better_errors138 better_errors
134 browser139 browser
140 chunky_png
135 coffee-script141 coffee-script
136 erubi142 erubi
137 html-pipeline143 html-pipeline
@@ -153,6 +159,8 @@ DEPENDENCIES · 15 unmodified lines
153 sinatra-contrib159 sinatra-contrib
154 sprockets160 sprockets
155 sprockets-helpers161 sprockets-helpers
162 sprockets-sass
163 terminal-table
156 thin164 thin
157 thor165 thor
158 tty-pager166 tty-pager
@@ -162,7 +170,7 @@ DEPENDENCIES · 3 unmodified lines
162 yajl-ruby170 yajl-ruby
163 171
164RUBY VERSION172RUBY VERSION
165 ruby 2.6.0p0173 ruby 2.6.3p62
166 174
167BUNDLED WITH175BUNDLED WITH
168 1.17.2176 2.0.2
PULL_REQUEST_TEMPLATE.md+11−0
@@ -0,0 +1,11 @@
1If you’re adding a new scraper, please ensure that you have:
2
3- [ ] Tested the scraper on a local copy of DevDocs
4- [ ] Ensured that the docs are styled similarly to other docs on DevDocs
5<!-- If the docs don’t have an icon, delete the next four items: -->
6- [ ] Added these files to the <code>public/icons/*your_scraper_name*/</code> directory:
7 - [ ] `16.png`: a 16×16 pixel icon for the doc
8 - [ ] `16@2x.png`: a 32×32 pixel icon for the doc
9 - [ ] `SOURCE`: A text file containing the URL to the page the image can be found on or the URL of the original image itself
10
11<!-- Replace the `[ ]` with a `[x]` once you’ve completed each step. -->
README.md+11−10
@@ -59,14 +59,14 @@ The web app is all client-side JavaScript, written in [CoffeeScript](http://coff · 58 unmodified lines
59 59
60Many 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.60Many 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.
61 61
62Another 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.62Another 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.
63 63
64DevDocs being a developer tool, the browser requirements are high:64DevDocs being a developer tool, the browser requirements are high:
65 65
66* Recent versions of Firefox, Chrome, or Opera66* Recent versions of Firefox, Chrome, or Opera
67* Safari 9.1+67* Safari 11.1+
68* Edge 16+68* Edge 17+
69* iOS 10+69* iOS 11.3+
70 70
71This allows the code to take advantage of the latest DOM and HTML5 APIs and make developing DevDocs a lot more fun!71This allows the code to take advantage of the latest DOM and HTML5 APIs and make developing DevDocs a lot more fun!
72 72
@@ -83,12 +83,13 @@ Modifications made to each document include: · 10 unmodified lines
83* replacing all external (not scraped) URLs with their fully qualified counterpart83* replacing all external (not scraped) URLs with their fully qualified counterpart
84* replacing all internal (scraped) URLs with their unqualified and relative counterpart84* replacing all internal (scraped) URLs with their unqualified and relative counterpart
85* adding content, such as a title and link to the original document85* adding content, such as a title and link to the original document
86* ensuring correct syntax highlighting using [Prism](http://prismjs.com/)
86 87
87These modifications are applied via a set of filters using the [HTML::Pipeline](https://github.com/jch/html-pipeline) library. Each scraper includes filters specific to itself, one of which is tasked with figuring out the pages' metadata.88These modifications are applied via a set of filters using the [HTML::Pipeline](https://github.com/jch/html-pipeline) library. Each scraper includes filters specific to itself, one of which is tasked with figuring out the pages' metadata.
88 89
89The 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.).90The 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.).
90 91
91More information about scrapers and filters is available on the [wiki](https://github.com/freeCodeCamp/devdocs/wiki).92More information about [scrapers](./docs/scraper-reference.md) and [filters](./docs/filter-reference.md) is available in the `docs` folder.
92 93
93## Available Commands94## Available Commands
94 95
@@ -128,9 +129,7 @@ If multiple versions of Ruby are installed on your system, commands must be run · 33 unmodified lines
128 129
129## Contributing130## Contributing
130 131
131Contributions are welcome. Please read the [contributing guidelines](https://github.com/freeCodeCamp/devdocs/blob/master/.github/CONTRIBUTING.md).132Contributions are welcome. Please read the [contributing guidelines](./.github/CONTRIBUTING.md).
132
133DevDocs's own documentation is available on the [wiki](https://github.com/freeCodeCamp/devdocs/wiki).
134 133
135## Documentation134## Documentation
136 135
@@ -145,7 +144,7 @@ DevDocs's own documentation is available on the [wiki](https://github.com/freeCo · 8 unmodified lines
145* [Ubuntu Touch app](https://uappexplorer.com/app/devdocsunofficial.berkes)144* [Ubuntu Touch app](https://uappexplorer.com/app/devdocsunofficial.berkes)
146* [Sublime Text plugin](https://sublime.wbond.net/packages/DevDocs)145* [Sublime Text plugin](https://sublime.wbond.net/packages/DevDocs)
147* [Atom plugin](https://atom.io/packages/devdocs)146* [Atom plugin](https://atom.io/packages/devdocs)
148* [Brackets extension](https://github.com/gruehle/dev-docs-viewer) 147* [Brackets extension](https://github.com/gruehle/dev-docs-viewer)
149* [Fluid](http://fluidapp.com) for turning DevDocs into a real OS X app148* [Fluid](http://fluidapp.com) for turning DevDocs into a real OS X app
150* [GTK shell / Vim integration](https://github.com/naquad/devdocs-shell)149* [GTK shell / Vim integration](https://github.com/naquad/devdocs-shell)
151* [Emacs lookup](https://github.com/skeeto/devdocs-lookup)150* [Emacs lookup](https://github.com/skeeto/devdocs-lookup)
@@ -156,12 +155,14 @@ DevDocs's own documentation is available on the [wiki](https://github.com/freeCo · 4 unmodified lines
156* [Desktop application](https://github.com/egoist/devdocs-desktop)155* [Desktop application](https://github.com/egoist/devdocs-desktop)
157* [Doc Browser](https://github.com/qwfy/doc-browser) is a native Linux app that supports DevDocs docsets156* [Doc Browser](https://github.com/qwfy/doc-browser) is a native Linux app that supports DevDocs docsets
158* [GNOME Application](https://github.com/hardpixel/devdocs-desktop) GTK3 application with search integrated in headerbar157* [GNOME Application](https://github.com/hardpixel/devdocs-desktop) GTK3 application with search integrated in headerbar
158* [macOS Application](https://github.com/dteoh/devdocs-macos)
159* [Android Application](https://github.com/Merith-TK/devdocs_webapp_kotlin) is a fully working, advanced WebView
159 160
160## Copyright / License161## Copyright / License
161 162
162Copyright 2013-2019 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors)163Copyright 2013-2019 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors)
163 164
164This 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.165This software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](./COPYRIGHT) and [LICENSE](./LICENSE) files.
165 166
166Please 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.167Please 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.
167 168
assets/images/.gitignore+1−0
@@ -0,0 +1 @@
1sprites/**/*
assets/images/docs-1.png+0−0

Binary file not shown.

assets/images/docs-1@2x.png+0−0

Binary file not shown.

assets/images/docs-2.png+0−0

Binary file not shown.

assets/images/docs-2@2x.png+0−0

Binary file not shown.

assets/javascripts/app/app.coffee+13−8
@@ -13,10 +13,12 @@ · 12 unmodified lines
13 13
14 @el = $('._app')14 @el = $('._app')
15 @localStorage = new LocalStorageStore15 @localStorage = new LocalStorageStore
16 @appCache = new app.AppCache if app.AppCache.isEnabled()16 @serviceWorker = new app.ServiceWorker if app.ServiceWorker.isEnabled()
17 @settings = new app.Settings17 @settings = new app.Settings
18 @db = new app.DB()18 @db = new app.DB()
19 19
20 @settings.initLayout()
21
20 @docs = new app.collections.Docs22 @docs = new app.collections.Docs
21 @disabledDocs = new app.collections.Docs23 @disabledDocs = new app.collections.Docs
22 @entries = new app.collections.Entries24 @entries = new app.collections.Entries
@@ -138,7 +140,10 @@ · 115 unmodified lines
138 @docs.sort()140 @docs.sort()
139 @initDoc(doc)141 @initDoc(doc)
140 @saveDocs()142 @saveDocs()
141 _onSuccess()143 if app.settings.get('autoInstall')
144 doc.install(_onSuccess, onError)
145 else
146 _onSuccess()
142 return147 return
143 148
144 doc.load onSuccess, onError, writeCache: true149 doc.load onSuccess, onError, writeCache: true
@@ -147,7 +152,7 @@ · 2 unmodified lines
147 saveDocs: ->152 saveDocs: ->
148 @settings.setDocs(doc.slug for doc in @docs.all())153 @settings.setDocs(doc.slug for doc in @docs.all())
149 @db.migrate()154 @db.migrate()
150 @appCache?.updateInBackground()155 @serviceWorker?.updateInBackground()
151 156
152 welcomeBack: ->157 welcomeBack: ->
153 visitCount = @settings.get('count')158 visitCount = @settings.get('count')
@@ -167,14 +172,14 @@ · 13 unmodified lines
167 reload: ->172 reload: ->
168 @docs.clearCache()173 @docs.clearCache()
169 @disabledDocs.clearCache()174 @disabledDocs.clearCache()
170 if @appCache then @appCache.reload() else @reboot()175 if @serviceWorker then @serviceWorker.reload() else @reboot()
171 return176 return
172 177
173 reset: ->178 reset: ->
174 @localStorage.reset()179 @localStorage.reset()
175 @settings.reset()180 @settings.reset()
176 @db?.reset()181 @db?.reset()
177 @appCache?.update()182 @serviceWorker?.update()
178 window.location = '/'183 window.location = '/'
179 return184 return
180 185
@@ -193,9 +198,9 @@ · 12 unmodified lines
193 return198 return
194 199
195 indexHost: ->200 indexHost: ->
196 # Can't load the index files from the host/CDN when applicationCache is201 # Can't load the index files from the host/CDN when service worker is
197 # enabled because it doesn't support caching URLs that use CORS.202 # enabled because it doesn't support caching URLs that use CORS.
198 @config[if @appCache and @settings.hasDocs() then 'index_path' else 'docs_origin']203 @config[if @serviceWorker and @settings.hasDocs() then 'index_path' else 'docs_origin']
199 204
200 onBootError: (args...) ->205 onBootError: (args...) ->
201 @trigger 'bootError'206 @trigger 'bootError'
@@ -252,7 +257,7 @@ · 50 unmodified lines
252 matchMedia: !!window.matchMedia257 matchMedia: !!window.matchMedia
253 insertAdjacentHTML: !!document.body.insertAdjacentHTML258 insertAdjacentHTML: !!document.body.insertAdjacentHTML
254 defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false259 defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false
255 cssVariables: CSS.supports and CSS.supports('(--t: 0)')260 cssVariables: !!CSS?.supports?('(--t: 0)')
256 261
257 for key, value of features when not value262 for key, value of features when not value
258 Raven.captureMessage "unsupported/#{key}", level: 'info'263 Raven.captureMessage "unsupported/#{key}", level: 'info'
assets/javascripts/app/appcache.coffee+0−42
@@ -1,42 +0,0 @@
1class app.AppCache
2 $.extend @prototype, Events
3
4 @isEnabled: ->
5 try
6 applicationCache and applicationCache.status isnt applicationCache.UNCACHED
7 catch
8
9 constructor: ->
10 @cache = applicationCache
11 @notifyUpdate = true
12 @onUpdateReady() if @cache.status is @cache.UPDATEREADY
13
14 $.on @cache, 'progress', @onProgress
15 $.on @cache, 'updateready', @onUpdateReady
16
17 update: ->
18 @notifyUpdate = true
19 @notifyProgress = true
20 try @cache.update() catch
21 return
22
23 updateInBackground: ->
24 @notifyUpdate = false
25 @notifyProgress = false
26 try @cache.update() catch
27 return
28
29 reload: ->
30 $.on @cache, 'updateready noupdate error', -> app.reboot()
31 @notifyUpdate = false
32 @notifyProgress = true
33 try @cache.update() catch
34 return
35
36 onProgress: (event) =>
37 @trigger 'progress', event if @notifyProgress
38 return
39
40 onUpdateReady: =>
41 @trigger 'updateready' if @notifyUpdate
42 return
assets/javascripts/app/config.coffee.erb+2−0
@@ -13,3 +13,5 @@ app.config = · 12 unmodified lines
13 version: <%= Time.now.to_i %>13 version: <%= Time.now.to_i %>
14 release: <%= Time.now.utc.httpdate.to_json %>14 release: <%= Time.now.utc.httpdate.to_json %>
15 mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'15 mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'
16 service_worker_path: '/service-worker.js'
17 service_worker_enabled: <%= App.environment == :production || ENV['ENABLE_SERVICE_WORKER'] == 'true' %>
assets/javascripts/app/serviceworker.coffee+49−0
@@ -0,0 +1,49 @@
1class 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
assets/javascripts/app/settings.coffee+33−0
@@ -5,11 +5,13 @@ class app.Settings · 4 unmodified lines
5 'manualUpdate'5 'manualUpdate'
6 'fastScroll'6 'fastScroll'
7 'arrowScroll'7 'arrowScroll'
8 'analyticsConsent'
8 'docs'9 'docs'
9 'dark'10 'dark'
10 'layout'11 'layout'
11 'size'12 'size'
12 'tips'13 'tips'
14 'autoInstall'
13 ]15 ]
14 16
15 INTERNAL_KEYS = [17 INTERNAL_KEYS = [
@@ -19,6 +21,8 @@ class app.Settings · 3 unmodified lines
19 'news'21 'news'
20 ]22 ]
21 23
24 LAYOUTS: ['_max-width', '_sidebar-hidden', '_native-scrollbars']
25
22 @defaults:26 @defaults:
23 count: 027 count: 0
24 hideDisabled: false28 hideDisabled: false
@@ -26,6 +30,7 @@ class app.Settings · 1 unmodified line
26 news: 030 news: 0
27 manualUpdate: false31 manualUpdate: false
28 schema: 132 schema: 1
33 analyticsConsent: false
29 34
30 constructor: ->35 constructor: ->
31 @store = new CookieStore36 @store = new CookieStore
@@ -38,6 +43,7 @@ class app.Settings · 6 unmodified lines
38 set: (key, value) ->43 set: (key, value) ->
39 @store.set(key, value)44 @store.set(key, value)
40 delete @cache[key]45 delete @cache[key]
46 @toggleDark(value) if key == 'dark'
41 return47 return
42 48
43 del: (key) ->49 del: (key) ->
@@ -63,6 +69,8 @@ class app.Settings · 19 unmodified lines
63 return69 return
64 70
65 setLayout: (name, enable) ->71 setLayout: (name, enable) ->
72 @toggleLayout(name, enable)
73
66 layout = (@store.get('layout') || '').split(' ')74 layout = (@store.get('layout') || '').split(' ')
67 $.arrayDelete(layout, '')75 $.arrayDelete(layout, '')
68 76
@@ -104,3 +112,28 @@ class app.Settings · 35 unmodified lines
104 @store.reset()112 @store.reset()
105 @cache = {}113 @cache = {}
106 return114 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
assets/javascripts/app/update_checker.coffee+3−3
@@ -3,13 +3,13 @@ class app.UpdateChecker · 2 unmodified lines
3 @lastCheck = Date.now()3 @lastCheck = Date.now()
4 4
5 $.on window, 'focus', @onFocus5 $.on window, 'focus', @onFocus
6 app.appCache.on 'updateready', @onUpdateReady if app.appCache6 app.serviceWorker?.on 'updateready', @onUpdateReady
7 7
8 setTimeout @checkDocs, 08 setTimeout @checkDocs, 0
9 9
10 check: ->10 check: ->
11 if app.appCache11 if app.serviceWorker
12 app.appCache.update()12 app.serviceWorker.update()
13 else13 else
14 ajax14 ajax
15 url: $('script[src*="application"]').getAttribute('src')15 url: $('script[src*="application"]').getAttribute('src')
assets/javascripts/lib/page.coffee+17−1
@@ -199,5 +199,21 @@ page.track = (fn) -> · 198 unmodified lines
199 return199 return
200 200
201track = ->201track = ->
202 tracker.call() for tracker in trackers202 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 return219 return
assets/javascripts/lib/util.coffee+4−0
@@ -352,6 +352,10 @@ isIE = null · 351 unmodified lines
352$.isIE = ->352$.isIE = ->
353 isIE ?= navigator.userAgent?.indexOf('MSIE') >= 0 || navigator.userAgent?.indexOf('rv:11.0') >= 0353 isIE ?= navigator.userAgent?.indexOf('MSIE') >= 0 || navigator.userAgent?.indexOf('rv:11.0') >= 0
354 354
355isChromeForAndroid = null
356$.isChromeForAndroid = ->
357 isChromeForAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 && /Chrome\/([.0-9])+ Mobile/.test(navigator.userAgent)
358
355isAndroid = null359isAndroid = null
356$.isAndroid = ->360$.isAndroid = ->
357 isAndroid ?= navigator.userAgent?.indexOf('Android') >= 0361 isAndroid ?= navigator.userAgent?.indexOf('Android') >= 0
assets/javascripts/models/doc.coffee+3−1
@@ -142,4 +142,6 @@ class app.models.Doc extends app.Model · 141 unmodified lines
142 return142 return
143 143
144 isOutdated: (status) ->144 isOutdated: (status) ->
145 status and status.installed and @mtime isnt status.mtime145 return false if not status
146 isInstalled = status.installed or app.settings.get('autoInstall')
147 isInstalled and @mtime isnt status.mtime
assets/javascripts/news.json+4−0
@@ -1,4 +1,8 @@
1[1[
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 ],
2 [6 [
3 "2018-09-23",7 "2018-09-23",
4 "New documentations: <a href=\"/puppeteer/\">Puppeteer</a> and <a href=\"/handlebars/\">Handlebars.js</a>"8 "New documentations: <a href=\"/puppeteer/\">Puppeteer</a> and <a href=\"/handlebars/\">Handlebars.js</a>"
assets/javascripts/templates/error_tmpl.coffee+5−5
@@ -12,8 +12,8 @@ app.templates.notFoundPage = -> · 11 unmodified lines
12 12
13app.templates.pageLoadError = ->13app.templates.pageLoadError = ->
14 error """ The page failed to load. """,14 error """ The page failed to load. """,
15 """ It may be missing from the server (try reloading the app) or you could be offline.<br>15 """ It may be missing from the server (try reloading the app) or you could be offline (try <a href="/offline">installing the documentation for offline usage</a> when online again).<br>
16 If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """,16 If you're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """,
17 """ #{back} &middot; <a href="/##{location.pathname}" target="_top" class="_error-link">Reload</a>17 """ #{back} &middot; <a href="/##{location.pathname}" target="_top" class="_error-link">Reload</a>
18 &middot; <a href="#" class="_error-link" data-retry>Retry</a> """18 &middot; <a href="#" class="_error-link" data-retry>Retry</a> """
19 19
@@ -57,9 +57,9 @@ app.templates.unsupportedBrowser = """ · 37 unmodified lines
57 <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:57 <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:
58 <ul class="_fail-list">58 <ul class="_fail-list">
59 <li>Recent versions of Firefox, Chrome, or Opera59 <li>Recent versions of Firefox, Chrome, or Opera
60 <li>Safari 9.1+60 <li>Safari 11.1+
61 <li>Edge 16+61 <li>Edge 17+
62 <li>iOS 10+62 <li>iOS 11.3+
63 </ul>63 </ul>
64 <p class="_fail-text">64 <p class="_fail-text">
65 If you're unable to upgrade, we apologize.65 If you're unable to upgrade, we apologize.
assets/javascripts/templates/notif_tmpl.coffee+6−0
@@ -68,3 +68,9 @@ app.templates.notifShare = -> · 67 unmodified lines
68app.templates.notifUpdateDocs = ->68app.templates.notifUpdateDocs = ->
69 textNotif """ Documentation updates available. """,69 textNotif """ Documentation updates available. """,
70 """ <a href="/offline">Install them</a> as soon as possible to avoid broken pages. """70 """ <a href="/offline">Install them</a> as soon as possible to avoid broken pages. """
71
72app.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> """
assets/javascripts/templates/pages/about_tmpl.coffee+2−1
@@ -73,7 +73,8 @@ app.templates.aboutPage = -> """ · 72 unmodified lines
73 <ul>73 <ul>
74 <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a> ("We").74 <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a> ("We").
75 <li>We do not collect personal information through the app.75 <li>We do not collect personal information through the app.
76 <li>We use Google Analytics, Gauges and Sentry to collect anonymous traffic information and improve the app.76 <li>We use Google Analytics and Gauges to collect anonymous traffic information if you have given consent to this. You can change your decision in the <a href="/settings">settings</a>.
77 <li>We use Sentry to collect crash data and improve the app.
77 <li>The app uses cookies to store user preferences.78 <li>The app uses cookies to store user preferences.
78 <li>By using the app, you signify your acceptance of this policy. If you do not agree to this policy, please do not use the app.79 <li>By using the app, you signify your acceptance of this policy. If you do not agree to this policy, please do not use the app.
79 <li>If you have any questions regarding privacy, please email <a href="mailto:privacy@freecodecamp.org">privacy@freecodecamp.org</a>.80 <li>If you have any questions regarding privacy, please email <a href="mailto:privacy@freecodecamp.org">privacy@freecodecamp.org</a>.
assets/javascripts/templates/pages/offline_tmpl.coffee+9−4
@@ -25,8 +25,8 @@ app.templates.offlinePage = (docs) -> """ · 24 unmodified lines
25 <h2 class="_block-heading">Questions & Answers</h2>25 <h2 class="_block-heading">Questions & Answers</h2>
26 <dl>26 <dl>
27 <dt>How does this work?27 <dt>How does this work?
28 <dd>Each page is cached as a key-value pair in <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> (downloaded from a single file).<br>28 <dd>Each page is cached as a key-value pair in <a href="https://devdocs.io/dom/indexeddb_api">IndexedDB</a> (downloaded from a single file).<br>
29 The app also uses <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache">AppCache</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a> to cache the assets and index files.29 The app also uses <a href="https://devdocs.io/dom/service_worker_api/using_service_workers">Service Workers</a> and <a href="https://devdocs.io/dom/web_storage_api">localStorage</a> to cache the assets and index files.
30 <dt>Can I close the tab/browser?30 <dt>Can I close the tab/browser?
31 <dd>#{canICloseTheTab()}31 <dd>#{canICloseTheTab()}
32 <dt>What if I don't update a documentation?32 <dt>What if I don't update a documentation?
@@ -41,10 +41,15 @@ app.templates.offlinePage = (docs) -> """ · 8 unmodified lines
41"""41"""
42 42
43canICloseTheTab = ->43canICloseTheTab = ->
44 if app.AppCache.isEnabled()44 if app.ServiceWorker.isEnabled()
45 """ Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). """45 """ Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). """
46 else46 else
47 """ No. AppCache isn't available in your browser (or is disabled), so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br>47 reason = "aren't available in your browser (or are disabled)"
48
49 if app.config.env != 'production'
50 reason = "are disabled in your development instance of DevDocs (enable them by setting the <code>ENABLE_SERVICE_WORKER</code> environment variable to <code>true</code>)"
51
52 """ No. Service Workers #{reason}, so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br>
48 The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """53 The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """
49 54
50app.templates.offlineDoc = (doc, status) ->55app.templates.offlineDoc = (doc, status) ->
assets/javascripts/templates/pages/root_tmpl.coffee.erb+2−2
@@ -8,7 +8,7 @@ app.templates.intro = """ · 7 unmodified lines
8 <p>Thanks for downloading DevDocs. Here are a few things you should know:8 <p>Thanks for downloading DevDocs. Here are a few things you should know:
9 <ol class="_intro-list">9 <ol class="_intro-list">
10 <li>Your local version of DevDocs won't self-update. Unless you're modifying the code,10 <li>Your local version of DevDocs won't self-update. Unless you're modifying the code,
11 I&nbsp;recommend using the hosted version at <a href="https://devdocs.io">devdocs.io</a>.11 we&nbsp;recommend using the hosted version at <a href="https://devdocs.io">devdocs.io</a>.
12 <li>Run <code>thor docs:list</code> to see all available documentations.12 <li>Run <code>thor docs:list</code> to see all available documentations.
13 <li>Run <code>thor docs:download &lt;name&gt;</code> to download documentations.13 <li>Run <code>thor docs:download &lt;name&gt;</code> to download documentations.
14 <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations.14 <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations.
@@ -39,7 +39,7 @@ app.templates.intro = """ · 24 unmodified lines
39 <li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed on <a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome</a>.39 <li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed on <a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome</a>.
40 <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.40 <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.
41 <li>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>.41 <li>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>.
42 <iframe class="_github-btn" src="//ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe>42 <object data="https://img.shields.io/github/stars/freeCodeCamp/devdocs.svg?style=social" type="image/svg+xml" aria-hidden="true"></object>
43 <li>And if you're new to coding, check out <a href="https://www.freecodecamp.org/">freeCodeCamp's open source curriculum</a>.43 <li>And if you're new to coding, check out <a href="https://www.freecodecamp.org/">freeCodeCamp's open source curriculum</a>.
44 </ol>44 </ol>
45 <p>Happy coding!45 <p>Happy coding!
assets/javascripts/templates/pages/settings_tmpl.coffee+8−0
@@ -15,6 +15,14 @@ app.templates.settingsPage = (settings) -> """ · 14 unmodified lines
15 <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar15 <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar
16 <small>Tip: drag the edge of the sidebar to resize it.</small>16 <small>Tip: drag the edge of the sidebar to resize it.</small>
17 </label>17 </label>
18 <label class="_settings-label">
19 <input type="checkbox" form="settings" name="autoInstall" value="_auto-install"#{if settings.autoInstall then ' checked' else ''}>Automatically download documentation for offline use
20 <small>Only enable this when bandwidth isn't a concern to you.</small>
21 </label>
22 <label class="_settings-label">
23 <input type="checkbox" form="settings" name="analyticsConsent"#{if settings.analyticsConsent then ' checked' else ''}>Enable tracking cookies
24 <small>With this checked, we enable Google Analytics and Gauges to collect anonymous traffic information.</small>
25 </label>
18 </div>26 </div>
19 </div>27 </div>
20 28
assets/javascripts/tracking.js+27−23
@@ -1,28 +1,32 @@
1try {1try {
2 if (app.config.env == 'production') {2 if (app.config.env === 'production') {
3 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){3 if (Cookies.get('analyticsConsent') === '1') {
4 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),4 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
5 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)5 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
6 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');6 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
7 ga('create', 'UA-5544833-12', 'devdocs.io');7 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
8 page.track(function() {8 ga('create', 'UA-5544833-12', 'devdocs.io');
9 ga('send', 'pageview', {9 page.track(function() {
10 page: location.pathname + location.search + location.hash,10 ga('send', 'pageview', {
11 dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version11 page: location.pathname + location.search + location.hash,
12 dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version
13 });
12 });14 });
13 });
14 15
15 page.track(function() {16 page.track(function() {
16 if (window._gauges)17 if (window._gauges)
17 _gauges.push(['track']);18 _gauges.push(['track']);
18 else19 else
19 (function() {20 (function() {
20 var _gauges=_gauges||[];!function(){var a=document.createElement("script");21 var _gauges=_gauges||[];!function(){var a=document.createElement("script");
21 a.type="text/javascript",a.async=!0,a.id="gauges-tracker",22 a.type="text/javascript",a.async=!0,a.id="gauges-tracker",
22 a.setAttribute("data-site-id","51c15f82613f5d7819000067"),23 a.setAttribute("data-site-id","51c15f82613f5d7819000067"),
23 a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];24 a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];
24 b.parentNode.insertBefore(a,b)}();25 b.parentNode.insertBefore(a,b)}();
25 })();26 })();
26 });27 });
28 } else {
29 resetAnalytics();
30 }
27 }31 }
28} catch(e) { }32} catch(e) { }
assets/javascripts/views/content/entry_page.coffee+1−1
@@ -123,7 +123,7 @@ class app.views.EntryPage extends app.View · 122 unmodified lines
123 @render @tmpl('pageLoadError')123 @render @tmpl('pageLoadError')
124 @resetClass()124 @resetClass()
125 @addClass @constructor.errorClass125 @addClass @constructor.errorClass
126 app.appCache?.update()126 app.serviceWorker?.update()
127 return127 return
128 128
129 cache: ->129 cache: ->
assets/javascripts/views/content/settings_page.coffee+10−11
@@ -1,7 +1,4 @@
1class app.views.SettingsPage extends app.View1class app.views.SettingsPage extends app.View
2 LAYOUTS = ['_max-width', '_sidebar-hidden', '_native-scrollbars']
3 SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden'
4
5 @className: '_static'2 @className: '_static'
6 3
7 @events:4 @events:
@@ -17,31 +14,31 @@ class app.views.SettingsPage extends app.View · 9 unmodified lines
17 settings.dark = app.settings.get('dark')14 settings.dark = app.settings.get('dark')
18 settings.smoothScroll = !app.settings.get('fastScroll')15 settings.smoothScroll = !app.settings.get('fastScroll')
19 settings.arrowScroll = app.settings.get('arrowScroll')16 settings.arrowScroll = app.settings.get('arrowScroll')
20 settings[layout] = app.settings.hasLayout(layout) for layout in LAYOUTS17 settings.autoInstall = app.settings.get('autoInstall')
18 settings.analyticsConsent = app.settings.get('analyticsConsent')
19 settings[layout] = app.settings.hasLayout(layout) for layout in app.settings.LAYOUTS
21 settings20 settings
22 21
23 getTitle: ->22 getTitle: ->
24 'Preferences'23 'Preferences'
25 24
26 toggleDark: (enable) ->25 toggleDark: (enable) ->
27 html = document.documentElement
28 html.classList.toggle('_theme-default')
29 html.classList.toggle('_theme-dark')
30 app.settings.set('dark', !!enable)26 app.settings.set('dark', !!enable)
31 app.appCache?.updateInBackground()
32 return27 return
33 28
34 toggleLayout: (layout, enable) ->29 toggleLayout: (layout, enable) ->
35 document.body.classList[if enable then 'add' else 'remove'](layout) unless layout is SIDEBAR_HIDDEN_LAYOUT
36 document.body.classList[if $.overlayScrollbarsEnabled() then 'add' else 'remove']('_overlay-scrollbars')
37 app.settings.setLayout(layout, enable)30 app.settings.setLayout(layout, enable)
38 app.appCache?.updateInBackground()
39 return31 return
40 32
41 toggleSmoothScroll: (enable) ->33 toggleSmoothScroll: (enable) ->
42 app.settings.set('fastScroll', !enable)34 app.settings.set('fastScroll', !enable)
43 return35 return
44 36
37 toggleAnalyticsConsent: (enable) ->
38 app.settings.set('analyticsConsent', if enable then '1' else '0')
39 resetAnalytics() unless enable
40 return
41
45 toggle: (name, enable) ->42 toggle: (name, enable) ->
46 app.settings.set(name, enable)43 app.settings.set(name, enable)
47 return44 return
@@ -85,6 +82,8 @@ class app.views.SettingsPage extends app.View · 37 unmodified lines
85 @toggleSmoothScroll input.checked82 @toggleSmoothScroll input.checked
86 when 'import'83 when 'import'
87 @import input.files[0], input84 @import input.files[0], input
85 when 'analyticsConsent'
86 @toggleAnalyticsConsent input.checked
88 else87 else
89 @toggle input.name, input.checked88 @toggle input.name, input.checked
90 return89 return
assets/javascripts/views/layout/document.coffee+7−5
@@ -75,9 +75,11 @@ class app.views.Document extends app.View · 74 unmodified lines
75 return unless target.hasAttribute('data-behavior')75 return unless target.hasAttribute('data-behavior')
76 $.stopEvent(event)76 $.stopEvent(event)
77 switch target.getAttribute('data-behavior')77 switch target.getAttribute('data-behavior')
78 when 'back' then history.back()78 when 'back' then history.back()
79 when 'reload' then window.location.reload()79 when 'reload' then window.location.reload()
80 when 'reboot' then app.reboot()80 when 'reboot' then app.reboot()
81 when 'hard-reload' then app.reload()81 when 'hard-reload' then app.reload()
82 when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?')82 when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?')
83 when 'accept-analytics' then Cookies.set('analyticsConsent', '1') && app.reboot()
84 when 'decline-analytics' then Cookies.set('analyticsConsent', '0') && app.reboot()
83 return85 return
assets/javascripts/views/layout/resizer.coffee+2−9
@@ -11,9 +11,6 @@ class app.views.Resizer extends app.View · 10 unmodified lines
11 init: ->11 init: ->
12 @el.setAttribute('draggable', 'true')12 @el.setAttribute('draggable', 'true')
13 @appendTo $('._app')13 @appendTo $('._app')
14
15 @style = $('style[data-resizer]')
16 @size = @style.getAttribute('data-size')
17 return14 return
18 15
19 MIN = 26016 MIN = 260
@@ -24,15 +21,11 @@ class app.views.Resizer extends app.View · 4 unmodified lines
24 return unless value > 021 return unless value > 0
25 value = Math.min(Math.max(Math.round(value), MIN), MAX)22 value = Math.min(Math.max(Math.round(value), MIN), MAX)
26 newSize = "#{value}px"23 newSize = "#{value}px"
27 @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize)24 document.documentElement.style.setProperty('--sidebarWidth', newSize)
28 @size = newSize25 app.settings.setSize(value) if save
29 if save
30 app.settings.setSize(value)
31 app.appCache?.updateInBackground()
32 return26 return
33 27
34 onDragStart: (event) =>28 onDragStart: (event) =>
35 @style.removeAttribute('disabled')
36 event.dataTransfer.effectAllowed = 'link'29 event.dataTransfer.effectAllowed = 'link'
37 event.dataTransfer.setData('Text', '')30 event.dataTransfer.setData('Text', '')
38 $.on(window, 'dragover', @onDrag)31 $.on(window, 'dragover', @onDrag)
assets/javascripts/views/layout/settings.coffee+1−9
@@ -25,7 +25,6 @@ class app.views.Settings extends app.View · 24 unmodified lines
25 if super25 if super
26 @render()26 @render()
27 document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT)27 document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT)
28 app.appCache?.on 'progress', @onAppCacheProgress
29 return28 return
30 29
31 deactivate: ->30 deactivate: ->
@@ -33,7 +32,6 @@ class app.views.Settings extends app.View · 1 unmodified line
33 @resetClass()32 @resetClass()
34 @docPicker.detach()33 @docPicker.detach()
35 document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)34 document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)
36 app.appCache?.off 'progress', @onAppCacheProgress
37 return35 return
38 36
39 render: ->37 render: ->
@@ -52,7 +50,7 @@ class app.views.Settings extends app.View · 12 unmodified lines
52 docs = @docPicker.getSelectedDocs()50 docs = @docPicker.getSelectedDocs()
53 app.settings.setDocs(docs)51 app.settings.setDocs(docs)
54 52
55 @saveBtn.textContent = if app.appCache then 'Downloading\u2026' else 'Saving\u2026'53 @saveBtn.textContent = 'Saving\u2026'
56 disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1)54 disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1)
57 disabledDocs.uninstall ->55 disabledDocs.uninstall ->
58 app.db.migrate()56 app.db.migrate()
@@ -83,9 +81,3 @@ class app.views.Settings extends app.View · 24 unmodified lines
83 $.stopEvent(event)81 $.stopEvent(event)
84 app.router.show '/'82 app.router.show '/'
85 return83 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
assets/javascripts/views/search/search.coffee+8−0
@@ -30,6 +30,9 @@ class app.views.Search extends app.View · 29 unmodified lines
30 .on 'results', @onResults30 .on 'results', @onResults
31 .on 'end', @onEnd31 .on 'end', @onEnd
32 32
33 @scope
34 .on 'change', @onScopeChange
35
33 app.on 'ready', @onReady36 app.on 'ready', @onReady
34 $.on window, 'hashchange', @searchUrl37 $.on window, 'hashchange', @searchUrl
35 $.on window, 'focus', @onWindowFocus38 $.on window, 'focus', @onWindowFocus
@@ -138,6 +141,11 @@ class app.views.Search extends app.View · 102 unmodified lines
138 $.stopEvent(event)141 $.stopEvent(event)
139 return142 return
140 143
144 onScopeChange: =>
145 @value = ''
146 @onInput()
147 return
148
141 afterRoute: (name, context) =>149 afterRoute: (name, context) =>
142 return if app.shortcuts.eventInProgress?.name is 'escape'150 return if app.shortcuts.eventInProgress?.name is 'escape'
143 @reset(true) if not context.init and app.router.isIndex()151 @reset(true) if not context.init and app.router.isIndex()
assets/javascripts/views/search/search_scope.coffee+23−5
@@ -6,7 +6,9 @@ class app.views.SearchScope extends app.View · 5 unmodified lines
6 tag: '._search-tag'6 tag: '._search-tag'
7 7
8 @events:8 @events:
9 click: 'onClick'
9 keydown: 'onKeydown'10 keydown: 'onKeydown'
11 textInput: 'onTextInput'
10 12
11 @routes:13 @routes:
12 after: 'afterRoute'14 after: 'afterRoute'
@@ -87,17 +89,33 @@ class app.views.SearchScope extends app.View · 74 unmodified lines
87 @trigger 'change', null, previousDoc89 @trigger 'change', null, previousDoc
88 return90 return
89 91
92 doScopeSearch: (event) =>
93 @search @input.value[0...@input.selectionStart]
94 $.stopEvent(event) if @doc
95 return
96
97 onClick: (event) =>
98 if event.target is @tag
99 @reset()
100 $.stopEvent(event)
101 return
102
90 onKeydown: (event) =>103 onKeydown: (event) =>
91 if event.which is 8 # backspace104 if event.which is 8 # backspace
92 if @doc and not @input.value105 if @doc and @input.selectionEnd is 0
93 $.stopEvent(event)
94 @reset()106 @reset()
95 else if not @doc and @input.value107 $.stopEvent(event)
108 else if not @doc and @input.value and not $.isChromeForAndroid()
96 return if event.ctrlKey or event.metaKey or event.altKey or event.shiftKey109 return if event.ctrlKey or event.metaKey or event.altKey or event.shiftKey
97 if event.which is 9 or # tab110 if event.which is 9 or # tab
98 (event.which is 32 and app.isMobile()) # space111 (event.which is 32 and app.isMobile()) # space
99 @search @input.value[0...@input.selectionStart]112 @doScopeSearch(event)
100 $.stopEvent(event) if @doc113 return
114
115 onTextInput: (event) =>
116 return unless $.isChromeForAndroid()
117 if not @doc and @input.value and event.data == ' '
118 @doScopeSearch(event)
101 return119 return
102 120
103 extractHashValue: ->121 extractHashValue: ->
assets/javascripts/views/sidebar/sidebar.coffee+1−1
@@ -28,7 +28,7 @@ class app.views.Sidebar extends app.View · 27 unmodified lines
28 28
29 app.on 'ready', @onReady29 app.on 'ready', @onReady
30 30
31 $.on document.documentElement, 'mouseleave', (event) => @display() if event.clientX < 1031 $.on document.documentElement, 'mouseleave', (event) => @display() unless event.clientX <= 0
32 $.on document.documentElement, 'mouseenter', => @resetDisplay(forceNoHover: false)32 $.on document.documentElement, 'mouseenter', => @resetDisplay(forceNoHover: false)
33 return33 return
34 34
assets/stylesheets/application.css.scss+3−4
@@ -1,7 +1,6 @@
1//= depend_on docs-1.png1//= depend_on sprites/docs.png
2//= depend_on docs-1@2x.png2//= depend_on sprites/docs@2x.png
3//= depend_on docs-2.png3//= depend_on sprites/docs.json
4//= depend_on docs-2@2x.png
5 4
6/*!5/*!
7 * Copyright 2013-2019 Thibaut Courouble and other contributors6 * Copyright 2013-2019 Thibaut Courouble and other contributors
assets/stylesheets/components/_header.scss+1−0
@@ -215,5 +215,6 @@ · 214 unmodified lines
215 color: var(--textColorLight);215 color: var(--textColorLight);
216 background: var(--searchTagBackground);216 background: var(--searchTagBackground);
217 border-radius: 2px;217 border-radius: 2px;
218 cursor: pointer;
218 @extend %truncate-text;219 @extend %truncate-text;
219}220}
assets/stylesheets/components/_notif.scss+4−0
@@ -134,3 +134,7 @@ · 133 unmodified lines
134 134
135 ._notif-info { color: var(--textColorLight); }135 ._notif-info { color: var(--textColorLight); }
136}136}
137
138._notif-right {
139 float: right;
140}
assets/stylesheets/global/_icons.scss+0−182
@@ -1,182 +0,0 @@
1%svg-icon {
2 display: inline-block;
3 vertical-align: top;
4 width: 1rem;
5 height: 1rem;
6 pointer-events: none;
7 fill: currentColor;
8}
9
10%doc-icon {
11 content: '';
12 display: block;
13 width: 1rem;
14 height: 1rem;
15 background-image: image-url('docs-1.png');
16 background-size: 10rem 10rem;
17}
18
19%doc-icon-2 { background-image: image-url('docs-2.png') !important; }
20
21@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {
22 %doc-icon { background-image: image-url('docs-1@2x.png'); }
23 %doc-icon-2 { background-image: image-url('docs-2@2x.png') !important; }
24}
25
26html._theme-dark {
27 %darkIconFix {
28 filter: invert(100%) grayscale(100%);
29 -webkit-filter: invert(100%) grayscale(100%);
30 }
31}
32
33._icon-jest:before { background-position: 0 0; }
34._icon-liquid:before { background-position: -1rem 0; }
35._icon-openjdk:before { background-position: -2rem 0; }
36._icon-codeceptjs:before { background-position: -3rem 0; }
37._icon-codeception:before { background-position: -4rem 0; }
38._icon-sqlite:before { background-position: -5rem 0; @extend %darkIconFix !optional; }
39._icon-async:before { background-position: -6rem 0; @extend %darkIconFix !optional; }
40._icon-http:before { background-position: -7rem 0; @extend %darkIconFix !optional; }
41._icon-jquery:before { background-position: -8rem 0; @extend %darkIconFix !optional; }
42._icon-underscore:before { background-position: -9rem 0; @extend %darkIconFix !optional; }
43._icon-html:before { background-position: 0 -1rem; }
44._icon-css:before { background-position: -1rem -1rem; }
45._icon-dom:before { background-position: -2rem -1rem; }
46._icon-dom_events:before { background-position: -3rem -1rem; }
47._icon-javascript:before { background-position: -4rem -1rem; }
48._icon-backbone:before { background-position: -5rem -1rem; @extend %darkIconFix !optional; }
49._icon-node:before,
50._icon-node_lts:before { background-position: -6rem -1rem; }
51._icon-sass:before { background-position: -7rem -1rem; }
52._icon-less:before { background-position: -8rem -1rem; }
53._icon-angularjs:before { background-position: -9rem -1rem; }
54._icon-coffeescript:before { background-position: 0 -2rem; @extend %darkIconFix !optional; }
55._icon-ember:before { background-position: -1rem -2rem; }
56._icon-yarn:before { background-position: -2rem -2rem; }
57._icon-immutable:before { background-position: -3rem -2rem; @extend %darkIconFix !optional; }
58._icon-jqueryui:before { background-position: -4rem -2rem; }
59._icon-jquerymobile:before { background-position: -5rem -2rem; }
60._icon-lodash:before { background-position: -6rem -2rem; }
61._icon-php:before { background-position: -7rem -2rem; }
62._icon-ruby:before,
63._icon-minitest:before { background-position: -8rem -2rem; }
64._icon-rails:before { background-position: -9rem -2rem; }
65._icon-python:before,
66._icon-python2:before { background-position: 0 -3rem; }
67._icon-git:before { background-position: -1rem -3rem; }
68._icon-redis:before { background-position: -2rem -3rem; }
69._icon-postgresql:before { background-position: -3rem -3rem; }
70._icon-d3:before { background-position: -4rem -3rem; }
71._icon-knockout:before { background-position: -5rem -3rem; }
72._icon-moment:before { background-position: -6rem -3rem; @extend %darkIconFix !optional; }
73._icon-c:before { background-position: -7rem -3rem; }
74._icon-statsmodels:before { background-position: -8rem -3rem; }
75._icon-yii:before,
76._icon-yii1:before { background-position: -9rem -3rem; }
77._icon-cpp:before { background-position: 0 -4rem; }
78._icon-go:before { background-position: -1rem -4rem; }
79._icon-express:before { background-position: -2rem -4rem; }
80._icon-grunt:before { background-position: -3rem -4rem; }
81._icon-rust:before { background-position: -4rem -4rem; @extend %darkIconFix !optional; }
82._icon-laravel:before { background-position: -5rem -4rem; }
83._icon-haskell:before { background-position: -6rem -4rem; }
84._icon-requirejs:before { background-position: -7rem -4rem; }
85._icon-chai:before { background-position: -8rem -4rem; }
86._icon-sinon:before { background-position: -9rem -4rem; }
87._icon-cordova:before { background-position: 0 -5rem; }
88._icon-markdown:before { background-position: -1rem -5rem; @extend %darkIconFix !optional; }
89._icon-django:before { background-position: -2rem -5rem; }
90._icon-xslt_xpath:before { background-position: -3rem -5rem; }
91._icon-nginx:before,
92._icon-nginx_lua_module:before { background-position: -4rem -5rem; }
93._icon-svg:before { background-position: -5rem -5rem; }
94._icon-marionette:before { background-position: -6rem -5rem; }
95._icon-jsdoc:before,
96._icon-koa:before,
97._icon-graphite:before,
98._icon-mongoose:before { background-position: -7rem -5rem; }
99._icon-phpunit:before { background-position: -8rem -5rem; }
100._icon-nokogiri:before { background-position: -9rem -5rem; @extend %darkIconFix !optional; }
101._icon-rethinkdb:before { background-position: 0 -6rem; }
102._icon-react:before { background-position: -1rem -6rem; }
103._icon-socketio:before { background-position: -2rem -6rem; }
104._icon-modernizr:before { background-position: -3rem -6rem; }
105._icon-bower:before { background-position: -4rem -6rem; }
106._icon-fish:before { background-position: -5rem -6rem; @extend %darkIconFix !optional; }
107._icon-scikit_image:before { background-position: -6rem -6rem; }
108._icon-twig:before { background-position: -7rem -6rem; }
109._icon-pandas:before { background-position: -8rem -6rem; }
110._icon-scikit_learn:before { background-position: -9rem -6rem; }
111._icon-bottle:before { background-position: 0 -7rem; }
112._icon-docker:before { background-position: -1rem -7rem; }
113._icon-cakephp:before { background-position: -2rem -7rem; }
114._icon-lua:before { background-position: -3rem -7rem; @extend %darkIconFix !optional; }
115._icon-clojure:before { background-position: -4rem -7rem; }
116._icon-symfony:before { background-position: -5rem -7rem; }
117._icon-mocha:before { background-position: -6rem -7rem; }
118._icon-meteor:before { background-position: -7rem -7rem; @extend %darkIconFix !optional; }
119._icon-npm:before { background-position: -8rem -7rem; }
120._icon-apache_http_server:before { background-position: -9rem -7rem; }
121._icon-drupal:before { background-position: 0 -8rem; }
122._icon-webpack:before { background-position: -1rem -8rem; }
123._icon-phaser:before { background-position: -2rem -8rem; }
124._icon-vue:before { background-position: -3rem -8rem; }
125._icon-opentsdb:before { background-position: -4rem -8rem; }
126._icon-q:before { background-position: -5rem -8rem; }
127._icon-crystal:before { background-position: -6rem -8rem; @extend %darkIconFix !optional; }
128._icon-julia:before { background-position: -7rem -8rem; @extend %darkIconFix !optional; }
129._icon-redux:before { background-position: -8rem -8rem; @extend %darkIconFix !optional; }
130._icon-bootstrap:before { background-position: -9rem -8rem; }
131._icon-react_native:before { background-position: 0 -9rem; }
132._icon-phalcon:before { background-position: -1rem -9rem; }
133._icon-matplotlib:before { background-position: -2rem -9rem; }
134._icon-cmake:before { background-position: -3rem -9rem; }
135._icon-elixir:before { background-position: -4rem -9rem; @extend %darkIconFix !optional; }
136._icon-vagrant:before { background-position: -5rem -9rem; }
137._icon-dojo:before { background-position: -6rem -9rem; }
138._icon-flow:before { background-position: -7rem -9rem; }
139._icon-relay:before { background-position: -8rem -9rem; }
140._icon-phoenix:before { background-position: -9rem -9rem; }
141
142._icon-tcl_tk:before { background-position: 0 0; @extend %doc-icon-2; }
143._icon-erlang:before { background-position: -1rem 0; @extend %doc-icon-2; }
144._icon-chef:before { background-position: -2rem 0; @extend %doc-icon-2; }
145._icon-ramda:before { background-position: -3rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
146._icon-codeigniter:before { background-position: -4rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
147._icon-influxdata:before { background-position: -5rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
148._icon-tensorflow:before { background-position: -6rem 0; @extend %doc-icon-2; }
149._icon-haxe:before { background-position: -7rem 0; @extend %doc-icon-2; }
150._icon-ansible:before { background-position: -8rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
151._icon-typescript:before { background-position: -9rem 0; @extend %doc-icon-2; }
152._icon-browser_support_tables:before { background-position: 0rem -1rem; @extend %doc-icon-2; }
153._icon-gnu_fortran:before { background-position: -1rem -1rem; @extend %doc-icon-2; }
154._icon-gcc:before { background-position: -2rem -1rem; @extend %doc-icon-2; }
155._icon-perl:before { background-position: -3rem -1rem; @extend %doc-icon-2; }
156._icon-apache_pig:before { background-position: -4rem -1rem; @extend %doc-icon-2; }
157._icon-numpy:before { background-position: -5rem -1rem; @extend %doc-icon-2; }
158._icon-kotlin:before { background-position: -6rem -1rem; @extend %doc-icon-2; }
159._icon-padrino:before { background-position: -7rem -1rem; @extend %doc-icon-2; }
160._icon-angular:before { background-position: -8rem -1rem; @extend %doc-icon-2; }
161._icon-love:before { background-position: -9rem -1rem; @extend %doc-icon-2; }
162._icon-jasmine:before { background-position: 0 -2rem; @extend %doc-icon-2; }
163._icon-pug:before { background-position: -1rem -2rem; @extend %doc-icon-2; }
164._icon-electron:before { background-position: -2rem -2rem; @extend %doc-icon-2; }
165._icon-falcon:before { background-position: -3rem -2rem; @extend %doc-icon-2; }
166._icon-godot:before { background-position: -4rem -2rem; @extend %doc-icon-2; }
167._icon-nim:before { background-position: -5rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; }
168._icon-vulkan:before { background-position: -6rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; }
169._icon-d:before { background-position: -7rem -2rem; @extend %doc-icon-2; }
170._icon-bluebird:before { background-position: -8rem -2rem; @extend %doc-icon-2; }
171._icon-eslint:before { background-position: -9rem -2rem; @extend %doc-icon-2; }
172._icon-homebrew:before { background-position: 0 -3rem; @extend %doc-icon-2; }
173._icon-jekyll:before { background-position: -1rem -3rem; @extend %doc-icon-2; }
174._icon-babel:before { background-position: -2rem -3rem; @extend %doc-icon-2; }
175._icon-leaflet:before { background-position: -3rem -3rem; @extend %doc-icon-2; }
176._icon-terraform:before { background-position: -4rem -3rem; @extend %doc-icon-2; }
177._icon-pygame:before { background-position: -5rem -3rem; @extend %doc-icon-2; }
178._icon-bash:before { background-position: -6rem -3rem; @extend %doc-icon-2; }
179._icon-dart:before { background-position: -7rem -3rem; @extend %doc-icon-2; }
180._icon-qt:before { background-position: -8rem -3rem; @extend %doc-icon-2; }
181._icon-puppeteer:before { background-position: -9rem -3rem; @extend %doc-icon-2; }
182._icon-handlebars:before { background-position: 0 -4rem; @extend %doc-icon-2; @extend %darkIconFix !optional; }
assets/stylesheets/global/_icons.scss.erb+43−0
@@ -0,0 +1,43 @@
1<% manifest = JSON.parse(File.read('assets/images/sprites/docs.json')) %>
2
3%svg-icon {
4 display: inline-block;
5 vertical-align: top;
6 width: 1rem;
7 height: 1rem;
8 pointer-events: none;
9 fill: currentColor;
10}
11
12%doc-icon {
13 content: '';
14 display: block;
15 width: 1rem;
16 height: 1rem;
17 background-image: image-url('sprites/docs.png');
18 background-size: <%= manifest['icons_per_row'] %>rem <%= manifest['icons_per_row'] %>rem;
19}
20
21@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {
22 %doc-icon { background-image: image-url('sprites/docs@2x.png'); }
23}
24
25html._theme-dark {
26 %darkIconFix {
27 filter: invert(100%) grayscale(100%);
28 -webkit-filter: invert(100%) grayscale(100%);
29 }
30}
31
32<%=
33 items = []
34
35 manifest['items'].each do |item|
36 rules = []
37 rules << "background-position: -#{item['col']}rem -#{item['row']}rem;"
38 rules << "@extend %darkIconFix !optional;" if item['dark_icon_fix']
39 items << "._icon-#{item['type']}:before { #{rules.join(' ')} }"
40 end
41
42 items.join('')
43 %>
docs/adding-docs.md+5−3
@@ -1,6 +1,6 @@
1Adding a documentation may look like a daunting task but once you get the hang of it, it's actually quite simple. Don't hesitate to ask for help [in Gitter](https://gitter.im/FreeCodeCamp/DevDocs) if you ever get stuck.1Adding a documentation may look like a daunting task but once you get the hang of it, it's actually quite simple. Don't hesitate to ask for help [in Gitter](https://gitter.im/FreeCodeCamp/DevDocs) if you ever get stuck.
2 2
3**Note:** please read the [contributing guidelines](https://github.com/Thibaut/devdocs/blob/master/.github/CONTRIBUTING.md) before submitting a new documentation.3**Note:** please read the [contributing guidelines](../.github/CONTRIBUTING.md) before submitting a new documentation.
4 4
51. Create a subclass of `Docs::UrlScraper` or `Docs::FileScraper` in the `lib/docs/scrapers/` directory. Its name should be the [PascalCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename (e.g. `my_doc` → `MyDoc`)51. Create a subclass of `Docs::UrlScraper` or `Docs::FileScraper` in the `lib/docs/scrapers/` directory. Its name should be the [PascalCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename (e.g. `my_doc` → `MyDoc`)
62. Add the appropriate class attributes and filter options (see the [Scraper Reference](./scraper-reference.md) page).62. Add the appropriate class attributes and filter options (see the [Scraper Reference](./scraper-reference.md) page).
@@ -13,9 +13,11 @@ Adding a documentation may look like a daunting task but once you get the hang o · 6 unmodified lines
136. Generate the full documentation using the `thor docs:generate [my_doc] --force` command. Additionally, you can use the `--verbose` option to see which files are being created/updated/deleted (useful to see what changed since the last run), and the `--debug` option to see which URLs are being requested and added to the queue (useful to pin down which page adds unwanted URLs to the queue).136. Generate the full documentation using the `thor docs:generate [my_doc] --force` command. Additionally, you can use the `--verbose` option to see which files are being created/updated/deleted (useful to see what changed since the last run), and the `--debug` option to see which URLs are being requested and added to the queue (useful to pin down which page adds unwanted URLs to the queue).
147. Start the server, open the app, enable the documentation, and see how everything plays out.147. Start the server, open the app, enable the documentation, and see how everything plays out.
158. Tweak the scraper/filters and repeat 5) and 6) until the pages and metadata are ok.158. Tweak the scraper/filters and repeat 5) and 6) until the pages and metadata are ok.
169. To customize the pages' styling, create an SCSS file in the `assets/stylesheets/pages/` directory and import it in both `application.css.scss` AND `application-dark.css.scss`. Both the file and CSS class should be named `_[type]` where [type] is equal to the scraper's `type` attribute (documentations with the same type share the same custom CSS and JS). _(Note: feel free to submit a pull request without custom CSS/JS)_169. To customize the pages' styling, create an SCSS file in the `assets/stylesheets/pages/` directory and import it in both `application.css.scss` AND `application-dark.css.scss`. Both the file and CSS class should be named `_[type]` where [type] is equal to the scraper's `type` attribute (documentations with the same type share the same custom CSS and JS). Setting the type to `simple` will apply the general styling rules in `assets/stylesheets/pages/_simple.scss`, which can be used for documentations where little to no CSS changes are needed.
1710. To add syntax highlighting or execute custom JavaScript on the pages, create a file in the `assets/javascripts/views/pages/` directory (take a look at the other files to see how it works).1710. To add syntax highlighting or execute custom JavaScript on the pages, create a file in the `assets/javascripts/views/pages/` directory (take a look at the other files to see how it works).
1811. Add the documentation's icon in the `public/icons/docs/[my_doc]/` directory, in both 16x16 and 32x32-pixels formats. It'll be added to the icon sprite after your pull request is merged.1811. Add the documentation's icon in the `public/icons/docs/[my_doc]/` directory, in both 16x16 and 32x32-pixels formats. The icon spritesheet is automatically generated when you (re)start your local DevDocs instance.
1912. Add the documentation's copyright details to the list in `assets/javascripts/templates/pages/about_tmpl.coffee`. This is the data shown in the table on the [about](https://devdocs.io/about) page, and is ordered alphabetically. Simply copying an existing item, placing it in the right slot and updating the values to match the new scraper will do the job.
2013. Ensure `thor updates:check [my_doc]` shows the correct latest version.
19 21
20If the documentation includes more than a few hundreds pages and is available for download, try to scrape it locally (e.g. using `FileScraper`). It'll make the development process much faster and avoids putting too much load on the source site. (It's not a problem if your scraper is coupled to your local setup, just explain how it works in your pull request.)22If the documentation includes more than a few hundreds pages and is available for download, try to scrape it locally (e.g. using `FileScraper`). It'll make the development process much faster and avoids putting too much load on the source site. (It's not a problem if your scraper is coupled to your local setup, just explain how it works in your pull request.)
21 23
docs/filter-reference.mdrenamed from docs/Filter-Reference.md+0−0

File renamed without changes.

docs/maintainers.md+7−7
@@ -4,7 +4,7 @@ This document is intended for [DevDocs maintainers](#list-of-maintainers). · 3 unmodified lines
4 4
5## Merging pull requests5## Merging pull requests
6 6
7- Unless the change is trivial or in an area that you are familiar with, PRs should be approved by at least two maintainers before being merged.7- PRs should be approved by at least one maintainer before being merged.
8 8
9- PRs that add or update documentations should always be merged locally, and the app deployed, before the merge is pushed to GitHub.9- PRs that add or update documentations should always be merged locally, and the app deployed, before the merge is pushed to GitHub.
10 10
@@ -21,8 +21,7 @@ The process for updating docs is as follow: · 10 unmodified lines
21- Commit the changes (protip: use the `thor docs:commit` command documented below).21- Commit the changes (protip: use the `thor docs:commit` command documented below).
22- Optional: do more updates.22- Optional: do more updates.
23- Run `thor docs:upload` (documented below).23- Run `thor docs:upload` (documented below).
24- [Deploy the app](#deploying-devdocs) and verify that everything works in production.24- Push to GitHub to [deploy the app](#deploying-devdocs) and verify that everything works in production.
25- Push to GitHub.
26- Run `thor docs:clean` (documented below).25- Run `thor docs:clean` (documented below).
27 26
28Note: changes to `public/docs/docs.json` should never be committed. This file reflects which documentations have been downloaded or generated locally, which is always none on a fresh `git clone`.27Note: changes to `public/docs/docs.json` should never be committed. This file reflects which documentations have been downloaded or generated locally, which is always none on a fresh `git clone`.
@@ -82,21 +81,22 @@ In addition to the [publicly-documented commands](https://github.com/freeCodeCam · 53 unmodified lines
82 81
83## Deploying DevDocs82## Deploying DevDocs
84 83
85Once docs have been uploaded via `thor docs:upload` (if applicable), deploying DevDocs is as simple as running `git push heroku master`. See [Heroku's documentation](https://devcenter.heroku.com/articles/git) for more information.84Once docs have been uploaded via `thor docs:upload` (if applicable), you can push to the DevDocs master branch (or merge the PR containing the updates). If the Travis build succeeds, the Heroku application will be deployed automatically.
86 85
87- If you're deploying documentation updates, verify that the documentations work properly once the deploy is done (you will need to reload [devdocs.io](https://devdocs.io/) a couple times for the application cache to update and the new version to load).86- If you're deploying documentation updates, verify that the documentations work properly once the deploy is done. Keep in mind that you'll need to wait a few seconds for the service worker to finish caching the new assets. You should see a "DevDocs has been updated" notification appear when the caching is done, after which you need to refresh the page to see the changes.
88- If you're deploying frontend changes, monitor [Sentry](https://sentry.io/devdocs/devdocs-js/) for new JS errors once the deploy is done.87- If you're deploying frontend changes, monitor [Sentry](https://sentry.io/devdocs/devdocs-js/) for new JS errors once the deploy is done.
89- If you're deploying server changes, monitor New Relic (accessible through [the Heroku dashboard](https://dashboard.heroku.com/apps/devdocs)) for Ruby exceptions and throughput or response time changes once the deploy is done.88- If you're deploying server changes, monitor New Relic (accessible through [the Heroku dashboard](https://dashboard.heroku.com/apps/devdocs)) for Ruby exceptions and throughput or response time changes once the deploy is done.
90 89
91If any issue arises, run `heroku rollback` to rollback to the previous of the app (this can also be done via Heroku's UI). Note that this will not revert changes made to documentation files that were uploaded via `thor docs:upload`. Try and fix the issue as quickly as possible, then re-deploy the app. Reach out to other maintainers if you need help.90If any issue arises, run `heroku rollback` to rollback to the previous version of the app (this can also be done via Heroku's UI). Note that this will not revert changes made to documentation files that were uploaded via `thor docs:upload`. Try and fix the issue as quickly as possible, then re-deploy the app. Reach out to other maintainers if you need help.
92 91
93If this is your first deploy, make sure another maintainer is around to assist. 92If this is your first deploy, make sure another maintainer is around to assist.
94 93
95## List of maintainers94## List of maintainers
96 95
97- [Beau Carnes](https://github.com/beaucarnes)
98- [Jed Fox](https://github.com/j-f1)96- [Jed Fox](https://github.com/j-f1)
99- [Jasper van Merle](https://github.com/jmerle)97- [Jasper van Merle](https://github.com/jmerle)
98- [Ahmad Abdolsaheb](https://github.com/ahmadabdolsaheb)
99- [Mrugesh Mohapatra](https://github.com/raisedadead)
100- [Thibaut Courouble](https://github.com/thibaut)100- [Thibaut Courouble](https://github.com/thibaut)
101 101
102Interested in helping maintain DevDocs? Come talk to us on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs) :)102Interested in helping maintain DevDocs? Come talk to us on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs) :)
docs/scraper-reference.mdrenamed from docs/Scraper-Reference.md+41−0
@@ -184,3 +184,44 @@ More information about how filters work is available on the [Filter Reference](. · 183 unmodified lines
184 Overrides the `:title` option for the root page only.184 Overrides the `:title` option for the root page only.
185 185
186 _Note: this filter is disabled by default._186 _Note: this filter is disabled by default._
187
188## Keeping scrapers up-to-date
189
190In order to keep scrapers up-to-date the `get_latest_version(opts)` method should be overridden. If `self.release` is defined, this should return the latest version of the documentation. If `self.release` is not defined, it should return the Epoch time when the documentation was last modified. If the documentation will never change, simply return `1.0.0`. The result of this method is periodically reported in a "Documentation versions report" issue which helps maintainers keep track of outdated documentations.
191
192To make life easier, there are a few utility methods that you can use in `get_latest_version`:
193* `fetch(url, opts)`
194
195 Makes a GET request to the url and returns the response body.
196
197 Example: [lib/docs/scrapers/bash.rb](../lib/docs/scrapers/bash.rb)
198* `fetch_doc(url, opts)`
199
200 Makes a GET request to the url and returns the HTML body converted to a Nokogiri document.
201
202 Example: [lib/docs/scrapers/git.rb](../lib/docs/scrapers/git.rb)
203* `fetch_json(url, opts)`
204
205 Makes a GET request to the url and returns the JSON body converted to a dictionary.
206
207 Example: [lib/docs/scrapers/mdn/mdn.rb](../lib/docs/scrapers/mdn/mdn.rb)
208* `get_npm_version(package, opts)`
209
210 Returns the latest version of the given npm package.
211
212 Example: [lib/docs/scrapers/bower.rb](../lib/docs/scrapers/bower.rb)
213* `get_latest_github_release(owner, repo, opts)`
214
215 Returns the tag name of the latest GitHub release of the given repository. If the tag name is preceded by a "v", the "v" will be removed.
216
217 Example: [lib/docs/scrapers/jsdoc.rb](../lib/docs/scrapers/jsdoc.rb)
218* `get_github_tags(owner, repo, opts)`
219
220 Returns the list of tags on the given repository ([format](https://developer.github.com/v3/repos/#list-tags)).
221
222 Example: [lib/docs/scrapers/liquid.rb](../lib/docs/scrapers/liquid.rb)
223* `get_github_file_contents(owner, repo, path, opts)`
224
225 Returns the contents of the requested file in the default branch of the given repository.
226
227 Example: [lib/docs/scrapers/minitest.rb](../lib/docs/scrapers/minitest.rb)
lib/app.rb+42−24
@@ -37,6 +37,9 @@ class App < Sinatra::Application · 36 unmodified lines
37 37
38 set :csp, false38 set :csp, false
39 39
40 require 'docs'
41 Docs.generate_manifest
42
40 Dir[docs_path, root.join(assets_prefix, '*/')].each do |path|43 Dir[docs_path, root.join(assets_prefix, '*/')].each do |path|
41 sprockets.append_path(path)44 sprockets.append_path(path)
42 end45 end
@@ -50,6 +53,11 @@ class App < Sinatra::Application · 7 unmodified lines
50 end53 end
51 54
52 configure :test, :development do55 configure :test, :development do
56 require 'thor'
57 load 'tasks/sprites.thor'
58
59 SpritesCLI.new.invoke(:generate)
60
53 require 'active_support/per_thread_registry'61 require 'active_support/per_thread_registry'
54 require 'active_support/cache'62 require 'active_support/cache'
55 sprockets.cache = ActiveSupport::Cache.lookup_store :file_store, root.join('tmp', 'cache', 'assets', environment.to_s)63 sprockets.cache = ActiveSupport::Cache.lookup_store :file_store, root.join('tmp', 'cache', 'assets', environment.to_s)
@@ -192,35 +200,45 @@ class App < Sinatra::Application · 136 unmodified lines
192 request.query_string.empty? ? nil : "?#{request.query_string}"200 request.query_string.empty? ? nil : "?#{request.query_string}"
193 end201 end
194 202
195 def manifest_asset_urls203 def service_worker_asset_urls
196 @@manifest_asset_urls ||= [204 @@service_worker_asset_urls ||= [
197 javascript_path('application', asset_host: false),205 javascript_path('application', asset_host: false),
198 stylesheet_path('application'),206 stylesheet_path('application'),
199 image_path('docs-1.png'),207 image_path('sprites/docs.png'),
200 image_path('docs-1@2x.png'),208 image_path('sprites/docs@2x.png'),
201 image_path('docs-2.png'),209 asset_path('docs.js'),
202 image_path('docs-2@2x.png'),210 App.production? ? nil : javascript_path('debug'),
203 asset_path('docs.js')211 ].compact
204 ]
205 end212 end
206 213
207 def app_size214 # Returns a cache name for the service worker to use which changes if any of the assets changes
208 @app_size ||= memoized_cookies['size'].nil? ? '20rem' : "#{memoized_cookies['size']}px"215 # When a manifest exist, this name is only created once based on the asset manifest because it never changes without a server restart
209 end216 # If a manifest does not exist, it is created every time this method is called because the assets can change while the server is running
217 def service_worker_cache_name
218 if File.exist?(App.assets_manifest_path)
219 if defined?(@@service_worker_cache_name)
220 return @@service_worker_cache_name
221 end
210 222
211 def app_layout223 digest = Sprockets::Manifest
212 memoized_cookies['layout']224 .new(nil, App.assets_manifest_path)
213 end225 .files
226 .values
227 .map {|file| file["digest"]}
228 .join
214 229
215 def app_theme230 return @@service_worker_cache_name ||= Digest::MD5.hexdigest(digest)
216 @app_theme ||= memoized_cookies['dark'].nil? ? 'default' : 'dark'231 else
217 end232 paths = App.sprockets
233 .each_file
234 .to_a
235 .reject {|file| file.start_with?(App.docs_path)}
218 236
219 def dark_theme?237 return App.sprockets.pack_hexdigest(App.sprockets.files_digest(paths))
220 app_theme == 'dark'238 end
221 end239 end
222 240
223 def redirect_via_js(path) # courtesy of HTML5 App Cache241 def redirect_via_js(path)
224 response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/'242 response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/'
225 redirect '/', 302243 redirect '/', 302
226 end244 end
@@ -243,15 +261,15 @@ class App < Sinatra::Application · 16 unmodified lines
243 end261 end
244 end262 end
245 263
246 get '/manifest.appcache' do264 get '/service-worker.js' do
247 content_type 'text/cache-manifest'265 content_type 'application/javascript'
248 expires 0, :'no-cache'266 expires 0, :'no-cache'
249 erb :manifest267 erb :'service-worker.js'
250 end268 end
251 269
252 get '/' do270 get '/' do
253 return redirect "/#q=#{params[:q]}" if params[:q]271 return redirect "/#q=#{params[:q]}" if params[:q]
254 return redirect '/' unless request.query_string.empty? # courtesy of HTML5 App Cache272 return redirect '/' unless request.query_string.empty?
255 response.headers['Content-Security-Policy'] = settings.csp if settings.csp273 response.headers['Content-Security-Policy'] = settings.csp if settings.csp
256 erb :index274 erb :index
257 end275 end
lib/docs/core/doc.rb+99−1
@@ -152,7 +152,6 @@ module Docs · 151 unmodified lines
152 end152 end
153 end153 end
154 154
155
156 def initialize155 def initialize
157 raise NotImplementedError, "#{self.class} is an abstract class and cannot be instantiated." if self.class.abstract156 raise NotImplementedError, "#{self.class} is an abstract class and cannot be instantiated." if self.class.abstract
158 end157 end
@@ -164,5 +163,104 @@ module Docs · 5 unmodified lines
164 def build_pages(&block)163 def build_pages(&block)
165 raise NotImplementedError164 raise NotImplementedError
166 end165 end
166
167 def get_scraper_version(opts)
168 if self.class.method_defined?(:options) and !options[:release].nil?
169 options[:release]
170 else
171 # If options[:release] does not exist, we return the Epoch timestamp of when the doc was last modified in DevDocs production
172 json = fetch_json('https://devdocs.io/docs.json', opts)
173 items = json.select {|item| item['name'] == self.class.name}
174 items = items.map {|item| item['mtime']}
175 items.max
176 end
177 end
178
179 # Should return the latest version of this documentation
180 # If options[:release] is defined, it should be in the same format
181 # If options[:release] is not defined, it should return the Epoch timestamp of when the documentation was last updated
182 # If the docs will never change, simply return '1.0.0'
183 def get_latest_version(opts)
184 raise NotImplementedError
185 end
186
187 # Returns whether or not this scraper is outdated.
188 #
189 # The default implementation assumes the documentation uses a semver(-like) approach when it comes to versions.
190 # Patch updates are ignored because there are usually little to no documentation changes in bug-fix-only releases.
191 #
192 # Scrapers of documentations that do not use this versioning approach should override this method.
193 #
194 # Examples of the default implementation:
195 # 1 -> 2 = outdated
196 # 1.1 -> 1.2 = outdated
197 # 1.1.1 -> 1.1.2 = not outdated
198 def is_outdated(scraper_version, latest_version)
199 scraper_parts = scraper_version.to_s.split(/\./).map(&:to_i)
200 latest_parts = latest_version.to_s.split(/\./).map(&:to_i)
201
202 # Only check the first two parts, the third part is for patch updates
203 [0, 1].each do |i|
204 break if i >= scraper_parts.length or i >= latest_parts.length
205 return true if latest_parts[i] > scraper_parts[i]
206 return false if latest_parts[i] < scraper_parts[i]
207 end
208
209 false
210 end
211
212 private
213
214 #
215 # Utility methods for get_latest_version
216 #
217
218 def fetch(url, opts)
219 headers = {}
220
221 if opts.key?(:github_token) and url.start_with?('https://api.github.com/')
222 headers['Authorization'] = "token #{opts[:github_token]}"
223 end
224
225 opts[:logger].debug("Fetching #{url}")
226 response = Request.run(url, { connecttimeout: 15, headers: headers })
227
228 if response.success?
229 response.body
230 else
231 reason = response.timed_out? ? "Timed out while connecting to #{url}" : "Couldn't fetch #{url} (response code #{response.code})"
232 opts[:logger].error(reason)
233 raise reason
234 end
235 end
236
237 def fetch_doc(url, opts)
238 body = fetch(url, opts)
239 Nokogiri::HTML.parse(body, nil, 'UTF-8')
240 end
241
242 def fetch_json(url, opts)
243 JSON.parse fetch(url, opts)
244 end
245
246 def get_npm_version(package, opts)
247 json = fetch_json("https://registry.npmjs.com/#{package}", opts)
248 json['dist-tags']['latest']
249 end
250
251 def get_latest_github_release(owner, repo, opts)
252 release = fetch_json("https://api.github.com/repos/#{owner}/#{repo}/releases/latest", opts)
253 tag_name = release['tag_name']
254 tag_name.start_with?('v') ? tag_name[1..-1] : tag_name
255 end
256
257 def get_github_tags(owner, repo, opts)
258 fetch_json("https://api.github.com/repos/#{owner}/#{repo}/tags", opts)
259 end
260
261 def get_github_file_contents(owner, repo, path, opts)
262 json = fetch_json("https://api.github.com/repos/#{owner}/#{repo}/contents/#{path}", opts)
263 Base64.decode64(json['content'])
264 end
167 end265 end
168end266end
lib/docs/core/scrapers/file_scraper.rb+2−2
@@ -29,7 +29,7 @@ module Docs · 28 unmodified lines
29 29
30 def request_one(url)30 def request_one(url)
31 assert_source_directory_exists31 assert_source_directory_exists
32 Response.new read_file(url_to_path(url)), URL.parse(url)32 Response.new read_file(File.join(source_directory, url_to_path(url))), URL.parse(url)
33 end33 end
34 34
35 def request_all(urls)35 def request_all(urls)
@@ -50,7 +50,7 @@ module Docs · 14 unmodified lines
50 end50 end
51 51
52 def read_file(path)52 def read_file(path)
53 File.read(File.join(source_directory, path))53 File.read(path)
54 rescue54 rescue
55 instrument 'warn.doc', msg: "Failed to open file: #{path}"55 instrument 'warn.doc', msg: "Failed to open file: #{path}"
56 nil56 nil
lib/docs/scrapers/angular.rb+4−0
@@ -155,6 +155,10 @@ module Docs · 154 unmodified lines
155 end155 end
156 end156 end
157 157
158 def get_latest_version(opts)
159 get_npm_version('@angular/core', opts)
160 end
161
158 private162 private
159 163
160 def parse(response)164 def parse(response)
lib/docs/scrapers/angularjs.rb+4−0
@@ -69,5 +69,9 @@ module Docs · 68 unmodified lines
69 self.release = '1.2.32'69 self.release = '1.2.32'
70 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"70 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
71 end71 end
72
73 def get_latest_version(opts)
74 get_npm_version('angular', opts)
75 end
72 end76 end
73end77end
lib/docs/scrapers/ansible.rb+5−0
@@ -87,5 +87,10 @@ module Docs · 86 unmodified lines
87 quickstart.html87 quickstart.html
88 list_of_all_modules.html)88 list_of_all_modules.html)
89 end89 end
90
91 def get_latest_version(opts)
92 doc = fetch_doc('https://docs.ansible.com/ansible/latest/index.html', opts)
93 doc.at_css('.DocSiteProduct-CurrentVersion').content.strip
94 end
90 end95 end
91end96end
lib/docs/scrapers/apache.rb+5−0
@@ -33,5 +33,10 @@ module Docs · 32 unmodified lines
33 &copy; 2018 The Apache Software Foundation<br>33 &copy; 2018 The Apache Software Foundation<br>
34 Licensed under the Apache License, Version 2.0.34 Licensed under the Apache License, Version 2.0.
35 HTML35 HTML
36
37 def get_latest_version(opts)
38 doc = fetch_doc('http://httpd.apache.org/docs/', opts)
39 doc.at_css('#apcontents > ul a')['href'][0...-1]
40 end
36 end41 end
37end42end
lib/docs/scrapers/apache_pig.rb+5−0
@@ -43,5 +43,10 @@ module Docs · 42 unmodified lines
43 self.base_url = "https://pig.apache.org/docs/r#{release}/"43 self.base_url = "https://pig.apache.org/docs/r#{release}/"
44 end44 end
45 45
46 def get_latest_version(opts)
47 doc = fetch_doc('https://pig.apache.org/', opts)
48 item = doc.at_css('div[id="menu_1.2"] > .menuitem:last-child')
49 item.content.strip.sub(/Release /, '')
50 end
46 end51 end
47end52end
lib/docs/scrapers/async.rb+5−0
@@ -17,5 +17,10 @@ module Docs · 16 unmodified lines
17 &copy; 2010&ndash;2018 Caolan McMahon<br>17 &copy; 2010&ndash;2018 Caolan McMahon<br>
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 doc = fetch_doc('https://caolan.github.io/async/v3/', opts)
23 doc.at_css('#version-dropdown > a').content.strip[1..-1]
24 end
20 end25 end
21end26end
lib/docs/scrapers/babel.rb+5−0
@@ -22,5 +22,10 @@ module Docs · 21 unmodified lines
22 stub '' do22 stub '' do
23 '<div></div>'23 '<div></div>'
24 end24 end
25
26 def get_latest_version(opts)
27 doc = fetch_doc('https://babeljs.io/docs/en/', opts)
28 doc.at_css('a[href="/versions"] > h3').content
29 end
25 end30 end
26end31end
lib/docs/scrapers/backbone.rb+5−0
@@ -20,5 +20,10 @@ module Docs · 19 unmodified lines
20 &copy; 2010&ndash;2016 Jeremy Ashkenas, DocumentCloud<br>20 &copy; 2010&ndash;2016 Jeremy Ashkenas, DocumentCloud<br>
21 Licensed under the MIT License.21 Licensed under the MIT License.
22 HTML22 HTML
23
24 def get_latest_version(opts)
25 doc = fetch_doc('https://backbonejs.org/', opts)
26 doc.at_css('.version').content[1...-1]
27 end
23 end28 end
24end29end
lib/docs/scrapers/bash.rb+5−0
@@ -17,5 +17,10 @@ module Docs · 16 unmodified lines
17 Copyright &copy; 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.<br>17 Copyright &copy; 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.<br>
18 Licensed under the GNU Free Documentation License.18 Licensed under the GNU Free Documentation License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 body = fetch('https://www.gnu.org/software/bash/manual/html_node/index.html', opts)
23 body.scan(/, Version ([0-9.]+)/)[0][0][0...-1]
24 end
20 end25 end
21end26end
lib/docs/scrapers/bluebird.rb+4−0
@@ -18,5 +18,9 @@ module Docs · 17 unmodified lines
18 &copy; 2013&ndash;2017 Petka Antonov<br>18 &copy; 2013&ndash;2017 Petka Antonov<br>
19 Licensed under the MIT License.19 Licensed under the MIT License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 get_npm_version('bluebird', opts)
24 end
21 end25 end
22end26end
lib/docs/scrapers/bootstrap.rb+5−0
@@ -34,5 +34,10 @@ module Docs · 33 unmodified lines
34 34
35 options[:only] = %w(getting-started/ css/ components/ javascript/)35 options[:only] = %w(getting-started/ css/ components/ javascript/)
36 end36 end
37
38 def get_latest_version(opts)
39 doc = fetch_doc('https://getbootstrap.com/', opts)
40 doc.at_css('#bd-versions').content.strip[1..-1]
41 end
37 end42 end
38end43end
lib/docs/scrapers/bottle.rb+6−0
@@ -27,5 +27,11 @@ module Docs · 26 unmodified lines
27 self.release = '0.11.7'27 self.release = '0.11.7'
28 self.base_url = "https://bottlepy.org/docs/#{self.version}/"28 self.base_url = "https://bottlepy.org/docs/#{self.version}/"
29 end29 end
30
31 def get_latest_version(opts)
32 doc = fetch_doc('https://bottlepy.org/docs/stable/', opts)
33 label = doc.at_css('.sphinxsidebarwrapper > ul > li > b')
34 label.content.sub(/Bottle /, '')
35 end
30 end36 end
31end37end
lib/docs/scrapers/bower.rb+4−0
@@ -19,5 +19,9 @@ module Docs · 18 unmodified lines
19 &copy; 2018 Bower contributors<br>19 &copy; 2018 Bower contributors<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_npm_version('bower', opts)
25 end
22 end26 end
23end27end
lib/docs/scrapers/c.rb+7−0
@@ -26,6 +26,13 @@ module Docs · 25 unmodified lines
26 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.26 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
27 HTML27 HTML
28 28
29 def get_latest_version(opts)
30 doc = fetch_doc('https://en.cppreference.com/w/Cppreference:Archives', opts)
31 link = doc.at_css('a[title^="File:"]')
32 date = link.content.scan(/(\d+)\./)[0][0]
33 DateTime.strptime(date, '%Y%m%d').to_time.to_i
34 end
35
29 private36 private
30 37
31 def file_path_for(*)38 def file_path_for(*)
lib/docs/scrapers/cakephp.rb+5−0
@@ -71,6 +71,11 @@ module Docs · 70 unmodified lines
71 self.base_url = 'https://api.cakephp.org/2.7/'71 self.base_url = 'https://api.cakephp.org/2.7/'
72 end72 end
73 73
74 def get_latest_version(opts)
75 doc = fetch_doc('https://api.cakephp.org/3.7/', opts)
76 doc.at_css('.version-picker .dropdown-toggle').content.strip
77 end
78
74 private79 private
75 80
76 def parse(response)81 def parse(response)
lib/docs/scrapers/chai.rb+4−0
@@ -23,5 +23,9 @@ module Docs · 22 unmodified lines
23 &copy; 2016 Chai.js Assertion Library<br>23 &copy; 2016 Chai.js Assertion Library<br>
24 Licensed under the MIT License.24 Licensed under the MIT License.
25 HTML25 HTML
26
27 def get_latest_version(opts)
28 get_npm_version('chai', opts)
29 end
26 end30 end
27end31end
lib/docs/scrapers/chef.rb+5−0
@@ -47,5 +47,10 @@ module Docs · 46 unmodified lines
47 47
48 options[:only_patterns] = [/\A#{client_path}\//, /\A#{server_path}\//]48 options[:only_patterns] = [/\A#{client_path}\//, /\A#{server_path}\//]
49 end49 end
50
51 def get_latest_version(opts)
52 doc = fetch_doc('https://downloads.chef.io/chef', opts)
53 doc.at_css('h1.product-heading > span').content.strip
54 end
50 end55 end
51end56end
lib/docs/scrapers/clojure.rb+5−0
@@ -27,5 +27,10 @@ module Docs · 26 unmodified lines
27 self.release = '1.7'27 self.release = '1.7'
28 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'28 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'
29 end29 end
30
31 def get_latest_version(opts)
32 doc = fetch_doc('http://clojure.github.io/clojure/index.html', opts)
33 doc.at_css('#header-version').content[1..-1]
34 end
30 end35 end
31end36end
lib/docs/scrapers/cmake.rb+6−0
@@ -59,5 +59,11 @@ module Docs · 58 unmodified lines
59 self.release = '3.5.2'59 self.release = '3.5.2'
60 self.base_url = 'https://cmake.org/cmake/help/v3.5/'60 self.base_url = 'https://cmake.org/cmake/help/v3.5/'
61 end61 end
62
63 def get_latest_version(opts)
64 doc = fetch_doc('https://cmake.org/documentation/', opts)
65 link = doc.at_css('.entry-content ul > li > strong > a > big')
66 link.content.scan(/([0-9.]+)/)[0][0]
67 end
62 end68 end
63end69end
lib/docs/scrapers/codeception.rb+5−0
@@ -18,5 +18,10 @@ module Docs · 17 unmodified lines
18 &copy; 2011 Michael Bodnarchuk and contributors<br>18 &copy; 2011 Michael Bodnarchuk and contributors<br>
19 Licensed under the MIT License.19 Licensed under the MIT License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 doc = fetch_doc('https://codeception.com/changelog', opts)
24 doc.at_css('#page > h4').content
25 end
21 end26 end
22end27end
lib/docs/scrapers/codeceptjs.rb+4−0
@@ -21,5 +21,9 @@ module Docs · 20 unmodified lines
21 &copy; 2015 DavertMik &lt;davert@codegyre.com&gt; (http://codegyre.com)<br>21 &copy; 2015 DavertMik &lt;davert@codegyre.com&gt; (http://codegyre.com)<br>
22 Licensed under the MIT License.22 Licensed under the MIT License.
23 HTML23 HTML
24
25 def get_latest_version(opts)
26 get_npm_version('codeceptjs', opts)
27 end
24 end28 end
25end29end
lib/docs/scrapers/codeigniter.rb+6−0
@@ -38,5 +38,11 @@ module Docs · 37 unmodified lines
38 version '3' do38 version '3' do
39 self.release = '3.1.8'39 self.release = '3.1.8'
40 end40 end
41
42 def get_latest_version(opts)
43 doc = fetch_doc('https://codeigniter.com/user_guide/changelog.html', opts)
44 header = doc.at_css('#change-log h2')
45 header.content.scan(/([0-9.]+)/)[0][0]
46 end
41 end47 end
42end48end
lib/docs/scrapers/coffeescript.rb+4−0
@@ -30,5 +30,9 @@ module Docs · 29 unmodified lines
30 30
31 options[:container] = '.container'31 options[:container] = '.container'
32 end32 end
33
34 def get_latest_version(opts)
35 get_npm_version('coffeescript', opts)
36 end
33 end37 end
34end38end
lib/docs/scrapers/cordova.rb+10−0
@@ -42,5 +42,15 @@ module Docs · 41 unmodified lines
42 self.release = '6.5.0'42 self.release = '6.5.0'
43 self.base_url = 'https://cordova.apache.org/docs/en/6.x/'43 self.base_url = 'https://cordova.apache.org/docs/en/6.x/'
44 end44 end
45
46 def get_latest_version(opts)
47 doc = fetch_doc('https://cordova.apache.org/docs/en/latest/', opts)
48
49 label = doc.at_css('#versionDropdown').content.strip
50 version = label.scan(/([0-9.]+)/)[0][0]
51 version = version[0...-1] if version.end_with?('.')
52
53 version
54 end
45 end55 end
46end56end
lib/docs/scrapers/cpp.rb+8−0
@@ -34,6 +34,14 @@ module Docs · 33 unmodified lines
34 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.34 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
35 HTML35 HTML
36 36
37 # Same as get_latest_version in lib/docs/scrapers/c.rb
38 def get_latest_version(opts)
39 doc = fetch_doc('https://en.cppreference.com/w/Cppreference:Archives', opts)
40 link = doc.at_css('a[title^="File:"]')
41 date = link.content.scan(/(\d+)\./)[0][0]
42 DateTime.strptime(date, '%Y%m%d').to_time.to_i
43 end
44
37 private45 private
38 46
39 def file_path_for(*)47 def file_path_for(*)
lib/docs/scrapers/crystal.rb+5−0
@@ -34,5 +34,10 @@ module Docs · 33 unmodified lines
34 HTML34 HTML
35 end35 end
36 }36 }
37
38 def get_latest_version(opts)
39 body = fetch('https://crystal-lang.org/api', opts)
40 body.scan(/Crystal Docs ([0-9.]+)/)[0][0]
41 end
37 end42 end
38end43end
lib/docs/scrapers/d.rb+5−0
@@ -26,5 +26,10 @@ module Docs · 25 unmodified lines
26 def initial_urls26 def initial_urls
27 %w(https://dlang.org/phobos/index.html https://dlang.org/spec/intro.html)27 %w(https://dlang.org/phobos/index.html https://dlang.org/spec/intro.html)
28 end28 end
29
30 def get_latest_version(opts)
31 doc = fetch_doc('https://dlang.org/changelog/', opts)
32 doc.at_css('#content > ul > li:nth-child(2) > a')['id']
33 end
29 end34 end
30end35end
lib/docs/scrapers/d3.rb+4−0
@@ -58,5 +58,9 @@ module Docs · 57 unmodified lines
58 options[:root_title] = 'D3.js'58 options[:root_title] = 'D3.js'
59 options[:only_patterns] = [/\.md\z/]59 options[:only_patterns] = [/\.md\z/]
60 end60 end
61
62 def get_latest_version(opts)
63 get_npm_version('d3', opts)
64 end
61 end65 end
62end66end
lib/docs/scrapers/dart.rb+6−0
@@ -31,5 +31,11 @@ module Docs · 30 unmodified lines
31 self.release = '1.24.3'31 self.release = '1.24.3'
32 self.base_url = "https://api.dartlang.org/stable/#{release}/"32 self.base_url = "https://api.dartlang.org/stable/#{release}/"
33 end33 end
34
35 def get_latest_version(opts)
36 doc = fetch_doc('https://api.dartlang.org/', opts)
37 label = doc.at_css('footer > span').content.strip
38 label.sub(/Dart /, '')
39 end
34 end40 end
35end41end
lib/docs/scrapers/django.rb+5−0
@@ -63,5 +63,10 @@ module Docs · 62 unmodified lines
63 self.release = '1.8.18'63 self.release = '1.8.18'
64 self.base_url = 'https://docs.djangoproject.com/en/1.8/'64 self.base_url = 'https://docs.djangoproject.com/en/1.8/'
65 end65 end
66
67 def get_latest_version(opts)
68 doc = fetch_doc('https://docs.djangoproject.com/', opts)
69 doc.at_css('#doc-versions > li.current > span > strong').content
70 end
66 end71 end
67end72end
lib/docs/scrapers/docker.rb+6−0
@@ -137,5 +137,11 @@ module Docs · 136 unmodified lines
137 options[:container] = '#docs'137 options[:container] = '#docs'
138 options[:only_patterns] << /\Aswarm\//138 options[:only_patterns] << /\Aswarm\//
139 end139 end
140
141 def get_latest_version(opts)
142 doc = fetch_doc('https://docs.docker.com/', opts)
143 label = doc.at_css('.nav-container button.dropdown-toggle').content.strip
144 label.scan(/([0-9.]+)/)[0][0]
145 end
140 end146 end
141end147end
lib/docs/scrapers/dojo.rb+5−0
@@ -36,6 +36,11 @@ module Docs · 35 unmodified lines
36 urls.map { |url| "<a href='#{url}'>#{url}</a>" }.join36 urls.map { |url| "<a href='#{url}'>#{url}</a>" }.join
37 end37 end
38 38
39 def get_latest_version(opts)
40 doc = fetch_doc('https://dojotoolkit.org/api/', opts)
41 doc.at_css('#versionSelector > option[selected]').content
42 end
43
39 private44 private
40 45
41 def get_url_list(json, set = Set.new)46 def get_url_list(json, set = Set.new)
lib/docs/scrapers/drupal.rb+5−0
@@ -98,5 +98,10 @@ module Docs · 97 unmodified lines
98 /\A[\w\-\.]+\.php\/7\.x\z/98 /\A[\w\-\.]+\.php\/7\.x\z/
99 ]99 ]
100 end100 end
101
102 def get_latest_version(opts)
103 json = fetch_json('https://packagist.org/packages/drupal/drupal.json', opts)
104 json['package']['versions'].keys.find {|version| !version.end_with?('-dev')}
105 end
101 end106 end
102end107end
lib/docs/scrapers/electron.rb+5−0
@@ -22,5 +22,10 @@ module Docs · 21 unmodified lines
22 &copy; 2013&ndash;2018 GitHub Inc.<br>22 &copy; 2013&ndash;2018 GitHub Inc.<br>
23 Licensed under the MIT license.23 Licensed under the MIT license.
24 HTML24 HTML
25
26 def get_latest_version(opts)
27 doc = fetch_doc('https://electronjs.org/docs', opts)
28 doc.at_css('.docs-version').content
29 end
25 end30 end
26end31end
lib/docs/scrapers/elixir.rb+5−0
@@ -97,5 +97,10 @@ module Docs · 96 unmodified lines
97 'https://elixir-lang.org/getting-started/'97 'https://elixir-lang.org/getting-started/'
98 ]98 ]
99 end99 end
100
101 def get_latest_version(opts)
102 doc = fetch_doc('https://hexdocs.pm/elixir/api-reference.html', opts)
103 doc.at_css('h2.sidebar-projectVersion').content.strip[1..-1]
104 end
100 end105 end
101end106end
lib/docs/scrapers/ember.rb+5−0
@@ -56,5 +56,10 @@ module Docs · 55 unmodified lines
56 https://emberjs.com/api/ember-data/2.14/classes/DS56 https://emberjs.com/api/ember-data/2.14/classes/DS
57 )57 )
58 end58 end
59
60 def get_latest_version(opts)
61 doc = fetch_doc('https://emberjs.com/api/ember/release', opts)
62 doc.at_css('.sidebar > .select-container .ember-power-select-selected-item').content.strip
63 end
59 end64 end
60end65end
lib/docs/scrapers/erlang.rb+5−0
@@ -55,5 +55,10 @@ module Docs · 54 unmodified lines
55 version '18' do55 version '18' do
56 self.release = '18.3'56 self.release = '18.3'
57 end57 end
58
59 def get_latest_version(opts)
60 doc = fetch_doc('https://www.erlang.org/downloads', opts)
61 doc.at_css('.col-lg-3 > ul > li').content.strip.sub(/OTP /, '')
62 end
58 end63 end
59end64end
lib/docs/scrapers/eslint.rb+4−0
@@ -20,5 +20,9 @@ module Docs · 19 unmodified lines
20 &copy; JS Foundation and other contributors<br>20 &copy; JS Foundation and other contributors<br>
21 Licensed under the MIT License.21 Licensed under the MIT License.
22 HTML22 HTML
23
24 def get_latest_version(opts)
25 get_npm_version('eslint', opts)
26 end
23 end27 end
24end28end
lib/docs/scrapers/express.rb+4−0
@@ -28,5 +28,9 @@ module Docs · 27 unmodified lines
28 &copy; 2017 StrongLoop, IBM, and other expressjs.com contributors.<br>28 &copy; 2017 StrongLoop, IBM, and other expressjs.com contributors.<br>
29 Licensed under the Creative Commons Attribution-ShareAlike License v3.0.29 Licensed under the Creative Commons Attribution-ShareAlike License v3.0.
30 HTML30 HTML
31
32 def get_latest_version(opts)
33 get_npm_version('express', opts)
34 end
31 end35 end
32end36end
lib/docs/scrapers/falcon.rb+5−0
@@ -33,5 +33,10 @@ module Docs · 32 unmodified lines
33 self.release = '1.2.0'33 self.release = '1.2.0'
34 self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"34 self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"
35 end35 end
36
37 def get_latest_version(opts)
38 doc = fetch_doc('https://falcon.readthedocs.io/en/stable/changes/index.html', opts)
39 doc.at_css('#changelogs ul > li > a').content
40 end
36 end41 end
37end42end
lib/docs/scrapers/fish.rb+5−0
@@ -46,5 +46,10 @@ module Docs · 45 unmodified lines
46 self.release = '2.2.0'46 self.release = '2.2.0'
47 self.base_url = "https://fishshell.com/docs/#{version}/"47 self.base_url = "https://fishshell.com/docs/#{version}/"
48 end48 end
49
50 def get_latest_version(opts)
51 doc = fetch_doc('http://fishshell.com/docs/current/index.html', opts)
52 doc.at_css('#toc-index').content.scan(/([0-9.]+)/)[0][0]
53 end
49 end54 end
50end55end
lib/docs/scrapers/flow.rb+4−0
@@ -18,5 +18,9 @@ module Docs · 17 unmodified lines
18 &copy; 2013&ndash;present Facebook Inc.<br>18 &copy; 2013&ndash;present Facebook Inc.<br>
19 Licensed under the MIT License.19 Licensed under the MIT License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 get_npm_version('flow-bin', opts)
24 end
21 end25 end
22end26end
lib/docs/scrapers/git.rb+5−0
@@ -19,5 +19,10 @@ module Docs · 18 unmodified lines
19 &copy; 2005&ndash;2018 Linus Torvalds and others<br>19 &copy; 2005&ndash;2018 Linus Torvalds and others<br>
20 Licensed under the GNU General Public License version 2.20 Licensed under the GNU General Public License version 2.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 doc = fetch_doc('https://git-scm.com/', opts)
25 doc.at_css('.version').content.strip
26 end
22 end27 end
23end28end
lib/docs/scrapers/gnu/gcc.rb+6−0
@@ -99,5 +99,11 @@ module Docs · 98 unmodified lines
99 99
100 options[:replace_paths] = CPP_PATHS100 options[:replace_paths] = CPP_PATHS
101 end101 end
102
103 def get_latest_version(opts)
104 doc = fetch_doc('https://gcc.gnu.org/onlinedocs/', opts)
105 label = doc.at_css('ul > li > ul > li > a').content.strip
106 label.scan(/([0-9.]+)/)[0][0]
107 end
102 end108 end
103end109end
lib/docs/scrapers/gnu/gnu_fortran.rb+6−0
@@ -25,5 +25,11 @@ module Docs · 24 unmodified lines
25 self.release = '4.9.3'25 self.release = '4.9.3'
26 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"26 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
27 end27 end
28
29 def get_latest_version(opts)
30 doc = fetch_doc('https://gcc.gnu.org/onlinedocs/', opts)
31 label = doc.at_css('ul > li > ul > li > a').content.strip
32 label.scan(/([0-9.]+)/)[0][0]
33 end
28 end34 end
29end35end
lib/docs/scrapers/go.rb+5−0
@@ -24,6 +24,11 @@ module Docs · 23 unmodified lines
24 Licensed under the Creative Commons Attribution License 3.0.24 Licensed under the Creative Commons Attribution License 3.0.
25 HTML25 HTML
26 26
27 def get_latest_version(opts)
28 doc = fetch_doc('https://golang.org/project/', opts)
29 doc.at_css('#page ul > li > a').text[3..-1]
30 end
31
27 private32 private
28 33
29 def parse(response) # Hook here because Nokogori removes whitespace from textareas34 def parse(response) # Hook here because Nokogori removes whitespace from textareas
lib/docs/scrapers/godot.rb+5−0
@@ -37,5 +37,10 @@ module Docs · 36 unmodified lines
37 self.release = '2.1'37 self.release = '2.1'
38 self.base_url = "http://docs.godotengine.org/en/#{self.version}/"38 self.base_url = "http://docs.godotengine.org/en/#{self.version}/"
39 end39 end
40
41 def get_latest_version(opts)
42 doc = fetch_doc('https://docs.godotengine.org/', opts)
43 doc.at_css('.version').content.strip
44 end
40 end45 end
41end46end
lib/docs/scrapers/graphite.rb+5−0
@@ -17,5 +17,10 @@ module Docs · 16 unmodified lines
17 &copy; 2011&ndash;2016 The Graphite Project<br>17 &copy; 2011&ndash;2016 The Graphite Project<br>
18 Licensed under the Apache License, Version 2.0.18 Licensed under the Apache License, Version 2.0.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 doc = fetch_doc('https://graphite.readthedocs.io/en/latest/releases.html', opts)
23 doc.at_css('#release-notes li > a').content
24 end
20 end25 end
21end26end
lib/docs/scrapers/grunt.rb+4−0
@@ -26,5 +26,9 @@ module Docs · 25 unmodified lines
26 &copy; GruntJS Team<br>26 &copy; GruntJS Team<br>
27 Licensed under the MIT License.27 Licensed under the MIT License.
28 HTML28 HTML
29
30 def get_latest_version(opts)
31 get_npm_version('grunt-cli', opts)
32 end
29 end33 end
30end34end
lib/docs/scrapers/handlebars.rb+4−0
@@ -19,5 +19,9 @@ module Docs · 18 unmodified lines
19 &copy; 2011&ndash;2017 by Yehuda Katz<br>19 &copy; 2011&ndash;2017 by Yehuda Katz<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_npm_version('handlebars', opts)
25 end
22 end26 end
23end27end
lib/docs/scrapers/haskell.rb+8−1
@@ -10,7 +10,7 @@ module Docs · 9 unmodified lines
10 10
11 html_filters.push 'haskell/entries', 'haskell/clean_html'11 html_filters.push 'haskell/entries', 'haskell/clean_html'
12 12
13 options[:container] = ->(filter) { filter.subpath.start_with?('users_guide') ? '.body' : '#content' }13 options[:container] = ->(filter) {filter.subpath.start_with?('users_guide') ? '.body' : '#content'}
14 14
15 options[:only_patterns] = [/\Alibraries\//, /\Ausers_guide\//]15 options[:only_patterns] = [/\Alibraries\//, /\Ausers_guide\//]
16 options[:skip_patterns] = [16 options[:skip_patterns] = [
@@ -68,5 +68,12 @@ module Docs · 51 unmodified lines
68 68
69 options[:only_patterns] = [/\Alibraries\//]69 options[:only_patterns] = [/\Alibraries\//]
70 end70 end
71
72 def get_latest_version(opts)
73 doc = fetch_doc('https://downloads.haskell.org/~ghc/latest/docs/html/', opts)
74 links = doc.css('a').to_a
75 versions = links.map {|link| link['href'].scan(/ghc-([0-9.]+)/)}
76 versions.find {|version| !version.empty?}[0][0]
77 end
71 end78 end
72end79end
lib/docs/scrapers/haxe.rb+6−0
@@ -66,5 +66,11 @@ module Docs · 65 unmodified lines
66 version 'Python' do66 version 'Python' do
67 self.base_url = 'https://api.haxe.org/python/'67 self.base_url = 'https://api.haxe.org/python/'
68 end68 end
69
70 def get_latest_version(opts)
71 doc = fetch_doc('https://api.haxe.org/', opts)
72 label = doc.at_css('.container.main-content h1 > small').content
73 label.sub(/version /, '')
74 end
69 end75 end
70end76end
lib/docs/scrapers/homebrew.rb+4−0
@@ -19,5 +19,9 @@ module Docs · 18 unmodified lines
19 &copy; 2009&ndash;present Homebrew contributors<br>19 &copy; 2009&ndash;present Homebrew contributors<br>
20 Licensed under the BSD 2-Clause License.20 Licensed under the BSD 2-Clause License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_latest_github_release('Homebrew', 'brew', opts)
25 end
22 end26 end
23end27end
lib/docs/scrapers/http.rb+2−0
@@ -7,6 +7,8 @@ module Docs · 6 unmodified lines
7 7
8 html_filters.push 'http/clean_html', 'http/entries', 'title'8 html_filters.push 'http/clean_html', 'http/entries', 'title'
9 9
10 options[:mdn_tag] = 'HTTP'
11
10 options[:root_title] = 'HTTP'12 options[:root_title] = 'HTTP'
11 options[:title] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? false : filter.default_title }13 options[:title] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? false : filter.default_title }
12 options[:container] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? '.content' : nil }14 options[:container] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? '.content' : nil }
lib/docs/scrapers/immutable.rb+4−0
@@ -54,5 +54,9 @@ module Docs · 53 unmodified lines
54 JS54 JS
55 capybara.html55 capybara.html
56 end56 end
57
58 def get_latest_version(opts)
59 get_npm_version('immutable', opts)
60 end
57 end61 end
58end62end
lib/docs/scrapers/influxdata.rb+6−0
@@ -46,5 +46,11 @@ module Docs · 45 unmodified lines
46 &copy; 2015 InfluxData, Inc.<br>46 &copy; 2015 InfluxData, Inc.<br>
47 Licensed under the MIT license.47 Licensed under the MIT license.
48 HTML48 HTML
49
50 def get_latest_version(opts)
51 doc = fetch_doc('https://docs.influxdata.com/influxdb/', opts)
52 label = doc.at_css('.navbar--current-product').content.strip
53 label.scan(/([0-9.]+)/)[0][0]
54 end
49 end55 end
50end56end
lib/docs/scrapers/jasmine.rb+4−0
@@ -17,5 +17,9 @@ module Docs · 16 unmodified lines
17 &copy; 2008&ndash;2017 Pivotal Labs<br>17 &copy; 2008&ndash;2017 Pivotal Labs<br>
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 get_latest_github_release('jasmine', 'jasmine', opts)
23 end
20 end24 end
21end25end
lib/docs/scrapers/jekyll.rb+5−0
@@ -28,5 +28,10 @@ module Docs · 27 unmodified lines
28 &copy; 2008&ndash;2018 Tom Preston-Werner and Jekyll contributors<br>28 &copy; 2008&ndash;2018 Tom Preston-Werner and Jekyll contributors<br>
29 Licensed under the MIT license.29 Licensed under the MIT license.
30 HTML30 HTML
31
32 def get_latest_version(opts)
33 doc = fetch_doc('https://jekyllrb.com/docs/', opts)
34 doc.at_css('.meta a').content[1..-1]
35 end
31 end36 end
32end37end
lib/docs/scrapers/jest.rb+5−0
@@ -17,5 +17,10 @@ module Docs · 16 unmodified lines
17 &copy; 2014&ndash;present Facebook Inc.<br>17 &copy; 2014&ndash;present Facebook Inc.<br>
18 Licensed under the BSD License.18 Licensed under the BSD License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 doc = fetch_doc('https://jestjs.io/docs/en/getting-started', opts)
23 doc.at_css('header > a > h3').content
24 end
20 end25 end
21end26end
lib/docs/scrapers/jquery/jquery_core.rb+4−0
@@ -22,5 +22,9 @@ module Docs · 21 unmodified lines
22 /Selectors\/odd/i,22 /Selectors\/odd/i,
23 /index/i23 /index/i
24 ]24 ]
25
26 def get_latest_version(opts)
27 get_npm_version('jquery', opts)
28 end
25 end29 end
26end30end
lib/docs/scrapers/jquery/jquery_mobile.rb+5−0
@@ -16,5 +16,10 @@ module Docs · 15 unmodified lines
16 options[:fix_urls] = ->(url) do16 options[:fix_urls] = ->(url) do
17 url.sub! 'http://api.jquerymobile.com/', 'https://api.jquerymobile.com/'17 url.sub! 'http://api.jquerymobile.com/', 'https://api.jquerymobile.com/'
18 end18 end
19
20 def get_latest_version(opts)
21 doc = fetch_doc('https://jquerymobile.com/', opts)
22 doc.at_css('.download-box > .download-option:last-child > span').content.sub(/Version /, '')
23 end
19 end24 end
20end25end
lib/docs/scrapers/jquery/jquery_ui.rb+4−0
@@ -15,5 +15,9 @@ module Docs · 14 unmodified lines
15 options[:fix_urls] = ->(url) do15 options[:fix_urls] = ->(url) do
16 url.sub! 'http://api.jqueryui.com/', 'https://api.jqueryui.com/'16 url.sub! 'http://api.jqueryui.com/', 'https://api.jqueryui.com/'
17 end17 end
18
19 def get_latest_version(opts)
20 get_npm_version('jquery-ui', opts)
21 end
18 end22 end
19end23end
lib/docs/scrapers/jsdoc.rb+4−0
@@ -21,5 +21,9 @@ module Docs · 20 unmodified lines
21 &copy; 2011&ndash;2017 the contributors to the JSDoc 3 documentation project<br>21 &copy; 2011&ndash;2017 the contributors to the JSDoc 3 documentation project<br>
22 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.22 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
23 HTML23 HTML
24
25 def get_latest_version(opts)
26 get_latest_github_release('jsdoc3', 'jsdoc', opts)
27 end
24 end28 end
25end29end
lib/docs/scrapers/julia.rb+4−0
@@ -49,5 +49,9 @@ module Docs · 48 unmodified lines
49 49
50 html_filters.push 'julia/entries_sphinx', 'julia/clean_html_sphinx', 'sphinx/clean_html'50 html_filters.push 'julia/entries_sphinx', 'julia/clean_html_sphinx', 'sphinx/clean_html'
51 end51 end
52
53 def get_latest_version(opts)
54 get_latest_github_release('JuliaLang', 'julia', opts)
55 end
52 end56 end
53end57end
lib/docs/scrapers/knockout.rb+4−0
@@ -33,5 +33,9 @@ module Docs · 32 unmodified lines
33 &copy; Steven Sanderson, the Knockout.js team, and other contributors<br>33 &copy; Steven Sanderson, the Knockout.js team, and other contributors<br>
34 Licensed under the MIT License.34 Licensed under the MIT License.
35 HTML35 HTML
36
37 def get_latest_version(opts)
38 get_latest_github_release('knockout', 'knockout', opts)
39 end
36 end40 end
37end41end
lib/docs/scrapers/koa.rb+4−0
@@ -34,5 +34,9 @@ module Docs · 33 unmodified lines
34 &copy; 2018 Koa contributors<br>34 &copy; 2018 Koa contributors<br>
35 Licensed under the MIT License.35 Licensed under the MIT License.
36 HTML36 HTML
37
38 def get_latest_version(opts)
39 get_npm_version('koa', opts)
40 end
37 end41 end
38end42end
lib/docs/scrapers/kotlin.rb+4−0
@@ -28,5 +28,9 @@ module Docs · 27 unmodified lines
28 &copy; 2010&ndash;2018 JetBrains s.r.o.<br>28 &copy; 2010&ndash;2018 JetBrains s.r.o.<br>
29 Licensed under the Apache License, Version 2.0.29 Licensed under the Apache License, Version 2.0.
30 HTML30 HTML
31
32 def get_latest_version(opts)
33 get_latest_github_release('JetBrains', 'kotlin', opts)
34 end
31 end35 end
32end36end
lib/docs/scrapers/laravel.rb+4−0
@@ -133,5 +133,9 @@ module Docs · 132 unmodified lines
133 url133 url
134 end134 end
135 end135 end
136
137 def get_latest_version(opts)
138 get_latest_github_release('laravel', 'laravel', opts)
139 end
136 end140 end
137end141end
lib/docs/scrapers/leaflet.rb+5−0
@@ -39,5 +39,10 @@ module Docs · 38 unmodified lines
39 self.base_url = "https://leafletjs.com/reference-#{release}.html"39 self.base_url = "https://leafletjs.com/reference-#{release}.html"
40 end40 end
41 41
42 def get_latest_version(opts)
43 doc = fetch_doc('https://leafletjs.com/index.html', opts)
44 link = doc.css('ul > li > a').to_a.select {|node| node.content == 'Docs'}.first
45 link['href'].scan(/reference-([0-9.]+)\.html/)[0][0]
46 end
42 end47 end
43end48end
lib/docs/scrapers/less.rb+6−0
@@ -21,5 +21,11 @@ module Docs · 20 unmodified lines
21 &copy; 2009&ndash;2016 The Core Less Team<br>21 &copy; 2009&ndash;2016 The Core Less Team<br>
22 Licensed under the Creative Commons Attribution License 3.0.22 Licensed under the Creative Commons Attribution License 3.0.
23 HTML23 HTML
24
25 def get_latest_version(opts)
26 doc = fetch_doc('http://lesscss.org/features/', opts)
27 label = doc.at_css('.footer-links > li').content
28 label.scan(/([0-9.]+)/)[0][0]
29 end
24 end30 end
25end31end
lib/docs/scrapers/liquid.rb+5−0
@@ -19,5 +19,10 @@ module Docs · 18 unmodified lines
19 &copy; 2005, 2006 Tobias Luetke<br>19 &copy; 2005, 2006 Tobias Luetke<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 tags = get_github_tags('Shopify', 'liquid', opts)
25 tags[0]['name'][1..-1]
26 end
22 end27 end
23end28end
lib/docs/scrapers/lodash.rb+5−0
@@ -32,5 +32,10 @@ module Docs · 31 unmodified lines
32 self.release = '2.4.2'32 self.release = '2.4.2'
33 self.base_url = "https://lodash.com/docs/#{release}"33 self.base_url = "https://lodash.com/docs/#{release}"
34 end34 end
35
36 def get_latest_version(opts)
37 doc = fetch_doc('https://lodash.com/docs/', opts)
38 doc.at_css('#version > option[selected]').content
39 end
35 end40 end
36end41end
lib/docs/scrapers/love.rb+5−0
@@ -39,5 +39,10 @@ module Docs · 38 unmodified lines
39 &copy; 2006&ndash;2016 L&Ouml;VE Development Team<br>39 &copy; 2006&ndash;2016 L&Ouml;VE Development Team<br>
40 Licensed under the GNU Free Documentation License, Version 1.3.40 Licensed under the GNU Free Documentation License, Version 1.3.
41 HTML41 HTML
42
43 def get_latest_version(opts)
44 doc = fetch_doc('https://love2d.org/wiki/Version_History', opts)
45 doc.at_css('#mw-content-text table a').content
46 end
42 end47 end
43end48end
lib/docs/scrapers/lua.rb+5−0
@@ -26,5 +26,10 @@ module Docs · 25 unmodified lines
26 self.release = '5.1.5'26 self.release = '5.1.5'
27 self.base_url = 'https://www.lua.org/manual/5.1/'27 self.base_url = 'https://www.lua.org/manual/5.1/'
28 end28 end
29
30 def get_latest_version(opts)
31 doc = fetch_doc('https://www.lua.org/manual/', opts)
32 doc.at_css('p.menubar > a').content
33 end
29 end34 end
30end35end
lib/docs/scrapers/marionette.rb+4−0
@@ -38,5 +38,9 @@ module Docs · 37 unmodified lines
38 38
39 html_filters.push 'marionette/entries_v2'39 html_filters.push 'marionette/entries_v2'
40 end40 end
41
42 def get_latest_version(opts)
43 get_npm_version('backbone.marionette', opts)
44 end
41 end45 end
42end46end
lib/docs/scrapers/markdown.rb+4−0
@@ -13,5 +13,9 @@ module Docs · 12 unmodified lines
13 &copy; 2004 John Gruber<br>13 &copy; 2004 John Gruber<br>
14 Licensed under the BSD License.14 Licensed under the BSD License.
15 HTML15 HTML
16
17 def get_latest_version(opts)
18 '1.0.0'
19 end
16 end20 end
17end21end
lib/docs/scrapers/matplotlib.rb+4−0
@@ -64,5 +64,9 @@ module Docs · 63 unmodified lines
64 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"64 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
65 ]65 ]
66 end66 end
67
68 def get_latest_version(opts)
69 get_latest_github_release('matplotlib', 'matplotlib', opts)
70 end
67 end71 end
68end72end
lib/docs/scrapers/mdn/css.rb+2−0
@@ -6,6 +6,8 @@ module Docs · 5 unmodified lines
6 6
7 html_filters.push 'css/clean_html', 'css/entries', 'title'7 html_filters.push 'css/clean_html', 'css/entries', 'title'
8 8
9 options[:mdn_tag] = 'CSS'
10
9 options[:root_title] = 'CSS'11 options[:root_title] = 'CSS'
10 12
11 options[:skip] = %w(/CSS3 /Media/Visual /paged_media /Media/TV /Media/Tactile)13 options[:skip] = %w(/CSS3 /Media/Visual /paged_media /Media/TV /Media/Tactile)
lib/docs/scrapers/mdn/dom.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
8 8
9 html_filters.push 'dom/clean_html', 'dom/entries', 'title'9 html_filters.push 'dom/clean_html', 'dom/entries', 'title'
10 10
11 options[:mdn_tag] = 'XSLT_Reference'
12
11 options[:root_title] = 'DOM'13 options[:root_title] = 'DOM'
12 14
13 options[:skip] = %w(15 options[:skip] = %w(
lib/docs/scrapers/mdn/dom_events.rb+2−0
@@ -9,6 +9,8 @@ module Docs · 8 unmodified lines
9 html_filters.insert_after 'clean_html', 'dom_events/clean_html'9 html_filters.insert_after 'clean_html', 'dom_events/clean_html'
10 html_filters.push 'dom_events/entries', 'title'10 html_filters.push 'dom_events/entries', 'title'
11 11
12 options[:mdn_tag] = 'events'
13
12 options[:root_title] = 'DOM Events'14 options[:root_title] = 'DOM Events'
13 15
14 options[:skip] = %w(/MozOrientation)16 options[:skip] = %w(/MozOrientation)
lib/docs/scrapers/mdn/html.rb+2−0
@@ -7,6 +7,8 @@ module Docs · 6 unmodified lines
7 7
8 html_filters.push 'html/clean_html', 'html/entries', 'title'8 html_filters.push 'html/clean_html', 'html/entries', 'title'
9 9
10 options[:mdn_tag] = 'HTML'
11
10 options[:root_title] = 'HTML'12 options[:root_title] = 'HTML'
11 13
12 options[:title] = ->(filter) do14 options[:title] = ->(filter) do
lib/docs/scrapers/mdn/javascript.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
8 8
9 html_filters.push 'javascript/clean_html', 'javascript/entries', 'title'9 html_filters.push 'javascript/clean_html', 'javascript/entries', 'title'
10 10
11 options[:mdn_tag] = 'JavaScript'
12
11 options[:root_title] = 'JavaScript'13 options[:root_title] = 'JavaScript'
12 14
13 # Don't want15 # Don't want
lib/docs/scrapers/mdn/mdn.rb+5−0
@@ -21,6 +21,11 @@ module Docs · 20 unmodified lines
21 Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.21 Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
22 HTML22 HTML
23 23
24 def get_latest_version(opts)
25 json = fetch_json("https://developer.mozilla.org/en-US/docs/feeds/json/tag/#{options[:mdn_tag]}", opts)
26 DateTime.parse(json[0]['pubdate']).to_time.to_i
27 end
28
24 private29 private
25 30
26 def process_response?(response)31 def process_response?(response)
lib/docs/scrapers/mdn/svg.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
8 8
9 html_filters.push 'svg/clean_html', 'svg/entries', 'title'9 html_filters.push 'svg/clean_html', 'svg/entries', 'title'
10 10
11 options[:mdn_tag] = 'XSLT_Reference'
12
11 options[:root_title] = 'SVG'13 options[:root_title] = 'SVG'
12 14
13 options[:title] = ->(filter) do15 options[:title] = ->(filter) do
lib/docs/scrapers/mdn/xslt_xpath.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
8 8
9 html_filters.push 'xslt_xpath/clean_html', 'xslt_xpath/entries', 'title'9 html_filters.push 'xslt_xpath/clean_html', 'xslt_xpath/entries', 'title'
10 10
11 options[:mdn_tag] = 'XSLT_Reference'
12
11 options[:root_title] = 'XSLT'13 options[:root_title] = 'XSLT'
12 14
13 options[:only_patterns] = [/\A\/XSLT/, /\A\/XPath/]15 options[:only_patterns] = [/\A\/XSLT/, /\A\/XPath/]
lib/docs/scrapers/meteor.rb+5−0
@@ -45,5 +45,10 @@ module Docs · 44 unmodified lines
45 self.base_urls = ['https://guide.meteor.com/v1.3/', "https://docs.meteor.com/v#{self.release}/"]45 self.base_urls = ['https://guide.meteor.com/v1.3/', "https://docs.meteor.com/v#{self.release}/"]
46 options[:fix_urls] = nil46 options[:fix_urls] = nil
47 end47 end
48
49 def get_latest_version(opts)
50 doc = fetch_doc('https://docs.meteor.com/#/full/', opts)
51 doc.at_css('select.version-select > option').content
52 end
48 end53 end
49end54end
lib/docs/scrapers/mocha.rb+4−0
@@ -18,5 +18,9 @@ module Docs · 17 unmodified lines
18 &copy; 2011&ndash;2018 JS Foundation and contributors<br>18 &copy; 2011&ndash;2018 JS Foundation and contributors<br>
19 Licensed under the Creative Commons Attribution 4.0 International License.19 Licensed under the Creative Commons Attribution 4.0 International License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 get_npm_version('mocha', opts)
24 end
21 end25 end
22end26end
lib/docs/scrapers/modernizr.rb+4−0
@@ -15,5 +15,9 @@ module Docs · 14 unmodified lines
15 &copy; 2009&ndash;2017 The Modernizr team<br>15 &copy; 2009&ndash;2017 The Modernizr team<br>
16 Licensed under the MIT License.16 Licensed under the MIT License.
17 HTML17 HTML
18
19 def get_latest_version(opts)
20 get_npm_version('modernizr', opts)
21 end
18 end22 end
19end23end
lib/docs/scrapers/moment.rb+5−0
@@ -22,5 +22,10 @@ module Docs · 21 unmodified lines
22 &copy; JS Foundation and other contributors<br>22 &copy; JS Foundation and other contributors<br>
23 Licensed under the MIT License.23 Licensed under the MIT License.
24 HTML24 HTML
25
26 def get_latest_version(opts)
27 doc = fetch_doc('http://momentjs.com/', opts)
28 doc.at_css('.hero-title > h1 > span').content
29 end
25 end30 end
26end31end
lib/docs/scrapers/mongoose.rb+6−0
@@ -26,5 +26,11 @@ module Docs · 25 unmodified lines
26 &copy; 2010 LearnBoost<br>26 &copy; 2010 LearnBoost<br>
27 Licensed under the MIT License.27 Licensed under the MIT License.
28 HTML28 HTML
29
30 def get_latest_version(opts)
31 doc = fetch_doc('https://mongoosejs.com/docs/', opts)
32 label = doc.at_css('.pure-menu-link').content.strip
33 label.sub(/Version /, '')
34 end
29 end35 end
30end36end
lib/docs/scrapers/nginx.rb+6−0
@@ -25,5 +25,11 @@ module Docs · 24 unmodified lines
25 &copy; 2011-2018 Nginx, Inc.<br>25 &copy; 2011-2018 Nginx, Inc.<br>
26 Licensed under the BSD License.26 Licensed under the BSD License.
27 HTML27 HTML
28
29 def get_latest_version(opts)
30 doc = fetch_doc('https://nginx.org/en/download.html', opts)
31 table = doc.at_css('#content > table').inner_html
32 table.scan(/nginx-([0-9.]+)</)[0][0]
33 end
28 end34 end
29end35end
lib/docs/scrapers/nginx_lua_module.rb+9−0
@@ -4,6 +4,9 @@ module Docs · 3 unmodified lines
4 self.slug = 'nginx_lua_module'4 self.slug = 'nginx_lua_module'
5 self.release = '0.10.13'5 self.release = '0.10.13'
6 self.base_url = "https://github.com/openresty/lua-nginx-module/tree/v#{self.release}/"6 self.base_url = "https://github.com/openresty/lua-nginx-module/tree/v#{self.release}/"
7 self.links = {
8 code: 'https://github.com/openresty/lua-nginx-module'
9 }
7 10
8 html_filters.push 'nginx_lua_module/clean_html', 'nginx_lua_module/entries', 'title'11 html_filters.push 'nginx_lua_module/clean_html', 'nginx_lua_module/entries', 'title'
9 12
@@ -15,5 +18,11 @@ module Docs · 5 unmodified lines
15 &copy; 2009&ndash;2018 Yichun "agentzh" Zhang (章亦春), OpenResty Inc.<br>18 &copy; 2009&ndash;2018 Yichun "agentzh" Zhang (章亦春), OpenResty Inc.<br>
16 Licensed under the BSD License.19 Licensed under the BSD License.
17 HTML20 HTML
21
22 def get_latest_version(opts)
23 tags = get_github_tags('openresty', 'lua-nginx-module', opts)
24 tag = tags.find {|tag| !tag['name'].include?('rc')}
25 tag['name'][1..-1]
26 end
18 end27 end
19end28end
lib/docs/scrapers/nim.rb+5−0
@@ -17,5 +17,10 @@ module Docs · 16 unmodified lines
17 &copy; 2006&ndash;2018 Andreas Rumpf<br>17 &copy; 2006&ndash;2018 Andreas Rumpf<br>
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 doc = fetch_doc('https://nim-lang.org/docs/overview.html', opts)
23 doc.at_css('.container > .docinfo > tbody > tr:last-child > td').content.strip
24 end
20 end25 end
21end26end
lib/docs/scrapers/node.rb+5−0
@@ -46,5 +46,10 @@ module Docs · 45 unmodified lines
46 self.release = '4.9.1'46 self.release = '4.9.1'
47 self.base_url = 'https://nodejs.org/dist/latest-v4.x/docs/api/'47 self.base_url = 'https://nodejs.org/dist/latest-v4.x/docs/api/'
48 end48 end
49
50 def get_latest_version(opts)
51 doc = fetch_doc('https://nodejs.org/en/', opts)
52 doc.at_css('#home-intro > .home-downloadblock:last-of-type > a')['data-version'][1..-1]
53 end
49 end54 end
50end55end
lib/docs/scrapers/nokogiri2.rb+4−0
@@ -19,5 +19,9 @@ module Docs · 18 unmodified lines
19 Patrick Mahoney, Yoko Harada, Akinori Musha, John Shahid, Lars Kanis<br>19 Patrick Mahoney, Yoko Harada, Akinori Musha, John Shahid, Lars Kanis<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_latest_github_release('sparklemotion', 'nokogiri', opts)
25 end
22 end26 end
23end27end
lib/docs/scrapers/npm.rb+4−0
@@ -29,5 +29,9 @@ module Docs · 28 unmodified lines
29 Licensed under the npm License.<br>29 Licensed under the npm License.<br>
30 npm is a trademark of npm, Inc.30 npm is a trademark of npm, Inc.
31 HTML31 HTML
32
33 def get_latest_version(opts)
34 get_latest_github_release('npm', 'cli', opts)
35 end
32 end36 end
33end37end
lib/docs/scrapers/numpy.rb+4−0
@@ -49,5 +49,9 @@ module Docs · 48 unmodified lines
49 self.release = '1.10.4'49 self.release = '1.10.4'
50 self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"50 self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"
51 end51 end
52
53 def get_latest_version(opts)
54 get_latest_github_release('numpy', 'numpy', opts)
55 end
52 end56 end
53end57end
lib/docs/scrapers/openjdk.rb+20−0
@@ -86,5 +86,25 @@ module Docs · 85 unmodified lines
86 def read_file(path)86 def read_file(path)
87 File.read(path).force_encoding('iso-8859-1').encode('utf-8') rescue nil87 File.read(path).force_encoding('iso-8859-1').encode('utf-8') rescue nil
88 end88 end
89
90 def get_latest_version(opts)
91 latest_version = 8
92 current_attempt = latest_version
93 attempts = 0
94
95 while attempts < 3
96 current_attempt += 1
97
98 doc = fetch_doc("https://packages.debian.org/sid/openjdk-#{current_attempt}-doc", opts)
99 if doc.at_css('.perror').nil?
100 latest_version = current_attempt
101 attempts = 0
102 else
103 attempts += 1
104 end
105 end
106
107 latest_version
108 end
89 end109 end
90end110end
lib/docs/scrapers/opentsdb.rb+4−0
@@ -18,5 +18,9 @@ module Docs · 17 unmodified lines
18 &copy; 2010&ndash;2016 The OpenTSDB Authors<br>18 &copy; 2010&ndash;2016 The OpenTSDB Authors<br>
19 Licensed under the GNU LGPLv2.1+ and GPLv3+ licenses.19 Licensed under the GNU LGPLv2.1+ and GPLv3+ licenses.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 get_latest_github_release('OpenTSDB', 'opentsdb', opts)
24 end
21 end25 end
22end26end
lib/docs/scrapers/padrino.rb+4−0
@@ -23,5 +23,9 @@ module Docs · 22 unmodified lines
23 stub 'index2' do23 stub 'index2' do
24 request_one(url_for('index')).body24 request_one(url_for('index')).body
25 end25 end
26
27 def get_latest_version(opts)
28 get_github_tags('padrino', 'padrino-framework', opts)[0]['name']
29 end
26 end30 end
27end31end
lib/docs/scrapers/pandas.rb+6−0
@@ -49,5 +49,11 @@ module Docs · 48 unmodified lines
49 self.release = '0.18.1'49 self.release = '0.18.1'
50 self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"50 self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"
51 end51 end
52
53 def get_latest_version(opts)
54 doc = fetch_doc('http://pandas.pydata.org/pandas-docs/stable/', opts)
55 label = doc.at_css('.body > .section > p').content
56 label.scan(/Version: ([0-9.]+)/)[0][0]
57 end
52 end58 end
53end59end
lib/docs/scrapers/perl.rb+6−0
@@ -43,5 +43,11 @@ module Docs · 42 unmodified lines
43 self.release = '5.20.2'43 self.release = '5.20.2'
44 self.base_url = "https://perldoc.perl.org/#{self.release}/"44 self.base_url = "https://perldoc.perl.org/#{self.release}/"
45 end45 end
46
47 def get_latest_version(opts)
48 doc = fetch_doc('https://perldoc.perl.org/', opts)
49 header = doc.at_css('h2.h1').content
50 header.scan(/Perl ([0-9.]+)/)[0][0]
51 end
46 end52 end
47end53end
lib/docs/scrapers/phalcon.rb+5−0
@@ -29,5 +29,10 @@ module Docs · 28 unmodified lines
29 self.release = '2.0.13'29 self.release = '2.0.13'
30 self.base_url = 'https://docs.phalconphp.com/en/2.0.0/'30 self.base_url = 'https://docs.phalconphp.com/en/2.0.0/'
31 end31 end
32
33 def get_latest_version(opts)
34 doc = fetch_doc('https://docs.phalconphp.com/', opts)
35 doc.at_css('.header__lang.expand > div > ul > li > a').content
36 end
32 end37 end
33end38end
lib/docs/scrapers/phaser.rb+4−0
@@ -25,5 +25,9 @@ module Docs · 24 unmodified lines
25 &copy; 2016 Richard Davey, Photon Storm Ltd.<br>25 &copy; 2016 Richard Davey, Photon Storm Ltd.<br>
26 Licensed under the MIT License.26 Licensed under the MIT License.
27 HTML27 HTML
28
29 def get_latest_version(opts)
30 get_latest_github_release('photonstorm', 'phaser', opts)
31 end
28 end32 end
29end33end
lib/docs/scrapers/phoenix.rb+5−0
@@ -46,5 +46,10 @@ module Docs · 45 unmodified lines
46 HTML46 HTML
47 end47 end
48 }48 }
49
50 def get_latest_version(opts)
51 doc = fetch_doc('https://hexdocs.pm/phoenix/Phoenix.html', opts)
52 doc.at_css('.sidebar-projectVersion').content.strip[1..-1]
53 end
49 end54 end
50end55end
lib/docs/scrapers/php.rb+6−0
@@ -66,5 +66,11 @@ module Docs · 65 unmodified lines
66 &copy; 1997&ndash;2018 The PHP Documentation Group<br>66 &copy; 1997&ndash;2018 The PHP Documentation Group<br>
67 Licensed under the Creative Commons Attribution License v3.0 or later.67 Licensed under the Creative Commons Attribution License v3.0 or later.
68 HTML68 HTML
69
70 def get_latest_version(opts)
71 doc = fetch_doc('https://secure.php.net/manual/en/doc.changelog.php', opts)
72 label = doc.at_css('tbody.gen-changelog > tr > td').content
73 label.split(',').last.strip
74 end
69 end75 end
70end76end
lib/docs/scrapers/phpunit.rb+6−0
@@ -37,5 +37,11 @@ module Docs · 36 unmodified lines
37 self.release = '4.8'37 self.release = '4.8'
38 self.base_url = "https://phpunit.de/manual/#{release}/en/"38 self.base_url = "https://phpunit.de/manual/#{release}/en/"
39 end39 end
40
41 def get_latest_version(opts)
42 doc = fetch_doc('https://phpunit.readthedocs.io/', opts)
43 label = doc.at_css('.rst-current-version').content.strip
44 label.scan(/v: ([0-9.]+)/)[0][0]
45 end
40 end46 end
41end47end
lib/docs/scrapers/postgresql.rb+6−0
@@ -80,5 +80,11 @@ module Docs · 79 unmodified lines
80 80
81 html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'81 html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'
82 end82 end
83
84 def get_latest_version(opts)
85 doc = fetch_doc('https://www.postgresql.org/docs/current/index.html', opts)
86 label = doc.at_css('#pgContentWrap h1.title').content
87 label.scan(/([0-9.]+)/)[0][0]
88 end
83 end89 end
84end90end
lib/docs/scrapers/pug.rb+4−0
@@ -18,6 +18,10 @@ module Docs · 17 unmodified lines
18 Licensed under the MIT license.18 Licensed under the MIT license.
19 HTML19 HTML
20 20
21 def get_latest_version(opts)
22 get_npm_version('pug', opts)
23 end
24
21 private25 private
22 26
23 def parse(response) # Hook here because Nokogori removes whitespace from textareas27 def parse(response) # Hook here because Nokogori removes whitespace from textareas
lib/docs/scrapers/puppeteer.rb+5−0
@@ -14,5 +14,10 @@ module Docs · 13 unmodified lines
14 &copy; 2017 Google Inc<br>14 &copy; 2017 Google Inc<br>
15 Licensed under the Apache License 2.0.15 Licensed under the Apache License 2.0.
16 HTML16 HTML
17
18 def get_latest_version(opts)
19 contents = get_github_file_contents('GoogleChrome', 'puppeteer', 'README.md', opts)
20 contents.scan(/\/v([0-9.]+)\/docs\/api\.md/)[0][0]
21 end
17 end22 end
18end23end
lib/docs/scrapers/pygame.rb+5−0
@@ -2,6 +2,7 @@ module Docs · 1 unmodified line
2 class Pygame < UrlScraper2 class Pygame < UrlScraper
3 self.type = 'simple'3 self.type = 'simple'
4 self.release = '1.9.4'4 self.release = '1.9.4'
5 self.base_url = 'https://www.pygame.org/docs/'
5 self.root_path = 'py-modindex.html'6 self.root_path = 'py-modindex.html'
6 self.links = {7 self.links = {
7 home: 'https://www.pygame.org/',8 home: 'https://www.pygame.org/',
@@ -16,5 +17,9 @@ module Docs · 8 unmodified lines
16 &copy; Pygame Developpers.<br>17 &copy; Pygame Developpers.<br>
17 Licensed under the GNU LGPL License version 2.1.18 Licensed under the GNU LGPL License version 2.1.
18 HTML19 HTML
20
21 def get_latest_version(opts)
22 get_latest_github_release('pygame', 'pygame', opts)
23 end
19 end24 end
20end25end
lib/docs/scrapers/python.rb+5−0
@@ -50,5 +50,10 @@ module Docs · 49 unmodified lines
50 50
51 html_filters.push 'python/entries_v2', 'sphinx/clean_html', 'python/clean_html'51 html_filters.push 'python/entries_v2', 'sphinx/clean_html', 'python/clean_html'
52 end52 end
53
54 def get_latest_version(opts)
55 doc = fetch_doc('https://docs.python.org/', opts)
56 doc.at_css('.version_switcher_placeholder').content
57 end
53 end58 end
54end59end
lib/docs/scrapers/q.rb+4−0
@@ -19,5 +19,9 @@ module Docs · 18 unmodified lines
19 &copy; 2009&ndash;2017 Kristopher Michael Kowal<br>19 &copy; 2009&ndash;2017 Kristopher Michael Kowal<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_npm_version('q', opts)
25 end
22 end26 end
23end27end
lib/docs/scrapers/qt.rb+5−0
@@ -117,5 +117,10 @@ module Docs · 116 unmodified lines
117 self.release = '5.6'117 self.release = '5.6'
118 self.base_url = 'https://doc.qt.io/qt-5.6/'118 self.base_url = 'https://doc.qt.io/qt-5.6/'
119 end119 end
120
121 def get_latest_version(opts)
122 doc = fetch_doc('https://doc.qt.io/qt-5/index.html', opts)
123 doc.at_css('.mainContent h1.title').content.sub(/Qt /, '')
124 end
120 end125 end
121end126end
lib/docs/scrapers/ramda.rb+5−0
@@ -15,6 +15,11 @@ module Docs · 14 unmodified lines
15 &copy; 2013&ndash;2016 Scott Sauyet and Michael Hurley<br>15 &copy; 2013&ndash;2016 Scott Sauyet and Michael Hurley<br>
16 Licensed under the MIT License.16 Licensed under the MIT License.
17 HTML17 HTML
18
19 def get_latest_version(opts)
20 doc = fetch_doc('https://ramdajs.com/docs/', opts)
21 doc.at_css('.navbar-brand > .version').content[1..-1]
22 end
18 end23 end
19end24end
20 25
lib/docs/scrapers/rdoc/minitest.rb+5−0
@@ -21,5 +21,10 @@ module Docs · 20 unmodified lines
21 &copy; Ryan Davis, seattle.rb<br>21 &copy; Ryan Davis, seattle.rb<br>
22 Licensed under the MIT License.22 Licensed under the MIT License.
23 HTML23 HTML
24
25 def get_latest_version(opts)
26 contents = get_github_file_contents('seattlerb', 'minitest', 'History.rdoc', opts)
27 contents.scan(/([0-9.]+)/)[0][0]
28 end
24 end29 end
25end30end
lib/docs/scrapers/rdoc/rails.rb+4−0
@@ -93,5 +93,9 @@ module Docs · 92 unmodified lines
93 version '4.1' do93 version '4.1' do
94 self.release = '4.1.16'94 self.release = '4.1.16'
95 end95 end
96
97 def get_latest_version(opts)
98 get_latest_github_release('rails', 'rails', opts)
99 end
96 end100 end
97end101end
lib/docs/scrapers/rdoc/ruby.rb+11−0
@@ -84,5 +84,16 @@ module Docs · 83 unmodified lines
84 version '2.2' do84 version '2.2' do
85 self.release = '2.2.10'85 self.release = '2.2.10'
86 end86 end
87
88 def get_latest_version(opts)
89 tags = get_github_tags('ruby', 'ruby', opts)
90 tags.each do |tag|
91 version = tag['name'].gsub(/_/, '.')[1..-1]
92
93 if !/^([0-9.]+)$/.match(version).nil? && version.count('.') == 2
94 return version
95 end
96 end
97 end
87 end98 end
88end99end
lib/docs/scrapers/react.rb+5−0
@@ -30,5 +30,10 @@ module Docs · 29 unmodified lines
30 &copy; 2013&ndash;present Facebook Inc.<br>30 &copy; 2013&ndash;present Facebook Inc.<br>
31 Licensed under the Creative Commons Attribution 4.0 International Public License.31 Licensed under the Creative Commons Attribution 4.0 International Public License.
32 HTML32 HTML
33
34 def get_latest_version(opts)
35 doc = fetch_doc('https://reactjs.org/docs/getting-started.html', opts)
36 doc.at_css('a[href="/versions"]').content.strip[1..-1]
37 end
33 end38 end
34end39end
lib/docs/scrapers/react_native.rb+5−0
@@ -30,5 +30,10 @@ module Docs · 29 unmodified lines
30 &copy; 2015&ndash;2018 Facebook Inc.<br>30 &copy; 2015&ndash;2018 Facebook Inc.<br>
31 Licensed under the Creative Commons Attribution 4.0 International Public License.31 Licensed under the Creative Commons Attribution 4.0 International Public License.
32 HTML32 HTML
33
34 def get_latest_version(opts)
35 doc = fetch_doc('https://facebook.github.io/react-native/docs/getting-started.html', opts)
36 doc.at_css('header > a > h3').content
37 end
33 end38 end
34end39end
lib/docs/scrapers/redis.rb+6−0
@@ -19,5 +19,11 @@ module Docs · 18 unmodified lines
19 &copy; 2009&ndash;2018 Salvatore Sanfilippo<br>19 &copy; 2009&ndash;2018 Salvatore Sanfilippo<br>
20 Licensed under the Creative Commons Attribution-ShareAlike License 4.0.20 Licensed under the Creative Commons Attribution-ShareAlike License 4.0.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 body = fetch('http://download.redis.io/redis-stable/00-RELEASENOTES', opts)
25 body = body.lines[1..-1].join
26 body.scan(/Redis ([0-9.]+)/)[0][0]
27 end
22 end28 end
23end29end
lib/docs/scrapers/redux.rb+4−0
@@ -20,5 +20,9 @@ module Docs · 19 unmodified lines
20 stub '' do20 stub '' do
21 request_one('http://redux.js.org/index.html').body21 request_one('http://redux.js.org/index.html').body
22 end22 end
23
24 def get_latest_version(opts)
25 get_npm_version('redux', opts)
26 end
23 end27 end
24end28end
lib/docs/scrapers/relay.rb+5−0
@@ -18,5 +18,10 @@ module Docs · 17 unmodified lines
18 &copy; 2013&ndash;present Facebook Inc.<br>18 &copy; 2013&ndash;present Facebook Inc.<br>
19 Licensed under the BSD License.19 Licensed under the BSD License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 doc = fetch_doc('http://facebook.github.io/relay/en/', opts)
24 doc.at_css('header > a > h3').content[1..-1]
25 end
21 end26 end
22end27end
lib/docs/scrapers/requirejs.rb+4−0
@@ -30,5 +30,9 @@ module Docs · 29 unmodified lines
30 &copy; jQuery Foundation and other contributors<br>30 &copy; jQuery Foundation and other contributors<br>
31 Licensed under the MIT License.31 Licensed under the MIT License.
32 HTML32 HTML
33
34 def get_latest_version(opts)
35 get_npm_version('requirejs', opts)
36 end
33 end37 end
34end38end
lib/docs/scrapers/rethinkdb.rb+4−0
@@ -58,6 +58,10 @@ module Docs · 57 unmodified lines
58 CODE58 CODE
59 end59 end
60 60
61 def get_latest_version(opts)
62 get_latest_github_release('rethinkdb', 'rethinkdb', opts)
63 end
64
61 private65 private
62 66
63 def process_response?(response)67 def process_response?(response)
lib/docs/scrapers/rust.rb+6−0
@@ -39,6 +39,12 @@ module Docs · 38 unmodified lines
39 Licensed under the Apache License, Version 2.0 or the MIT license, at your option.39 Licensed under the Apache License, Version 2.0 or the MIT license, at your option.
40 HTML40 HTML
41 41
42 def get_latest_version(opts)
43 doc = fetch_doc('https://www.rust-lang.org/', opts)
44 label = doc.at_css('.button-download + p > a').content
45 label.sub(/Version /, '')
46 end
47
42 private48 private
43 49
44 REDIRECT_RGX = /http-equiv="refresh"/i50 REDIRECT_RGX = /http-equiv="refresh"/i
lib/docs/scrapers/sass.rb+4−0
@@ -23,5 +23,9 @@ module Docs · 22 unmodified lines
23 &copy; 2006&ndash;2016 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein<br>23 &copy; 2006&ndash;2016 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein<br>
24 Licensed under the MIT License.24 Licensed under the MIT License.
25 HTML25 HTML
26
27 def get_latest_version(opts)
28 get_latest_github_release('sass', 'libsass', opts)
29 end
26 end30 end
27end31end
lib/docs/scrapers/scikit_image.rb+5−0
@@ -20,5 +20,10 @@ module Docs · 19 unmodified lines
20 &copy; 2011 the scikit-image team<br>20 &copy; 2011 the scikit-image team<br>
21 Licensed under the BSD 3-clause License.21 Licensed under the BSD 3-clause License.
22 HTML22 HTML
23
24 def get_latest_version(opts)
25 tags = get_github_tags('scikit-image', 'scikit-image', opts)
26 tags[0]['name'][1..-1]
27 end
23 end28 end
24end29end
lib/docs/scrapers/scikit_learn.rb+4−0
@@ -25,5 +25,9 @@ module Docs · 24 unmodified lines
25 Licensed under the 3-clause BSD License.25 Licensed under the 3-clause BSD License.
26 HTML26 HTML
27 27
28 def get_latest_version(opts)
29 doc = fetch_doc('https://scikit-learn.org/stable/documentation.html', opts)
30 doc.at_css('.body h1').content.scan(/([0-9.]+)/)[0][0]
31 end
28 end32 end
29end33end
lib/docs/scrapers/sinon.rb+5−0
@@ -52,5 +52,10 @@ module Docs · 51 unmodified lines
52 self.release = '1.17.7'52 self.release = '1.17.7'
53 self.base_url = "https://sinonjs.org/releases/v#{release}/"53 self.base_url = "https://sinonjs.org/releases/v#{release}/"
54 end54 end
55
56 def get_latest_version(opts)
57 body = fetch('https://sinonjs.org/', opts)
58 body.scan(/\/releases\/v([0-9.]+)/)[0][0]
59 end
55 end60 end
56end61end
lib/docs/scrapers/socketio.rb+4−0
@@ -20,5 +20,9 @@ module Docs · 19 unmodified lines
20 &copy; 2014&ndash;2015 Automattic<br>20 &copy; 2014&ndash;2015 Automattic<br>
21 Licensed under the MIT License.21 Licensed under the MIT License.
22 HTML22 HTML
23
24 def get_latest_version(opts)
25 get_npm_version('socket.io', opts)
26 end
23 end27 end
24end28end
lib/docs/scrapers/sqlite.rb+5−0
@@ -41,6 +41,11 @@ module Docs · 40 unmodified lines
41 41
42 options[:attribution] = 'SQLite is in the Public Domain.'42 options[:attribution] = 'SQLite is in the Public Domain.'
43 43
44 def get_latest_version(opts)
45 doc = fetch_doc('https://sqlite.org/chronology.html', opts)
46 doc.at_css('#chrontab > tbody > tr > td:last-child > a').content
47 end
48
44 private49 private
45 50
46 def parse(response)51 def parse(response)
lib/docs/scrapers/statsmodels.rb+4−0
@@ -21,5 +21,9 @@ module Docs · 20 unmodified lines
21 Licensed under the 3-clause BSD License.21 Licensed under the 3-clause BSD License.
22 HTML22 HTML
23 23
24 def get_latest_version(opts)
25 doc = fetch_doc('http://www.statsmodels.org/stable/', opts)
26 doc.at_css('.sphinxsidebarwrapper h3 + p > b').content[1..-1]
27 end
24 end28 end
25end29end
lib/docs/scrapers/support_tables.rb+6−0
@@ -178,5 +178,11 @@ module Docs · 177 unmodified lines
178 </p>178 </p>
179 </div>179 </div>
180 HTML180 HTML
181
182 def get_latest_version(opts)
183 body = fetch('https://feeds.feedburner.com/WhenCanIUse?format=xml', opts)
184 timestamp = body.scan(/<updated>([^<]+)<\/updated>/)[0][0]
185 DateTime.parse(timestamp).to_time.to_i
186 end
181 end187 end
182end188end
lib/docs/scrapers/symfony.rb+4−0
@@ -70,5 +70,9 @@ module Docs · 69 unmodified lines
70 self.release = '2.7.35'70 self.release = '2.7.35'
71 self.base_url = "https://api.symfony.com/#{version}/"71 self.base_url = "https://api.symfony.com/#{version}/"
72 end72 end
73
74 def get_latest_version(opts)
75 get_latest_github_release('symfony', 'symfony', opts)
76 end
73 end77 end
74end78end
lib/docs/scrapers/tcl_tk.rb+5−0
@@ -25,5 +25,10 @@ module Docs · 24 unmodified lines
25 options[:attribution] = <<-HTML25 options[:attribution] = <<-HTML
26 Licensed under <a href="http://tcl.tk/software/tcltk/license.html">Tcl/Tk terms</a>26 Licensed under <a href="http://tcl.tk/software/tcltk/license.html">Tcl/Tk terms</a>
27 HTML27 HTML
28
29 def get_latest_version(opts)
30 doc = fetch_doc('https://www.tcl.tk/man/tcl/contents.htm', opts)
31 doc.at_css('h2').content.scan(/Tk([0-9.]+)/)[0][0]
32 end
28 end33 end
29end34end
lib/docs/scrapers/tensorflow.rb+4−0
@@ -56,6 +56,10 @@ module Docs · 55 unmodified lines
56 /\Aextend/]56 /\Aextend/]
57 end57 end
58 58
59 def get_latest_version(opts)
60 get_latest_github_release('tensorflow', 'tensorflow', opts)
61 end
62
59 private63 private
60 64
61 def parse(response)65 def parse(response)
lib/docs/scrapers/terraform.rb+5−0
@@ -18,5 +18,10 @@ module Docs · 17 unmodified lines
18 &copy; 2018 HashiCorp</br>18 &copy; 2018 HashiCorp</br>
19 Licensed under the MPL 2.0 License.19 Licensed under the MPL 2.0 License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 contents = get_github_file_contents('hashicorp', 'terraform-website', 'content/config.rb', opts)
24 contents.scan(/version\s+=\s+"([0-9.]+)"/)[0][0]
25 end
21 end26 end
22end27end
lib/docs/scrapers/twig.rb+5−0
@@ -28,5 +28,10 @@ module Docs · 27 unmodified lines
28 self.release = '1.34.3'28 self.release = '1.34.3'
29 self.base_url = 'https://twig.symfony.com/doc/1.x/'29 self.base_url = 'https://twig.symfony.com/doc/1.x/'
30 end30 end
31
32 def get_latest_version(opts)
33 tags = get_github_tags('twigphp', 'Twig', opts)
34 tags[0]['name'][1..-1]
35 end
31 end36 end
32end37end
lib/docs/scrapers/typescript.rb+4−0
@@ -24,6 +24,10 @@ module Docs · 23 unmodified lines
24 &copy; Microsoft and other contributors<br>24 &copy; Microsoft and other contributors<br>
25 Licensed under the Apache License, Version 2.0.25 Licensed under the Apache License, Version 2.0.
26 HTML26 HTML
27
28 def get_latest_version(opts)
29 get_latest_github_release('Microsoft', 'TypeScript', opts)
30 end
27 end31 end
28end32end
29 33
lib/docs/scrapers/underscore.rb+5−0
@@ -20,5 +20,10 @@ module Docs · 19 unmodified lines
20 &copy; 2009&ndash;2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters &amp; Editors<br>20 &copy; 2009&ndash;2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters &amp; Editors<br>
21 Licensed under the MIT License.21 Licensed under the MIT License.
22 HTML22 HTML
23
24 def get_latest_version(opts)
25 doc = fetch_doc('https://underscorejs.org/', opts)
26 doc.at_css('.version').content[1...-1]
27 end
23 end28 end
24end29end
lib/docs/scrapers/vagrant.rb+5−0
@@ -18,5 +18,10 @@ module Docs · 17 unmodified lines
18 &copy; 2010&ndash;2018 Mitchell Hashimoto<br>18 &copy; 2010&ndash;2018 Mitchell Hashimoto<br>
19 Licensed under the MPL 2.0 License.19 Licensed under the MPL 2.0 License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 contents = get_github_file_contents('hashicorp', 'vagrant', 'website/config.rb', opts)
24 contents.scan(/version\s+=\s+"([0-9.]+)"/)[0][0]
25 end
21 end26 end
22end27end
lib/docs/scrapers/vue.rb+4−0
@@ -32,5 +32,9 @@ module Docs · 31 unmodified lines
32 self.root_path = '/guide/index.html'32 self.root_path = '/guide/index.html'
33 self.initial_paths = %w(/api/index.html)33 self.initial_paths = %w(/api/index.html)
34 end34 end
35
36 def get_latest_version(opts)
37 get_latest_github_release('vuejs', 'vue', opts)
38 end
35 end39 end
36end40end
lib/docs/scrapers/vulkan.rb+5−0
@@ -20,5 +20,10 @@ module Docs · 19 unmodified lines
20 Licensed under the Creative Commons Attribution 4.0 International License.<br>20 Licensed under the Creative Commons Attribution 4.0 International License.<br>
21 Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc.21 Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc.
22 HTML22 HTML
23
24 def get_latest_version(opts)
25 tags = get_github_tags('KhronosGroup', 'Vulkan-Docs', opts)
26 tags[0]['name'][1..-1]
27 end
23 end28 end
24end29end
lib/docs/scrapers/webpack.rb+4−0
@@ -68,5 +68,9 @@ module Docs · 67 unmodified lines
68 Licensed under the MIT License.68 Licensed under the MIT License.
69 HTML69 HTML
70 end70 end
71
72 def get_latest_version(opts)
73 get_npm_version('webpack', opts)
74 end
71 end75 end
72end76end
lib/docs/scrapers/yarn.rb+4−0
@@ -20,5 +20,9 @@ module Docs · 19 unmodified lines
20 &copy; 2016&ndash;present Yarn Contributors<br>20 &copy; 2016&ndash;present Yarn Contributors<br>
21 Licensed under the BSD License.21 Licensed under the BSD License.
22 HTML22 HTML
23
24 def get_latest_version(opts)
25 get_latest_github_release('yarnpkg', 'yarn', opts)
26 end
23 end27 end
24end28end
lib/docs/scrapers/yii.rb+4−0
@@ -34,5 +34,9 @@ module Docs · 33 unmodified lines
34 34
35 options[:container] = '.grid_9'35 options[:container] = '.grid_9'
36 end36 end
37
38 def get_latest_version(opts)
39 get_latest_github_release('yiisoft', 'yii2', opts)
40 end
37 end41 end
38end42end
lib/tasks/assets.thor+3−0
@@ -14,6 +14,9 @@ class AssetsCLI < Thor · 13 unmodified lines
14 option :keep, type: :numeric, default: 0, desc: 'Number of old assets to keep'14 option :keep, type: :numeric, default: 0, desc: 'Number of old assets to keep'
15 option :verbose, type: :boolean15 option :verbose, type: :boolean
16 def compile16 def compile
17 load 'tasks/sprites.thor'
18 invoke 'sprites:generate', [], :remove_public_icons => true, :verbose => options[:verbose]
19
17 manifest.compile App.assets_compile20 manifest.compile App.assets_compile
18 manifest.clean(options[:keep]) if options[:clean]21 manifest.clean(options[:keep]) if options[:clean]
19 end22 end
lib/tasks/sprites.thor+211−0
@@ -0,0 +1,211 @@
1class SpritesCLI < Thor
2 def self.to_s
3 'Sprites'
4 end
5
6 def initialize(*args)
7 require 'docs'
8 require 'chunky_png'
9 require 'fileutils'
10 super
11 end
12
13 desc 'generate [--remove-public-icons] [--verbose]', 'Generate the documentation icon spritesheets'
14 option :remove_public_icons, type: :boolean, desc: 'Remove public/icons after generating the spritesheets'
15 option :verbose, type: :boolean
16 def generate
17 items = get_items
18 items_with_icons = items.select {|item| item[:has_icons]}
19 items_without_icons = items.select {|item| !item[:has_icons]}
20 icons_per_row = Math.sqrt(items_with_icons.length).ceil
21
22 bg_color = get_sidebar_background
23
24 items_with_icons.each_with_index do |item, index|
25 item[:row] = (index / icons_per_row).floor
26 item[:col] = index - item[:row] * icons_per_row
27
28 item[:icon_16] = get_icon(item[:path_16], 16)
29 item[:icon_32] = get_icon(item[:path_32], 32)
30
31 item[:dark_icon_fix] = needs_dark_icon_fix(item[:icon_32], bg_color)
32 end
33
34 log_details(items_with_icons, icons_per_row)
35
36 generate_spritesheet(16, items_with_icons, 'assets/images/sprites/docs.png') {|item| item[:icon_16]}
37 generate_spritesheet(32, items_with_icons, 'assets/images/sprites/docs@2x.png') {|item| item[:icon_32]}
38
39 # Add Mongoose's icon details to docs without custom icons
40 default_item = items_with_icons.find {|item| item[:type] == 'mongoose'}
41 items_without_icons.each do |item|
42 item[:row] = default_item[:row]
43 item[:col] = default_item[:col]
44 item[:dark_icon_fix] = default_item[:dark_icon_fix]
45 end
46
47 save_manifest(items, icons_per_row, 'assets/images/sprites/docs.json')
48
49 if options[:remove_public_icons]
50 logger.info('Removing public/icons')
51 FileUtils.rm_rf('public/icons')
52 end
53 end
54
55 private
56
57 def get_items
58 items = Docs.all.map do |doc|
59 base_path = "public/icons/docs/#{doc.slug}"
60 {
61 :type => doc.slug,
62 :path_16 => "#{base_path}/16.png",
63 :path_32 => "#{base_path}/16@2x.png"
64 }
65 end
66
67 # Checking paths against an array of possible paths is faster than 200+ File.exist? calls
68 files = Dir.glob('public/icons/docs/**/*.png')
69
70 items.each do |item|
71 item[:has_icons] = files.include?(item[:path_16]) && files.include?(item[:path_32])
72 end
73 end
74
75 def get_icon(path, max_size)
76 icon = ChunkyPNG::Image.from_file(path)
77
78 # Check if the icon is too big
79 # If it is, resize the image without changing the aspect ratio
80 if icon.width > max_size || icon.height > max_size
81 ratio = icon.width.to_f / icon.height
82 new_width = (icon.width >= icon.height ? max_size : max_size * ratio).floor
83 new_height = (icon.width >= icon.height ? max_size / ratio : max_size).floor
84
85 logger.warn("Icon #{path} is too big: max size is #{max_size} x #{max_size}, icon is #{icon.width} x #{icon.height}, resizing to #{new_width} x #{new_height}")
86
87 icon.resample_nearest_neighbor!(new_width, new_height)
88 end
89
90 icon
91 end
92
93 def get_sidebar_background
94 # This is a hacky way to get the background color of the sidebar
95 # Unfortunately, it's not possible to get the value of a SCSS variable from a Thor task
96 # Because hard-coding the value is even worse, we extract it using some regex
97 path = 'assets/stylesheets/global/_variables-dark.scss'
98 regex = /--sidebarBackground:\s+([^;]+);/
99 ChunkyPNG::Color.parse(File.read(path)[regex, 1])
100 end
101
102 def needs_dark_icon_fix(icon, bg_color)
103 # Determine whether the icon needs to be grayscaled if the user has enabled the dark theme
104 # The logic is roughly based on https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast
105 contrast = icon.pixels.select {|pixel| ChunkyPNG::Color.a(pixel) > 0}.map do |pixel|
106 get_contrast(bg_color, pixel)
107 end
108
109 avg = contrast.reduce(:+) / contrast.size.to_f
110 avg < 3.5
111 end
112
113 def get_contrast(base, other)
114 # Calculating the contrast ratio as described in the WCAG 2.0:
115 # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
116 l1 = get_luminance(base) + 0.05
117 l2 = get_luminance(other) + 0.05
118 ratio = l1 / l2
119 l2 > l1 ? 1 / ratio : ratio
120 end
121
122 def get_luminance(color)
123 rgb = [
124 ChunkyPNG::Color.r(color).to_f,
125 ChunkyPNG::Color.g(color).to_f,
126 ChunkyPNG::Color.b(color).to_f
127 ]
128
129 # Calculating the relative luminance as described in the WCAG 2.0:
130 # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
131
132 rgb.map! do |value|
133 value /= 255
134 value < 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4
135 end
136
137 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]
138 end
139
140 def generate_spritesheet(size, items_with_icons, output_path, &item_to_icon)
141 logger.info("Generating spritesheet #{output_path} with icons of size #{size} x #{size}")
142
143 icons_per_row = Math.sqrt(items_with_icons.length).ceil
144 spritesheet = ChunkyPNG::Image.new(size * icons_per_row, size * icons_per_row)
145
146 items_with_icons.each do |item|
147 icon = item_to_icon.call(item)
148
149 # Calculate the base coordinates
150 base_x = item[:col] * size
151 base_y = item[:row] * size
152
153 # Center the icon if it's not a perfect rectangle
154 x = base_x + ((size - icon.width) / 2).floor
155 y = base_y + ((size - icon.height) / 2).floor
156
157 spritesheet.compose!(icon, x, y)
158 end
159
160 FileUtils.mkdir_p(File.dirname(output_path))
161 spritesheet.save(output_path)
162 end
163
164 def save_manifest(items, icons_per_row, path)
165 logger.info("Saving spritesheet details to #{path}")
166
167 FileUtils.mkdir_p(File.dirname(path))
168
169 # Only save the details that the scss file needs
170 manifest_items = items.map do |item|
171 {
172 :type => item[:type],
173 :row => item[:row],
174 :col => item[:col],
175 :dark_icon_fix => item[:dark_icon_fix]
176 }
177 end
178
179 manifest = {:icons_per_row => icons_per_row, :items => manifest_items}
180
181 File.open(path, 'w') do |f|
182 f.write(JSON.generate(manifest))
183 end
184 end
185
186 def log_details(items_with_icons, icons_per_row)
187 logger.debug("Amount of icons: #{items_with_icons.length}")
188 logger.debug("Amount of icons needing the dark icon fix: #{items_with_icons.count {|item| item[:dark_icon_fix]}}")
189 logger.debug("Amount of icons per row: #{icons_per_row}")
190
191 max_type_length = items_with_icons.map {|item| item[:type].length}.max
192 border = "+#{'-' * (max_type_length + 2)}+#{'-' * 5}+#{'-' * 8}+#{'-' * 15}+"
193
194 logger.debug(border)
195 logger.debug("| #{'Type'.ljust(max_type_length)} | Row | Column | Dark icon fix |")
196 logger.debug(border)
197
198 items_with_icons.each do |item|
199 logger.debug("| #{item[:type].ljust(max_type_length)} | #{item[:row].to_s.ljust(3)} | #{item[:col].to_s.ljust(6)} | #{(item[:dark_icon_fix] ? 'Yes' : 'No').ljust(13)} |")
200 end
201
202 logger.debug(border)
203 end
204
205 def logger
206 @logger ||= Logger.new($stdout).tap do |logger|
207 logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
208 logger.formatter = proc {|severity, datetime, progname, msg| "#{msg}\n"}
209 end
210 end
211end
lib/tasks/updates.thor+315−0
@@ -0,0 +1,315 @@
1class UpdatesCLI < Thor
2 # The GitHub user that is allowed to upload reports
3 UPLOAD_USER = 'devdocs-bot'
4
5 # The repository to create an issue in when uploading the results
6 UPLOAD_REPO = 'freeCodeCamp/devdocs'
7
8 def self.to_s
9 'Updates'
10 end
11
12 def initialize(*args)
13 require 'docs'
14 require 'progress_bar'
15 require 'terminal-table'
16 require 'date'
17 super
18 end
19
20 desc 'check [--github-token] [--upload] [--verbose] [doc]...', 'Check for outdated documentations'
21 option :github_token, :type => :string
22 option :upload, :type => :boolean
23 option :verbose, :type => :boolean
24 def check(*names)
25 # Convert names to a list of Scraper instances
26 # Versions are omitted, if v10 is outdated than v8 is aswell
27 docs = names.map {|name| Docs.find(name.split(/@|~/)[0], false)}.uniq
28
29 # Check all documentations for updates when no arguments are given
30 docs = Docs.all if docs.empty?
31
32 opts = {
33 logger: logger
34 }
35
36 if options.key?(:github_token)
37 opts[:github_token] = options[:github_token]
38 end
39
40 with_progress_bar do |bar|
41 bar.max = docs.length
42 bar.write
43 end
44
45 results = docs.map do |doc|
46 result = check_doc(doc, opts)
47 with_progress_bar(&:increment!)
48 result
49 end
50
51 process_results(results)
52 rescue Docs::DocNotFound => error
53 logger.error(error)
54 logger.info('Run "thor docs:list" to see the list of docs.')
55 end
56
57 private
58
59 def check_doc(doc, opts)
60 logger.debug("Checking #{doc.name}")
61
62 instance = doc.versions.first.new
63 scraper_version = instance.get_scraper_version(opts)
64 latest_version = instance.get_latest_version(opts)
65
66 {
67 name: doc.name,
68 scraper_version: format_version(scraper_version),
69 latest_version: format_version(latest_version),
70 is_outdated: instance.is_outdated(scraper_version, latest_version)
71 }
72 rescue NotImplementedError
73 logger.warn("Couldn't check #{doc.name}, get_latest_version is not implemented")
74 error_result(doc, '`get_latest_version` is not implemented')
75 rescue => error
76 logger.error("Error while checking #{doc.name}\n#{error.full_message.strip}")
77 error_result(doc, error.message.gsub(/'/, '`'))
78 end
79
80 def format_version(version)
81 str = version.to_s
82
83 # If the version is numeric and greater than or equal to 1e9 it's probably a timestamp
84 return str if str.match(/^(\d)+$/).nil? or str.to_i < 1e9
85
86 DateTime.strptime(str, '%s').strftime('%F')
87 end
88
89 def error_result(doc, reason)
90 {
91 name: doc.name,
92 error: reason
93 }
94 end
95
96 def process_results(results)
97 successful_results = results.select {|result| result.key?(:is_outdated)}
98 failed_results = results.select {|result| result.key?(:error)}
99
100 up_to_date_results = successful_results.select {|result| !result[:is_outdated]}
101 outdated_results = successful_results.select {|result| result[:is_outdated]}
102
103 log_results(outdated_results, up_to_date_results, failed_results)
104 upload_results(outdated_results, up_to_date_results, failed_results) if options[:upload]
105 end
106
107 #
108 # Result logging methods
109 #
110
111 def log_results(outdated_results, up_to_date_results, failed_results)
112 log_failed_results(failed_results) unless failed_results.empty?
113 log_successful_results('Up-to-date', up_to_date_results) unless up_to_date_results.empty?
114 log_successful_results('Outdated', outdated_results) unless outdated_results.empty?
115 end
116
117 def log_successful_results(label, results)
118 title = "#{label} documentations (#{results.length})"
119 headings = ['Documentation', 'Scraper version', 'Latest version']
120 rows = results.map {|result| [result[:name], result[:scraper_version], result[:latest_version]]}
121
122 table = Terminal::Table.new :title => title, :headings => headings, :rows => rows
123 puts table
124 end
125
126 def log_failed_results(results)
127 title = "Documentations that could not be checked (#{results.length})"
128 headings = %w(Documentation Reason)
129 rows = results.map {|result| [result[:name], result[:error]]}
130
131 table = Terminal::Table.new :title => title, :headings => headings, :rows => rows
132 puts table
133 end
134
135 #
136 # Upload methods
137 #
138
139 def upload_results(outdated_results, up_to_date_results, failed_results)
140 # We can't create issues without a GitHub token
141 unless options.key?(:github_token)
142 logger.error("Please specify a GitHub token with the public_repo permission for #{UPLOAD_USER} with the --github-token parameter")
143 return
144 end
145
146 logger.info('Uploading the results to a new GitHub issue')
147
148 logger.info('Checking if the GitHub token belongs to the correct user')
149 user = github_get('/user')
150
151 # Only allow the DevDocs bot to upload reports
152 unless user['login'] == UPLOAD_USER
153 logger.error("Only #{UPLOAD_USER} is supposed to upload the results to a new issue. The specified github token is not for #{UPLOAD_USER}.")
154 return
155 end
156
157 logger.info('Creating a new GitHub issue')
158
159 issue = results_to_issue(outdated_results, up_to_date_results, failed_results)
160 created_issue = github_post("/repos/#{UPLOAD_REPO}/issues", issue)
161
162 logger.info('Checking if the previous issue is still open')
163
164 search_params = {
165 q: "Documentation versions report in:title author:#{UPLOAD_USER} is:issue repo:#{UPLOAD_REPO}",
166 sort: 'created',
167 order: 'desc'
168 }
169
170 matching_issues = github_get('/search/issues', search_params)
171 previous_issue = matching_issues['items'].find {|item| item['number'] != created_issue['number']}
172
173 if previous_issue.nil?
174 logger.info('No previous issue found')
175 log_upload_success(created_issue)
176 else
177 logger.info('Commenting on the previous issue')
178
179 comment = "This report was superseded by ##{created_issue['number']}."
180 github_post("/repos/#{UPLOAD_REPO}/issues/#{previous_issue['number']}/comments", {body: comment})
181 if previous_issue['closed_at'].nil?
182 logger.info('Closing the previous issue')
183 github_patch("/repos/#{UPLOAD_REPO}/issues/#{previous_issue['number']}", {state: 'closed'})
184 log_upload_success(created_issue)
185 else
186 logger.info('The previous issue has already been closed')
187 log_upload_success(created_issue)
188 end
189 end
190 end
191
192 def results_to_issue(outdated_results, up_to_date_results, failed_results)
193 results = [
194 successful_results_to_markdown('Outdated', outdated_results),
195 successful_results_to_markdown('Up-to-date', up_to_date_results),
196 failed_results_to_markdown(failed_results)
197 ]
198
199 results_str = results.select {|result| !result.nil?}.join("\n\n")
200 travis_str = ENV['TRAVIS'].nil? ? '' : "\n\nThis issue was created by Travis CI build [##{ENV['TRAVIS_BUILD_NUMBER']}](#{ENV['TRAVIS_BUILD_WEB_URL']})."
201
202 title = "Documentation versions report for #{Date.today.strftime('%B %Y')}"
203 body = <<-MARKDOWN
204## What is this?
205
206This is an automatically created issue which contains information about the version status of the documentations available on DevDocs. The results of this report can be used by maintainers when updating outdated documentations.
207
208Maintainers can close this issue when all documentations are up-to-date. The issue is also automatically closed when the next report is created.#{travis_str}
209
210## Results
211
212The #{outdated_results.length + up_to_date_results.length + failed_results.length} documentations are divided as follows:
213- #{outdated_results.length} that #{outdated_results.length == 1 ? 'is' : 'are'} outdated
214- #{up_to_date_results.length} that #{up_to_date_results.length == 1 ? 'is' : 'are'} up-to-date (patch updates are ignored)
215- #{failed_results.length} that could not be checked
216 MARKDOWN
217
218 {
219 title: title,
220 body: body.strip + "\n\n" + results_str
221 }
222 end
223
224 def successful_results_to_markdown(label, results)
225 return nil if results.empty?
226
227 title = "#{label} documentations (#{results.length})"
228 headings = ['Documentation', 'Scraper version', 'Latest version']
229 rows = results.map {|result| [result[:name], result[:scraper_version], result[:latest_version]]}
230
231 results_to_markdown(title, headings, rows)
232 end
233
234 def failed_results_to_markdown(results)
235 return nil if results.empty?
236
237 title = "Documentations that could not be checked (#{results.length})"
238 headings = %w(Documentation Reason)
239 rows = results.map {|result| [result[:name], result[:error]]}
240
241 results_to_markdown(title, headings, rows)
242 end
243
244 def results_to_markdown(title, headings, rows)
245 "<details>\n<summary>#{title}</summary>\n\n#{create_markdown_table(headings, rows)}\n</details>"
246 end
247
248 def create_markdown_table(headings, rows)
249 header = headings.join(' | ')
250 separator = '-|' * headings.length
251 body = rows.map {|row| row.join(' | ')}
252
253 header + "\n" + separator[0...-1] + "\n" + body.join("\n")
254 end
255
256 def log_upload_success(created_issue)
257 logger.info("Successfully uploaded the results to #{created_issue['html_url']}")
258 end
259
260 #
261 # HTTP utilities
262 #
263
264 def github_get(endpoint, params = {})
265 github_request(endpoint, {method: :get, params: params})
266 end
267
268 def github_post(endpoint, params)
269 github_request(endpoint, {method: :post, body: params.to_json})
270 end
271
272 def github_patch(endpoint, params)
273 github_request(endpoint, {method: :patch, body: params.to_json})
274 end
275
276 def github_request(endpoint, opts)
277 url = "https://api.github.com#{endpoint}"
278
279 # GitHub token authentication
280 opts[:headers] = {
281 Authorization: "token #{options[:github_token]}"
282 }
283
284 # GitHub requires the Content-Type to be application/json when a body is passed
285 if opts.key?(:body)
286 opts[:headers]['Content-Type'] = 'application/json'
287 end
288
289 logger.debug("Making a #{opts[:method]} request to #{url}")
290 response = Docs::Request.run(url, opts)
291
292 # response.success? is false if the response code is 201
293 # GitHub returns 201 Created after an issue is created
294 if response.success? || response.code == 201
295 JSON.parse(response.body)
296 else
297 logger.error("Couldn't make a #{opts[:method]} request to #{url} (response code #{response.code})")
298 nil
299 end
300 end
301
302 # A utility method which ensures no progress bar is shown when stdout is not a tty
303 def with_progress_bar(&block)
304 return unless $stdout.tty?
305 @progress_bar ||= ::ProgressBar.new
306 block.call @progress_bar
307 end
308
309 def logger
310 @logger ||= Logger.new($stdout).tap do |logger|
311 logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
312 logger.formatter = proc {|severity, datetime, progname, msg| "[#{severity}] #{msg}\n"}
313 end
314 end
315end
public/docs/docs.json+0−1
@@ -1 +0,0 @@
1[]
public/icons/docs-1.pxm+0−0

Binary file not shown.

public/icons/docs-1@2x.pxm+0−0

Binary file not shown.

public/icons/docs-2.pxm+0−0

Binary file not shown.

public/icons/docs-2@2x.pxm+0−0

Binary file not shown.

public/icons/docs/bluebird/16@2x.png+0−0

Binary file not shown.

public/images/webapp-icon-192.png+0−0

Binary file not shown.

public/manifest.json+6−0
@@ -4,6 +4,7 @@ · 3 unmodified lines
4 "description": "API Documentation Browser",4 "description": "API Documentation Browser",
5 "start_url": "/",5 "start_url": "/",
6 "display": "standalone",6 "display": "standalone",
7 "background_color": "#EEEEEE",
7 "icons": [8 "icons": [
8 {9 {
9 "src": "/images/webapp-icon-32.png",10 "src": "/images/webapp-icon-32.png",
@@ -25,6 +26,11 @@ · 15 unmodified lines
25 "sizes": "128x128",26 "sizes": "128x128",
26 "type": "image/png"27 "type": "image/png"
27 },28 },
29 {
30 "src": "/images/webapp-icon-192.png",
31 "sizes": "192x192",
32 "type": "image/png"
33 },
28 {34 {
29 "src": "/images/webapp-icon-256.png",35 "src": "/images/webapp-icon-256.png",
30 "sizes": "256x256",36 "sizes": "256x256",
test/app_test.rb+0−81
@@ -43,35 +43,6 @@ class AppTest < MiniTest::Spec · 42 unmodified lines
43 assert last_response.redirect?43 assert last_response.redirect?
44 assert_equal 'https://example.org/', last_response['Location']44 assert_equal 'https://example.org/', last_response['Location']
45 end45 end
46
47 it "sets default size" do
48 get '/'
49 assert_includes last_response.body, 'data-size="20rem"'
50 end
51
52 it "sets size from cookie" do
53 set_cookie('size=42')
54 get '/'
55 assert_includes last_response.body, 'data-size="42px"'
56 end
57
58 it "sets layout from cookie" do
59 set_cookie('layout=foo')
60 get '/'
61 assert_includes last_response.body, '<body class="foo">'
62 end
63
64 it "sets the <html> theme from cookie" do
65 get '/'
66 assert_match %r{<html [^>]*class="[^\"]*_theme-default}, last_response.body
67 refute_includes last_response.body, '_theme-dark'
68
69 set_cookie('dark=1')
70
71 get '/'
72 assert_match %r{<html [^>]*class="[^\"]*_theme-dark}, last_response.body
73 refute_includes last_response.body, '_theme-default'
74 end
75 end46 end
76 47
77 describe "/[static-page]" do48 describe "/[static-page]" do
@@ -106,58 +77,6 @@ class AppTest < MiniTest::Spec · 28 unmodified lines
106 end77 end
107 end78 end
108 79
109 describe "/manifest.appcache" do
110 it "works" do
111 get '/manifest.appcache'
112 assert last_response.ok?
113 end
114
115 it "works with cookie" do
116 set_cookie('docs=css/html~5')
117 get '/manifest.appcache'
118 assert last_response.ok?
119 assert_includes last_response.body, '/css/index.json?1420139788'
120 assert_includes last_response.body, '/html~5/index.json?1420139791'
121 end
122
123 it "ignores invalid docs in the cookie" do
124 set_cookie('docs=foo')
125 get '/manifest.appcache'
126 assert last_response.ok?
127 refute_includes last_response.body, 'foo'
128 end
129
130 it "has the word 'default' when no 'dark' cookie is set" do
131 get '/manifest.appcache'
132 assert_includes last_response.body, '# default'
133 refute_includes last_response.body, '# dark'
134 end
135
136 it "has the word 'dark' when the cookie is set" do
137 set_cookie('dark=1')
138 get '/manifest.appcache'
139 assert_includes last_response.body, '# dark'
140 refute_includes last_response.body, '# default'
141 end
142
143 it "sets default size" do
144 get '/manifest.appcache'
145 assert_includes last_response.body, '20rem'
146 end
147
148 it "sets size from cookie" do
149 set_cookie('size=42')
150 get '/manifest.appcache'
151 assert_includes last_response.body, '42px'
152 end
153
154 it "sets layout from cookie" do
155 set_cookie('layout=foo_layout')
156 get '/manifest.appcache'
157 assert_includes last_response.body, 'foo_layout'
158 end
159 end
160
161 describe "/[doc]" do80 describe "/[doc]" do
162 it "renders when the doc exists and isn't enabled" do81 it "renders when the doc exists and isn't enabled" do
163 set_cookie('docs=html~5')82 set_cookie('docs=html~5')
test/lib/docs/core/scrapers/file_scraper_test.rb+2−2
@@ -63,7 +63,7 @@ class FileScraperTest < MiniTest::Spec · 62 unmodified lines
63 end63 end
64 64
65 it "reads a file" do65 it "reads a file" do
66 mock(scraper).read_file(path)66 mock(scraper).read_file(File.join(ROOT_PATH, 'docs/scraper', path))
67 result67 result
68 end68 end
69 69
@@ -165,7 +165,7 @@ class FileScraperTest < MiniTest::Spec · 95 unmodified lines
165 165
166 describe "#read_file" do166 describe "#read_file" do
167 let :result do167 let :result do
168 scraper.send :read_file, 'file'168 scraper.send :read_file, File.join(ROOT_PATH, 'docs', 'scraper', 'file')
169 end169 end
170 170
171 it "returns the file's content when the file exists in the source directory" do171 it "returns the file's content when the file exists in the source directory" do
views/app.erb+1−13
@@ -28,13 +28,7 @@ · 27 unmodified lines
28 </nav>28 </nav>
29 </header>29 </header>
30 <section class="_sidebar" tabindex="-1">30 <section class="_sidebar" tabindex="-1">
31 <div class="_list" role="navigation">31 <div class="_list" role="navigation"></div>
32 <% unless @doc %>
33 <% App.docs.each do |slug, doc| %>
34 <a href="/<%= slug %>/" class="_list-item"><%= doc['full_name'] %></a>
35 <% end %>
36 <% end %>
37 </div>
38 </section>32 </section>
39 <div class="_container" role="document">33 <div class="_container" role="document">
40 <main class="_content _content-loading" role="main"></main>34 <main class="_content _content-loading" role="main"></main>
@@ -63,9 +57,3 @@ · 22 unmodified lines
63 <symbol id="icon-external-link" viewBox="0 0 24 24"><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></symbol>57 <symbol id="icon-external-link" viewBox="0 0 24 24"><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></symbol>
64 </defs>58 </defs>
65</svg>59</svg>
66<style data-size="<%= app_size %>" data-resizer>
67 ._container { margin-left: <%= app_size %>; }
68 ._header, ._list { width: <%= app_size %>; }
69 ._list-hover.clone { min-width: <%= app_size %>; }
70 ._notice, ._path, ._resizer { left: <%= app_size %>; }
71</style>
views/index.erb+3−2
@@ -1,5 +1,5 @@
1<!DOCTYPE html>1<!DOCTYPE html>
2<html<%= ' manifest="/manifest.appcache"' if App.production? %> prefix="og: http://ogp.me/ns#" lang="en" class="_booting _theme-<%= app_theme %>">2<html prefix="og: http://ogp.me/ns#" lang="en" class="_booting">
3<head>3<head>
4 <meta charset="utf-8">4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no">5 <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no">
@@ -14,6 +14,7 @@ · 8 unmodified lines
14 <meta name="apple-mobile-web-app-capable" content="yes">14 <meta name="apple-mobile-web-app-capable" content="yes">
15 <meta name="apple-mobile-web-app-status-bar-style" content="black">15 <meta name="apple-mobile-web-app-status-bar-style" content="black">
16 <meta name="format-detection" content="telephone=no">16 <meta name="format-detection" content="telephone=no">
17 <meta name="theme-color" content="#eee">
17 <meta name="robots" content="noodp">18 <meta name="robots" content="noodp">
18 <title>DevDocs API Documentation</title>19 <title>DevDocs API Documentation</title>
19 <link rel="canonical" href="<%= canonical_origin %>">20 <link rel="canonical" href="<%= canonical_origin %>">
@@ -32,7 +33,7 @@ · 12 unmodified lines
32 <link rel="mask-icon" href="<%= App.cdn_origin %>/images/webkit-mask-icon.svg" color="#398df0">33 <link rel="mask-icon" href="<%= App.cdn_origin %>/images/webkit-mask-icon.svg" color="#398df0">
33 <%= stylesheet_tag 'application' %>34 <%= stylesheet_tag 'application' %>
34</head>35</head>
35<body<%= %( class="#{app_layout}") if app_layout %>>36<body>
36<noscript class="_fail">DevDocs requires JavaScript to run.</noscript>37<noscript class="_fail">DevDocs requires JavaScript to run.</noscript>
37<%= erb :app -%>38<%= erb :app -%>
38<%= javascript_tag 'application', asset_host: false %>39<%= javascript_tag 'application', asset_host: false %>
views/manifest.erb+0−14
@@ -1,14 +0,0 @@
1CACHE MANIFEST
2# <%= app_theme %> <%= app_size %> <%= app_layout %>
3
4CACHE:
5/
6<%= manifest_asset_urls.join "\n" %>
7<%= doc_index_urls.join "\n" %>
8
9NETWORK:
10/s/
11*
12
13FALLBACK:
14/ /
views/other.erb+3−2
@@ -1,10 +1,11 @@
1<!DOCTYPE html>1<!DOCTYPE html>
2<html lang="en" class="_booting _theme-<%= app_theme %>">2<html lang="en" class="_booting">
3<head>3<head>
4 <meta charset="utf-8">4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">5 <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
6 <% if doc_index_page? %><meta name="description" content="<%= @doc['name'] %> <%= @doc['release'] %> API documentation with instant search, offline support, keyboard shortcuts, mobile version, and more."><% else %><meta name="robots" content="noindex"><% end %>6 <% if doc_index_page? %><meta name="description" content="<%= @doc['name'] %> <%= @doc['release'] %> API documentation with instant search, offline support, keyboard shortcuts, mobile version, and more."><% else %><meta name="robots" content="noindex"><% end %>
7 <meta name="format-detection" content="telephone=no">7 <meta name="format-detection" content="telephone=no">
8 <meta name="theme-color" content="#eee">
8 <meta property="og:image" content="<%= App.cdn_origin %>/images/icon-320.png">9 <meta property="og:image" content="<%= App.cdn_origin %>/images/icon-320.png">
9 <title>DevDocs<%= " &mdash; #{@doc['full_name']} documentation" if doc_index_page? %></title>10 <title>DevDocs<%= " &mdash; #{@doc['full_name']} documentation" if doc_index_page? %></title>
10 <link rel="canonical" href="<%= canonical_origin %><%= request.path %>">11 <link rel="canonical" href="<%= canonical_origin %><%= request.path %>">
@@ -13,7 +14,7 @@ · 2 unmodified lines
13 <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Search DevDocs">14 <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Search DevDocs">
14 <%= stylesheet_tag 'application' %>15 <%= stylesheet_tag 'application' %>
15</head>16</head>
16<body<%= %( class="#{app_layout}") if app_layout %> data-doc="<%= CGI::escape_html @doc.to_json %>">17<body data-doc="<%= CGI::escape_html @doc.to_json %>">
17<noscript class="_fail">DevDocs requires JavaScript to run.</noscript>18<noscript class="_fail">DevDocs requires JavaScript to run.</noscript>
18<%= erb :app -%>19<%= erb :app -%>
19<%= javascript_tag 'application', asset_host: false %><% unless App.production? %>20<%= javascript_tag 'application', asset_host: false %><% unless App.production? %>
views/service-worker.js.erb+49−0
@@ -0,0 +1,49 @@
1<%# The name of the cache to store responses in %>
2<%# If the cache name changes DevDocs is assumed to be updated %>
3const cacheName = '<%= service_worker_cache_name %>';
4
5<%# Url's to cache when the service worker is installed %>
6const urlsToCache = [
7 '/',
8 '/favicon.ico',
9 '/manifest.json',
10 '<%= service_worker_asset_urls.join "',\n '" %>',
11 '<%= doc_index_urls.join "',\n '" %>',
12];
13
14<%# Set-up the cache %>
15self.addEventListener('install', event => {
16 self.skipWaiting();
17
18 event.waitUntil(
19 caches.open(cacheName).then(cache => cache.addAll(urlsToCache)),
20 );
21});
22
23<%# Remove old caches %>
24self.addEventListener('activate', event => {
25 event.waitUntil((async () => {
26 const keys = await caches.keys();
27 const jobs = keys.map(key => key !== cacheName ? caches.delete(key) : Promise.resolve());
28 return Promise.all(jobs);
29 })());
30});
31
32<%# Handle HTTP requests %>
33self.addEventListener('fetch', event => {
34 event.respondWith((async () => {
35 const cachedResponse = await caches.match(event.request);
36 if (cachedResponse) return cachedResponse;
37
38 const url = new URL(event.request.url);
39
40 <%# Attempt to return the index page from the cache if the user is visiting a url like devdocs.io/offline or devdocs.io/javascript/global_objects/array/find %>
41 <%# The index page will handle the routing %>
42 if (url.origin === location.origin && !url.pathname.includes('.')) {
43 const cachedIndex = await caches.match('/');
44 if (cachedIndex) return cachedIndex;
45 }
46
47 return fetch(event.request);
48 })());
49});
views/unsupported.erb+3−3
@@ -11,9 +11,9 @@ · 10 unmodified lines
11 <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:</p>11 <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:</p>
12 <ul class="_fail-list">12 <ul class="_fail-list">
13 <li>Recent versions of Firefox, Chrome, or Opera</li>13 <li>Recent versions of Firefox, Chrome, or Opera</li>
14 <li>Safari 9.1+</li>14 <li>Safari 11.1+</li>
15 <li>Edge 16+</li>15 <li>Edge 17+</li>
16 <li>iOS 10+</li>16 <li>iOS 11.3+</li>
17 </ul>17 </ul>
18 <p class="_fail-text">18 <p class="_fail-text">
19 If you're unable to upgrade, we apologize.19 If you're unable to upgrade, we apologize.