History

Fix merge conflict

Jasper van Merle committed 7 years ago

Commit 186932e2 parents 57232ce b34dbb2450 files · +6920 −1990Browse files

Unified · Split

450 files
  1. .editorconfig +11 −0
  2. .github/CONTRIBUTING.md +7 −17
  3. .github/ISSUE_TEMPLATE.md +7 −0
  4. .github/no-response.yml +8 −0
  5. .gitignore +3 −4
  6. .ruby-version +1 −1
  7. .slugignore +1 −2
  8. .travis.yml +23 −1
  9. CODE_OF_CONDUCT.md +2 −0
  10. COPYRIGHT +4 −4
  11. Dockerfile +5 −2
  12. Dockerfile-alpine +5 −2
  13. Gemfile +10 −5
  14. Gemfile.lock +75 −60
  15. ISSUE_TEMPLATE.md +0 −7
  16. PULL_REQUEST_TEMPLATE.md +11 −0
  17. README.md +46 −25
  18. Rakefile +4 −0
  19. assets/images/.gitignore +1 −0
  20. assets/images/docs-1.png +0 −0
  21. assets/images/docs-1@2x.png +0 −0
  22. assets/images/docs-2.png +0 −0
  23. assets/images/docs-2@2x.png +0 −0
  24. assets/javascripts/app/app.coffee +21 −14
  25. assets/javascripts/app/appcache.coffee +0 −42
  26. assets/javascripts/app/config.coffee.erb +3 −0
  27. assets/javascripts/app/serviceworker.coffee +49 −0
  28. assets/javascripts/app/settings.coffee +34 −1
  29. assets/javascripts/app/shortcuts.coffee +3 −0
  30. assets/javascripts/app/update_checker.coffee +3 −3
  31. assets/javascripts/lib/cookies_store.coffee +5 −1
  32. assets/javascripts/lib/favicon.coffee +64 −0
  33. assets/javascripts/lib/license.coffee +1 −1
  34. assets/javascripts/lib/page.coffee +18 −2
  35. assets/javascripts/lib/util.coffee +4 −0
  36. assets/javascripts/models/doc.coffee +3 −1
  37. assets/javascripts/news.json +17 −0
  38. assets/javascripts/templates/error_tmpl.coffee +9 −12
  39. assets/javascripts/templates/notif_tmpl.coffee +6 −0
  40. assets/javascripts/templates/pages/about_tmpl.coffee +96 −31
  41. assets/javascripts/templates/pages/help_tmpl.coffee +3 −0
  42. assets/javascripts/templates/pages/offline_tmpl.coffee +9 −4
  43. assets/javascripts/templates/pages/root_tmpl.coffee.erb +3 −14
  44. assets/javascripts/templates/pages/settings_tmpl.coffee +8 −0
  45. assets/javascripts/tracking.js +27 −23
  46. assets/javascripts/vendor/fastclick.js +2 −2
  47. assets/javascripts/vendor/prism.js +633 −352
  48. assets/javascripts/vendor/raven.js +1 −1
  49. assets/javascripts/views/content/content.coffee +3 −0
  50. assets/javascripts/views/content/entry_page.coffee +2 −1
  51. assets/javascripts/views/content/offline_page.coffee +1 −0
  52. assets/javascripts/views/content/root_page.coffee +0 −4
  53. assets/javascripts/views/content/settings_page.coffee +10 −12
  54. assets/javascripts/views/content/type_page.coffee +1 −0
  55. assets/javascripts/views/layout/document.coffee +7 −5
  56. assets/javascripts/views/layout/resizer.coffee +2 −9
  57. assets/javascripts/views/layout/settings.coffee +1 −9
  58. assets/javascripts/views/search/search.coffee +13 −0
  59. assets/javascripts/views/search/search_scope.coffee +23 −5
  60. assets/javascripts/views/sidebar/sidebar.coffee +1 −1
  61. assets/stylesheets/application-dark.css.scss +0 −104
  62. assets/stylesheets/application.css.scss +14 −6
  63. assets/stylesheets/components/_app.scss +5 −5
  64. assets/stylesheets/components/_content.scss +28 −32
  65. assets/stylesheets/components/_fail.scss +2 −4
  66. assets/stylesheets/components/_header.scss +26 −35
  67. assets/stylesheets/components/_mobile.scss +2 −2
  68. assets/stylesheets/components/_notice.scss +5 −5
  69. assets/stylesheets/components/_notif.scss +16 −12
  70. assets/stylesheets/components/_page.scss +4 −4
  71. assets/stylesheets/components/_path.scss +6 −6
  72. assets/stylesheets/components/_prism.scss +25 −32
  73. assets/stylesheets/components/_settings.scss +12 −12
  74. assets/stylesheets/components/_sidebar.scss +37 −39
  75. assets/stylesheets/global/_base.scss +37 −31
  76. assets/stylesheets/global/_classes.scss +24 −25
  77. assets/stylesheets/global/_icons.scss +0 −175
  78. assets/stylesheets/global/_icons.scss.erb +43 −0
  79. assets/stylesheets/global/_mixins.scss +6 −0
  80. assets/stylesheets/global/_print.scss +2 −2
  81. assets/stylesheets/global/_variables-dark.scss +53 −69
  82. assets/stylesheets/global/_variables-light.scss +76 −0
  83. assets/stylesheets/global/_variables.scss +25 −82
  84. assets/stylesheets/pages/_angularjs.scss +1 −1
  85. assets/stylesheets/pages/_async.scss +2 −2
  86. assets/stylesheets/pages/_bash.scss +12 −0
  87. assets/stylesheets/pages/_bootstrap.scss +1 −1
  88. assets/stylesheets/pages/_c.scss +2 −2
  89. assets/stylesheets/pages/_clojure.scss +1 −1
  90. assets/stylesheets/pages/_cypress.scss +21 −0
  91. assets/stylesheets/pages/_d.scss +3 −3
  92. assets/stylesheets/pages/_dart.scss +12 −0
  93. assets/stylesheets/pages/_dojo.scss +1 −1
  94. assets/stylesheets/pages/_ember.scss +2 −2
  95. assets/stylesheets/pages/_erlang.scss +1 −1
  96. assets/stylesheets/pages/_graphite.scss +7 −0
  97. assets/stylesheets/pages/_jquery.scss +2 −2
  98. assets/stylesheets/pages/_love.scss +2 −2
  99. assets/stylesheets/pages/_mdn.scss +28 −3
  100. assets/stylesheets/pages/_meteor.scss +2 −2
  101. assets/stylesheets/pages/_node.scss +3 −0
  102. assets/stylesheets/pages/_npm.scss +1 −1
  103. assets/stylesheets/pages/_phalcon.scss +1 −1
  104. assets/stylesheets/pages/_php.scss +2 −2
  105. assets/stylesheets/pages/_postgres.scss +1 −1
  106. assets/stylesheets/pages/_qt.scss +9 −0
  107. assets/stylesheets/pages/_rdoc.scss +2 −13
  108. assets/stylesheets/pages/_react_native.scss +1 −1
  109. assets/stylesheets/pages/_redis.scss +2 −2
  110. assets/stylesheets/pages/_rfc.scss +1 −1
  111. assets/stylesheets/pages/_rust.scss +10 −1
  112. assets/stylesheets/pages/_scala.scss +4 −0
  113. assets/stylesheets/pages/_simple.scss +1 −31
  114. assets/stylesheets/pages/_sphinx.scss +2 −2
  115. assets/stylesheets/pages/_sphinx_simple.scss +1 −1
  116. assets/stylesheets/pages/_support_tables.scss +1 −1
  117. assets/stylesheets/pages/_wordpress.scss +15 −0
  118. assets/stylesheets/pages/_yard.scss +1 −1
  119. assets/stylesheets/pages/_yii.scss +1 −1
  120. docs/adding-docs.md +24 −0
  121. docs/filter-reference.md +224 −0
  122. docs/maintainers.md +102 −0
  123. docs/scraper-reference.md +227 −0
  124. lib/app.rb +45 −41
  125. lib/docs.rb +18 −1
  126. lib/docs/core/doc.rb +105 −1
  127. lib/docs/core/requester.rb +8 −4
  128. lib/docs/core/scrapers/file_scraper.rb +17 −6
  129. lib/docs/core/url.rb +1 −1
  130. lib/docs/filters/bash/clean_html.rb +74 −0
  131. lib/docs/filters/bash/entries.rb +56 −0
  132. lib/docs/filters/cmake/entries.rb +1 −0
  133. lib/docs/filters/codeceptjs/entries.rb +2 −2
  134. lib/docs/filters/composer/clean_html.rb +35 −0
  135. lib/docs/filters/composer/entries.rb +36 −0
  136. lib/docs/filters/cordova/clean_html.rb +1 −1
  137. lib/docs/filters/cpp/entries.rb +2 −0
  138. lib/docs/filters/css/entries.rb +17 −10
  139. lib/docs/filters/cypress/clean_html.rb +26 −0
  140. lib/docs/filters/cypress/entries.rb +34 −0
  141. lib/docs/filters/dart/clean_html.rb +66 −0
  142. lib/docs/filters/dart/entries.rb +42 −0
  143. lib/docs/filters/dom/clean_html.rb +1 −1
  144. lib/docs/filters/dom/entries.rb +15 −2
  145. lib/docs/filters/elixir/clean_html.rb +6 −0
  146. lib/docs/filters/express/clean_html.rb +5 −0
  147. lib/docs/filters/go/clean_html.rb +1 −0
  148. lib/docs/filters/graphite/clean_html.rb +27 −0
  149. lib/docs/filters/graphite/entries.rb +46 −0
  150. lib/docs/filters/handlebars/clean_html.rb +44 −0
  151. lib/docs/filters/handlebars/entries.rb +19 −0
  152. lib/docs/filters/haskell/clean_html.rb +4 −0
  153. lib/docs/filters/haskell/entries.rb +2 −2
  154. lib/docs/filters/homebrew/clean_html.rb +5 −0
  155. lib/docs/filters/homebrew/entries.rb +3 −1
  156. lib/docs/filters/html/entries.rb +1 −1
  157. lib/docs/filters/javascript/clean_html.rb +2 −2
  158. lib/docs/filters/javascript/entries.rb +1 −1
  159. lib/docs/filters/kotlin/clean_html.rb +7 −0
  160. lib/docs/filters/kotlin/entries.rb +3 −1
  161. lib/docs/filters/mdn/clean_html.rb +90 −0
  162. lib/docs/filters/node/clean_html.rb +1 −1
  163. lib/docs/filters/node/entries.rb +2 −0
  164. lib/docs/filters/pandas/entries.rb +5 −3
  165. lib/docs/filters/puppeteer/clean_html.rb +24 −0
  166. lib/docs/filters/puppeteer/entries.rb +41 −0
  167. lib/docs/filters/pygame/clean_html.rb +102 −0
  168. lib/docs/filters/pygame/entries.rb +29 −0
  169. lib/docs/filters/python/clean_html.rb +5 −3
  170. lib/docs/filters/python/entries_v2.rb +1 −7
  171. lib/docs/filters/python/entries_v3.rb +15 −17
  172. lib/docs/filters/qt/clean_html.rb +32 −0
  173. lib/docs/filters/qt/entries.rb +142 −0
  174. lib/docs/filters/ramda/clean_html.rb +1 −1
  175. lib/docs/filters/rdoc/clean_html.rb +4 −2
  176. lib/docs/filters/react/entries.rb +6 −2
  177. lib/docs/filters/redis/entries.rb +2 −1
  178. lib/docs/filters/rust/clean_html.rb +39 −6
  179. lib/docs/filters/rust/entries.rb +7 −5
  180. lib/docs/filters/salt_stack/clean_html.rb +27 −0
  181. lib/docs/filters/salt_stack/entries.rb +38 −0
  182. lib/docs/filters/scala/clean_html.rb +109 −0
  183. lib/docs/filters/scala/entries.rb +103 −0
  184. lib/docs/filters/sqlite/entries.rb +2 −1
  185. lib/docs/filters/typescript/entries.rb +1 −0
  186. lib/docs/filters/vue_router/clean_html.rb +21 −0
  187. lib/docs/filters/vue_router/entries.rb +71 −0
  188. lib/docs/filters/wordpress/clean_html.rb +39 −0
  189. lib/docs/filters/wordpress/entries.rb +19 −0
  190. lib/docs/scrapers/angular.rb +4 −0
  191. lib/docs/scrapers/angularjs.rb +16 −3
  192. lib/docs/scrapers/ansible.rb +35 −34
  193. lib/docs/scrapers/apache.rb +6 −1
  194. lib/docs/scrapers/apache_pig.rb +6 −1
  195. lib/docs/scrapers/async.rb +7 −2
  196. lib/docs/scrapers/babel.rb +5 −0
  197. lib/docs/scrapers/backbone.rb +5 −0
  198. lib/docs/scrapers/bash.rb +26 −0
  199. lib/docs/scrapers/bluebird.rb +4 −0
  200. lib/docs/scrapers/bootstrap.rb +10 −4
  201. lib/docs/scrapers/bottle.rb +6 −0
  202. lib/docs/scrapers/bower.rb +5 −1
  203. lib/docs/scrapers/c.rb +7 −1
  204. lib/docs/scrapers/cakephp.rb +5 −0
  205. lib/docs/scrapers/chai.rb +5 −1
  206. lib/docs/scrapers/chef.rb +5 −0
  207. lib/docs/scrapers/clojure.rb +14 −4
  208. lib/docs/scrapers/cmake.rb +40 −9
  209. lib/docs/scrapers/codeception.rb +6 −1
  210. lib/docs/scrapers/codeceptjs.rb +6 −2
  211. lib/docs/scrapers/codeigniter.rb +6 −0
  212. lib/docs/scrapers/coffeescript.rb +4 −0
  213. lib/docs/scrapers/composer.rb +28 −0
  214. lib/docs/scrapers/cordova.rb +16 −1
  215. lib/docs/scrapers/cpp.rb +8 −1
  216. lib/docs/scrapers/crystal.rb +6 −1
  217. lib/docs/scrapers/cypress.rb +36 −0
  218. lib/docs/scrapers/d.rb +6 −1
  219. lib/docs/scrapers/d3.rb +5 −1
  220. lib/docs/scrapers/dart.rb +41 −0
  221. lib/docs/scrapers/django.rb +17 −7
  222. lib/docs/scrapers/docker.rb +154 −27
  223. lib/docs/scrapers/dojo.rb +5 −0
  224. lib/docs/scrapers/drupal.rb +5 −0
  225. lib/docs/scrapers/electron.rb +6 −1
  226. lib/docs/scrapers/elixir.rb +45 −1
  227. lib/docs/scrapers/ember.rb +5 −0
  228. lib/docs/scrapers/erlang.rb +5 −4
  229. lib/docs/scrapers/eslint.rb +5 −1
  230. lib/docs/scrapers/express.rb +5 −1
  231. lib/docs/scrapers/falcon.rb +5 −0
  232. lib/docs/scrapers/fish.rb +12 −1
  233. lib/docs/scrapers/flow.rb +6 −2
  234. lib/docs/scrapers/git.rb +6 −1
  235. lib/docs/scrapers/gnu.rb +1 −1
  236. lib/docs/scrapers/gnu/gcc.rb +6 −8
  237. lib/docs/scrapers/gnu/gnu_fortran.rb +6 −4
  238. lib/docs/scrapers/go.rb +6 −1
  239. lib/docs/scrapers/godot.rb +6 −1
  240. lib/docs/scrapers/graphite.rb +26 −0
  241. lib/docs/scrapers/grunt.rb +5 −1
  242. lib/docs/scrapers/handlebars.rb +27 −0
  243. lib/docs/scrapers/haskell.rb +9 −2
  244. lib/docs/scrapers/haxe.rb +7 −1
  245. lib/docs/scrapers/homebrew.rb +6 −3
  246. lib/docs/scrapers/http.rb +2 −0
  247. lib/docs/scrapers/immutable.rb +5 −1
  248. lib/docs/scrapers/influxdata.rb +7 −1
  249. lib/docs/scrapers/jasmine.rb +7 −3
  250. lib/docs/scrapers/jekyll.rb +5 −0
  251. lib/docs/scrapers/jest.rb +7 −2
  252. lib/docs/scrapers/jquery/jquery_core.rb +4 −0
  253. lib/docs/scrapers/jquery/jquery_mobile.rb +5 −0
  254. lib/docs/scrapers/jquery/jquery_ui.rb +4 −0
  255. lib/docs/scrapers/jsdoc.rb +4 −0
  256. lib/docs/scrapers/julia.rb +27 −3
  257. lib/docs/scrapers/knockout.rb +4 −0
  258. lib/docs/scrapers/koa.rb +5 −1
  259. lib/docs/scrapers/kotlin.rb +15 −3
  260. lib/docs/scrapers/laravel.rb +18 −1
  261. lib/docs/scrapers/leaflet.rb +17 −2
  262. lib/docs/scrapers/less.rb +7 −1
  263. lib/docs/scrapers/liquid.rb +5 −0
  264. lib/docs/scrapers/lodash.rb +6 −1
  265. lib/docs/scrapers/love.rb +5 −0
  266. lib/docs/scrapers/lua.rb +5 −0
  267. lib/docs/scrapers/marionette.rb +12 −1
  268. lib/docs/scrapers/markdown.rb +5 −1
  269. lib/docs/scrapers/matplotlib.rb +42 −11
  270. lib/docs/scrapers/mdn/css.rb +2 −0
  271. lib/docs/scrapers/mdn/dom.rb +2 −0
  272. lib/docs/scrapers/mdn/dom_events.rb +2 −0
  273. lib/docs/scrapers/mdn/html.rb +2 −0
  274. lib/docs/scrapers/mdn/javascript.rb +2 −0
  275. lib/docs/scrapers/mdn/mdn.rb +5 −0
  276. lib/docs/scrapers/mdn/svg.rb +2 −0
  277. lib/docs/scrapers/mdn/xslt_xpath.rb +2 −0
  278. lib/docs/scrapers/meteor.rb +5 −0
  279. lib/docs/scrapers/mocha.rb +6 −2
  280. lib/docs/scrapers/modernizr.rb +4 −0
  281. lib/docs/scrapers/moment.rb +5 −0
  282. lib/docs/scrapers/mongoose.rb +7 −1
  283. lib/docs/scrapers/nginx.rb +10 −4
  284. lib/docs/scrapers/nginx_lua_module.rb +9 −0
  285. lib/docs/scrapers/nim.rb +8 −3
  286. lib/docs/scrapers/node.rb +13 −3
  287. lib/docs/scrapers/nokogiri2.rb +12 −4
  288. lib/docs/scrapers/npm.rb +5 −1
  289. lib/docs/scrapers/numpy.rb +4 −1
  290. lib/docs/scrapers/openjdk.rb +22 −3
  291. lib/docs/scrapers/opentsdb.rb +4 −0
  292. lib/docs/scrapers/padrino.rb +7 −3
  293. lib/docs/scrapers/pandas.rb +28 −6
  294. lib/docs/scrapers/perl.rb +6 −1
  295. lib/docs/scrapers/phalcon.rb +5 −0
  296. lib/docs/scrapers/phaser.rb +4 −0
  297. lib/docs/scrapers/phoenix.rb +6 −1
  298. lib/docs/scrapers/php.rb +9 −4
  299. lib/docs/scrapers/phpunit.rb +6 −0
  300. lib/docs/scrapers/postgresql.rb +6 −0
  301. lib/docs/scrapers/pug.rb +4 −0
  302. lib/docs/scrapers/puppeteer.rb +23 −0
  303. lib/docs/scrapers/pygame.rb +25 −0
  304. lib/docs/scrapers/python.rb +13 −12
  305. lib/docs/scrapers/q.rb +4 −0
  306. lib/docs/scrapers/qt.rb +136 −0
  307. lib/docs/scrapers/ramda.rb +7 −3
  308. lib/docs/scrapers/rdoc/minitest.rb +12 −2
  309. lib/docs/scrapers/rdoc/rails.rb +14 −3
  310. lib/docs/scrapers/rdoc/ruby.rb +19 −19
  311. lib/docs/scrapers/react.rb +6 −1
  312. lib/docs/scrapers/react_native.rb +5 −0
  313. lib/docs/scrapers/redis.rb +7 −1
  314. lib/docs/scrapers/redux.rb +5 −1
  315. lib/docs/scrapers/relay.rb +5 −0
  316. lib/docs/scrapers/requirejs.rb +5 −1
  317. lib/docs/scrapers/rethinkdb.rb +4 −0
  318. lib/docs/scrapers/rust.rb +14 −5
  319. lib/docs/scrapers/salt_stack.rb +36 −0
  320. lib/docs/scrapers/sass.rb +4 −0
  321. lib/docs/scrapers/scala.rb +61 −0
  322. lib/docs/scrapers/scikit_image.rb +7 −2
  323. lib/docs/scrapers/scikit_learn.rb +6 −2
  324. lib/docs/scrapers/sinon.rb +18 −8
  325. lib/docs/scrapers/socketio.rb +4 −0
  326. lib/docs/scrapers/sqlite.rb +6 −2
  327. lib/docs/scrapers/statsmodels.rb +5 −1
  328. lib/docs/scrapers/support_tables.rb +6 −0
  329. lib/docs/scrapers/symfony.rb +17 −8
  330. lib/docs/scrapers/tcl_tk.rb +5 −0
  331. lib/docs/scrapers/tensorflow.rb +4 −0
  332. lib/docs/scrapers/terraform.rb +5 −0
  333. lib/docs/scrapers/twig.rb +11 −6
  334. lib/docs/scrapers/typescript.rb +6 −2
  335. lib/docs/scrapers/underscore.rb +9 −4
  336. lib/docs/scrapers/vagrant.rb +7 −2
  337. lib/docs/scrapers/vue.rb +4 −0
  338. lib/docs/scrapers/vue_router.rb +29 −0
  339. lib/docs/scrapers/vulkan.rb +6 −1
  340. lib/docs/scrapers/webpack.rb +5 −1
  341. lib/docs/scrapers/wordpress.rb +44 −0
  342. lib/docs/scrapers/yarn.rb +6 −2
  343. lib/docs/scrapers/yii.rb +4 −0
  344. lib/docs/subscribers/requester_subscriber.rb +13 −0
  345. lib/tasks/assets.thor +3 −0
  346. lib/tasks/docs.thor +117 −15
  347. lib/tasks/sprites.thor +213 −0
  348. lib/tasks/updates.thor +315 −0
  349. public/docs/docs.json +0 −1
  350. public/favicon.pxm +0 −0
  351. public/favicon@2x.pxm +0 −0
  352. public/icons/docs-1.pxm +0 −0
  353. public/icons/docs-1@2x.pxm +0 −0
  354. public/icons/docs-2.pxm +0 −0
  355. public/icons/docs-2@2x.pxm +0 −0
  356. public/icons/docs/bash/16.png +0 −0
  357. public/icons/docs/bash/16@2x.png +0 −0
  358. public/icons/docs/bash/SOURCE +1 −0
  359. public/icons/docs/bluebird/16@2x.png +0 −0
  360. public/icons/docs/composer/16.png +0 −0
  361. public/icons/docs/composer/16@2x.png +0 −0
  362. public/icons/docs/composer/SOURCE +1 −0
  363. public/icons/docs/css/CSS3.sketch/Data +0 −0
  364. public/icons/docs/css/CSS3.sketch/QuickLook/Preview.png +0 −0
  365. public/icons/docs/css/CSS3.sketch/QuickLook/Thumbnail.png +0 −0
  366. public/icons/docs/css/CSS3.sketch/fonts +0 −0
  367. public/icons/docs/css/CSS3.sketch/version +1 −0
  368. public/icons/docs/cypress/16.png +0 −0
  369. public/icons/docs/cypress/16@2x.png +0 −0
  370. public/icons/docs/cypress/SOURCE +1 −0
  371. public/icons/docs/dart/16.png +0 −0
  372. public/icons/docs/dart/16@2x.png +0 −0
  373. public/icons/docs/dart/SOURCE +1 −0
  374. public/icons/docs/dom/DOM.sketch/Data +0 −0
  375. public/icons/docs/dom/DOM.sketch/QuickLook/Preview.png +0 −0
  376. public/icons/docs/dom/DOM.sketch/QuickLook/Thumbnail.png +0 −0
  377. public/icons/docs/dom/DOM.sketch/fonts +0 −0
  378. public/icons/docs/dom/DOM.sketch/version +1 −0
  379. public/icons/docs/dom_events/DOM_events.sketch/Data +0 −0
  380. public/icons/docs/dom_events/DOM_events.sketch/QuickLook/Preview.png +0 −0
  381. public/icons/docs/dom_events/DOM_events.sketch/QuickLook/Thumbnail.png +0 −0
  382. public/icons/docs/dom_events/DOM_events.sketch/fonts +0 −0
  383. public/icons/docs/dom_events/DOM_events.sketch/version +1 −0
  384. public/icons/docs/express/16.pxm +0 −0
  385. public/icons/docs/express/16@2x.pxm +0 −0
  386. public/icons/docs/handlebars/16.png +0 −0
  387. public/icons/docs/handlebars/16@2x.png +0 −0
  388. public/icons/docs/handlebars/SOURCE +1 −0
  389. public/icons/docs/html/HTML5.sketch/Data +0 −0
  390. public/icons/docs/html/HTML5.sketch/QuickLook/Preview.png +0 −0
  391. public/icons/docs/html/HTML5.sketch/QuickLook/Thumbnail.png +0 −0
  392. public/icons/docs/html/HTML5.sketch/fonts +0 −0
  393. public/icons/docs/html/HTML5.sketch/version +1 −0
  394. public/icons/docs/http/16.pxm +0 −0
  395. public/icons/docs/http/16@2x.pxm +0 −0
  396. public/icons/docs/javascript/16.pxm +0 −0
  397. public/icons/docs/javascript/16@2x.pxm +0 −0
  398. public/icons/docs/knockout/16@2x.pxm +0 −0
  399. public/icons/docs/less/less.pxm +0 −0
  400. public/icons/docs/moment/moment.sketch/Data +0 −0
  401. public/icons/docs/moment/moment.sketch/QuickLook/Preview.png +0 −0
  402. public/icons/docs/moment/moment.sketch/QuickLook/Thumbnail.png +0 −0
  403. public/icons/docs/moment/moment.sketch/fonts +0 −0
  404. public/icons/docs/moment/moment.sketch/metadata +14 −0
  405. public/icons/docs/moment/moment.sketch/version +1 −0
  406. public/icons/docs/nokogiri/icon.pxm +0 −0
  407. public/icons/docs/php/16@2x.pxm +0 −0
  408. public/icons/docs/puppeteer/16.png +0 −0
  409. public/icons/docs/puppeteer/16@2x.png +0 −0
  410. public/icons/docs/puppeteer/SOURCE +1 −0
  411. public/icons/docs/pygame/16.png +0 −0
  412. public/icons/docs/pygame/16@2x.png +0 −0
  413. public/icons/docs/qt/16.png +0 −0
  414. public/icons/docs/qt/16@2x.png +0 −0
  415. public/icons/docs/qt/SOURCE +1 −0
  416. public/icons/docs/rails/rails.pxm +0 −0
  417. public/icons/docs/saltstack/16.png +0 −0
  418. public/icons/docs/saltstack/16@2x.png +0 −0
  419. public/icons/docs/saltstack/SOURCE +1 −0
  420. public/icons/docs/sass/sass.pxm +0 −0
  421. public/icons/docs/scala/16.png +0 −0
  422. public/icons/docs/scala/16@2x.png +0 −0
  423. public/icons/docs/support_tables/16.pxm +0 −0
  424. public/icons/docs/support_tables/16@2x.pxm +0 −0
  425. public/icons/docs/vue_router/16.png +0 −0
  426. public/icons/docs/vue_router/16@2x.png +0 −0
  427. public/icons/docs/vue_router/SOURCE +1 −0
  428. public/icons/docs/wordpress/16.png +0 −0
  429. public/icons/docs/wordpress/16@2x.png +0 −0
  430. public/icons/docs/wordpress/SOURCE +1 −0
  431. public/icons/docs/xpath/XPath.sketch/Data +0 −0
  432. public/icons/docs/xpath/XPath.sketch/QuickLook/Preview.png +0 −0
  433. public/icons/docs/xpath/XPath.sketch/QuickLook/Thumbnail.png +0 −0
  434. public/icons/docs/xpath/XPath.sketch/metadata +18 −0
  435. public/icons/docs/xpath/XPath.sketch/version +1 −0
  436. public/images/apple-icon.pxm +0 −0
  437. public/images/fluid-icon.pxm +0 −0
  438. public/images/icon.pxm +0 −0
  439. public/images/webapp-icon-192.png +0 −0
  440. public/images/webapp-icon-512.png +0 −0
  441. public/images/webapp-icon-small.pxm +0 −0
  442. public/manifest.json +11 −0
  443. test/app_test.rb +36 −90
  444. test/lib/docs/core/scrapers/file_scraper_test.rb +90 −48
  445. views/app.erb +1 −13
  446. views/index.erb +4 −3
  447. views/manifest.erb +0 −14
  448. views/other.erb +3 −2
  449. views/service-worker.js.erb +61 −0
  450. views/unsupported.erb +7 −10
.editorconfig+11−0
@@ -0,0 +1,11 @@
1root = true
2
3[*]
4charset = utf-8
5indent_style = space
6indent_size = 2
7insert_final_newline = true
8trim_trailing_whitespace = true
9
10[*.md]
11trim_trailing_whitespace = false
.github/CONTRIBUTING.mdrenamed from CONTRIBUTING.md+7−17
@@ -24,20 +24,18 @@ 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
30For general feedback and ideas, please use the [mailing list](https://groups.google.com/d/forum/devdocs).
31
32## Requesting new documentations30## Requesting new documentations
33 31
34Please don't open issues to request new documentations. 32Please don't open issues to request new documentations.
35Use 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.
36 34
37## Contributing code and features35## Contributing code and features
38 36
391. Search for existing issues; someone may already be working on a similar feature.371. Search for existing issues; someone may already be working on a similar feature.
402. Before embarking on any significant pull request, please open an issue describing the changes you intend to make. Otherwise you risk spending a lot of time working on something I may not want to merge. This also tells other contributors that you're working on the feature.382. Before embarking on any significant pull request, please open an issue describing the changes you intend to make. Otherwise you risk spending a lot of time working on something we may not want to merge. This also tells other contributors that you're working on the feature.
413. Follow the [coding conventions](#coding-conventions).393. Follow the [coding conventions](#coding-conventions).
424. If you're modifying the Ruby code, include tests and ensure they pass.404. If you're modifying the Ruby code, include tests and ensure they pass.
435. Try to keep your pull request small and simple.415. Try to keep your pull request small and simple.
@@ -46,7 +44,7 @@ Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) wher · 2 unmodified lines
46 44
47## Contributing new documentations45## Contributing new documentations
48 46
49See 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.
50 48
51**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.
52 50
@@ -57,20 +55,12 @@ In addition to the [guidelines for contributing code](#contributing-code-and-fea · 4 unmodified lines
57* 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.).
58* 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.
59* The number of types (categories) should ideally be less than 100.57* The number of types (categories) should ideally be less than 100.
60* Don't modify the icon sprite. I'll do it after your pull request is merged.
61 58
62## Updating existing documentations59## Updating existing documentations
63 60
64Please 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 only the version number and/or the attribution of a documentation. Do submit a pull request if a bigger change is required in the scraper and you've verified that it works.
65
66To ask that an existing documentation be updated, please use the [Trello board](https://trello.com/c/2B0hmW7M/52-request-updates-here).
67
68## Other contributions
69
70Besides new docs and features, here are other ways you can contribute:
71 62
72* **Improve our copy.** English isn't my first language so if you notice grammatical or usage errors, feel free to submit a pull request — it'll be much appreciated.63To ask that an existing documentation be updated, first check the latest [documentation versions report](https://github.com/freeCodeCamp/devdocs/issues?utf8=%E2%9C%93&q=Documentation+versions+report+is%3Aissue+author%3Adevdocs-bot+sort%3Acreated-desc). Only create an issue if the documentation has been wrongly marked as up-to-date.
73* **Participate in the issue tracker.** Your opinion matters — feel free to add comments to existing issues. You're also welcome to participate to the [mailing list](https://groups.google.com/d/forum/devdocs).
74 64
75## Coding conventions65## Coding conventions
76 66
@@ -80,4 +70,4 @@ Besides new docs and features, here are other ways you can contribute: · 3 unmodified lines
80 70
81## Questions?71## Questions?
82 72
83If you have any questions, please feel free to ask on the [mailing list](https://groups.google.com/d/forum/devdocs).73If you have any questions, please feel free to ask them on the contributor chat room on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs).
.github/ISSUE_TEMPLATE.md+7−0
@@ -0,0 +1,7 @@
1<!--
2 Please read the contributing guidelines before opening an issue:
3 https://github.com/freeCodeCamp/devdocs/blob/master/.github/CONTRIBUTING.md
4
5 Go here to request a new documentation:
6 https://trello.com/b/6BmTulfx/devdocs-documentation
7-->
.github/no-response.yml+8−0
@@ -0,0 +1,8 @@
1daysUntilClose: 30
2responseRequiredLabel: needs-info
3closeComment: >
4 This issue has been automatically closed because there has been no response
5 to our request for more information from the original author. With only the
6 information that’s currently in the issue, we don’t have enough information
7 to take action. Please comment if you have or find the answer we need so we
8 can investigate further.
.gitignore+3−4
@@ -1,10 +1,9 @@
1.DS_Store1.DS_Store
2.bundle2.bundle
3*.pxm3log
4*.sketch
5tmp4tmp
6public/assets5public/assets
7public/fonts6public/fonts
8public/docs/**/*7public/docs/**/*
9!public/docs/docs.json8docs/**/*
10!public/docs/**/index.json9!docs/*.md
.ruby-version+1−1
@@ -1 +1 @@
12.5.112.6.3
.slugignore+1−2
@@ -1,2 +1 @@
1public/icons1test
2test
.travis.yml+23−1
@@ -1,4 +1,26 @@
1language: ruby1language: ruby
2before_script:2
3addons:
4 apt:
5 packages:
6 - libcurl4-openssl-dev
7
8cache: bundler
9
10before_install:
11 - "echo 'gem: --no-document' > ~/.gemrc"
3 - gem update --system12 - gem update --system
4 - 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/>
COPYRIGHT+4−4
@@ -1,13 +1,13 @@
1Copyright 2013-2018 Thibaut Courouble and other contributors1Copyright 2013-2019 Thibaut Courouble and other contributors
2 2
3 This Source Code Form is subject to the terms of the Mozilla Public3 This Source Code Form is subject to the terms of the Mozilla Public
4 License, v. 2.0. If a copy of the MPL was not distributed with this4 License, v. 2.0. If a copy of the MPL was not distributed with this
5 file, You can obtain one at http://mozilla.org/MPL/2.0/.5 file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 6
7Please do not use the name DevDocs to endorse or promote products7Please do not use the name DevDocs to endorse or promote products
8derived from this software without my permission, except as may be8derived from this software without the maintainers' permission, except
9necessary to comply with the notice/attribution requirements.9as may be necessary to comply with the notice/attribution requirements.
10 10
11I also wish that any documentation file generated using this software11We also wish that any documentation file generated using this software
12be attributed to DevDocs. Let's be fair to all contributors by giving12be attributed to DevDocs. Let's be fair to all contributors by giving
13credit where credit's due. Thanks.13credit where credit's due. Thanks.
Dockerfile+5−2
@@ -1,9 +1,12 @@
1FROM ruby:2.5.11FROM ruby:2.6.3
2
3ENV LANG=C.UTF-8
4ENV ENABLE_SERVICE_WORKER=true
2 5
3WORKDIR /devdocs6WORKDIR /devdocs
4 7
5RUN apt-get update && \8RUN apt-get update && \
6 apt-get -y install git nodejs && \9 apt-get -y install git nodejs libcurl4 && \
7 gem install bundler && \10 gem install bundler && \
8 rm -rf /var/lib/apt/lists/*11 rm -rf /var/lib/apt/lists/*
9 12
Dockerfile-alpine+5−2
@@ -1,10 +1,13 @@
1FROM ruby:2.5.1-alpine1FROM ruby:2.6.3-alpine
2
3ENV LANG=C.UTF-8
4ENV ENABLE_SERVICE_WORKER=true
2 5
3WORKDIR /devdocs6WORKDIR /devdocs
4 7
5COPY . /devdocs8COPY . /devdocs
6 9
7RUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev && \10RUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev libcurl && \
8 gem install bundler && \11 gem install bundler && \
9 bundle install --system --without test && \12 bundle install --system --without test && \
10 thor docs:download --all && \13 thor docs:download --all && \
Gemfile+10−5
@@ -1,16 +1,20 @@
1source 'https://rubygems.org'1source 'https://rubygems.org'
2ruby '2.5.1'2ruby '2.6.3'
3 3
4gem 'rake'4gem 'rake'
5gem 'thor'5gem 'thor'
6gem 'pry', '~> 0.11.0'6gem 'pry', '~> 0.12.0'
7gem 'activesupport', '~> 5.2', require: false7gem 'activesupport', '~> 5.2', require: false
8gem 'yajl-ruby', require: false8gem 'yajl-ruby', require: false
9gem 'html-pipeline'
10gem 'typhoeus'
11gem 'nokogiri'
9 12
10group :app do13group :app do
11 gem 'rack'14 gem 'rack'
12 gem 'sinatra'15 gem 'sinatra'
13 gem 'sinatra-contrib'16 gem 'sinatra-contrib'
17 gem 'rack-ssl-enforcer'
14 gem 'thin'18 gem 'thin'
15 gem 'sprockets'19 gem 'sprockets'
16 gem 'sprockets-helpers'20 gem 'sprockets-helpers'
@@ -18,6 +22,8 @@ group :app do · 1 unmodified line
18 gem 'browser'22 gem 'browser'
19 gem 'sass'23 gem 'sass'
20 gem 'coffee-script'24 gem 'coffee-script'
25 gem 'chunky_png'
26 gem 'sprockets-sass'
21end27end
22 28
23group :production do29group :production do
@@ -30,14 +36,13 @@ group :development do · 6 unmodified lines
30end36end
31 37
32group :docs do38group :docs do
33 gem 'typhoeus'
34 gem 'nokogiri'
35 gem 'html-pipeline'
36 gem 'image_optim'39 gem 'image_optim'
37 gem 'image_optim_pack', platforms: :ruby40 gem 'image_optim_pack', platforms: :ruby
38 gem 'progress_bar', require: false41 gem 'progress_bar', require: false
39 gem 'unix_utils', require: false42 gem 'unix_utils', require: false
40 gem 'tty-pager', require: false43 gem 'tty-pager', require: false
44 gem 'net-sftp', '>= 2.1.3.rc2', require: false
45 gem 'terminal-table', require: false
41end46end
42 47
43group :test do48group :test do
Gemfile.lock+75−60
@@ -1,124 +1,134 @@
1GEM1GEM
2 remote: https://rubygems.org/2 remote: https://rubygems.org/
3 specs:3 specs:
4 activesupport (5.2.0)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.3)9 backports (3.15.0)
10 better_errors (2.4.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.0.5)21 concurrent-ruby (1.1.5)
21 daemons (1.2.6)22 daemons (1.3.1)
22 erubi (1.7.1)23 erubi (1.8.0)
23 ethon (0.11.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.4)28 exifr (1.3.6)
28 ffi (1.9.25)29 ffi (1.11.1)
29 fspath (3.1.0)30 fspath (3.1.2)
30 highline (1.7.10)31 highline (2.0.2)
31 html-pipeline (2.8.3)32 html-pipeline (2.11.1)
32 activesupport (>= 2)33 activesupport (>= 2)
33 nokogiri (>= 1.4)34 nokogiri (>= 1.4)
34 i18n (1.0.1)35 i18n (1.6.0)
35 concurrent-ruby (~> 1.0)36 concurrent-ruby (~> 1.0)
36 image_optim (0.26.1)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)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)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 (1.5.0)46 image_size (2.0.2)
46 in_threads (1.5.0)47 in_threads (1.5.3)
47 method_source (0.9.0)48 method_source (0.9.2)
48 mini_portile2 (2.3.0)49 mini_portile2 (2.4.0)
49 minitest (5.11.3)50 minitest (5.11.3)
50 multi_json (1.13.1)51 multi_json (1.13.1)
51 mustermann (1.0.2)52 mustermann (1.0.3)
52 newrelic_rpm (5.2.0.345)53 net-sftp (3.0.0.beta1)
53 nokogiri (1.8.4)54 net-ssh (>= 5.0.0, < 6.0.0)
54 mini_portile2 (~> 2.3.0)55 net-ssh (5.2.0)
56 newrelic_rpm (6.5.0.357)
57 nokogiri (1.10.3)
58 mini_portile2 (~> 2.4.0)
55 options (2.3.2)59 options (2.3.2)
56 progress (3.4.0)60 progress (3.5.2)
57 progress_bar (1.2.0)61 progress_bar (1.3.0)
58 highline (~> 1.6)62 highline (>= 1.6, < 3)
59 options (~> 2.3.0)63 options (~> 2.3.0)
60 pry (0.11.3)64 pry (0.12.2)
61 coderay (~> 1.1.0)65 coderay (~> 1.1.0)
62 method_source (~> 0.9.0)66 method_source (~> 0.9.0)
63 rack (2.0.5)67 rack (2.0.7)
64 rack-protection (2.0.3)68 rack-protection (2.0.5)
65 rack69 rack
66 rack-test (1.0.0)70 rack-ssl-enforcer (0.2.9)
71 rack-test (1.1.0)
67 rack (>= 1.0, < 3)72 rack (>= 1.0, < 3)
68 rake (12.3.1)73 rake (12.3.2)
69 rb-fsevent (0.10.3)74 rb-fsevent (0.10.3)
70 rb-inotify (0.9.10)75 rb-inotify (0.10.0)
71 ffi (>= 0.5.0, < 2)76 ffi (~> 1.0)
72 rr (1.2.1)77 rr (1.2.1)
73 sass (3.5.6)78 sass (3.7.4)
74 sass-listen (~> 4.0.0)79 sass-listen (~> 4.0.0)
75 sass-listen (4.0.0)80 sass-listen (4.0.0)
76 rb-fsevent (~> 0.9, >= 0.9.4)81 rb-fsevent (~> 0.9, >= 0.9.4)
77 rb-inotify (~> 0.9, >= 0.9.7)82 rb-inotify (~> 0.9, >= 0.9.7)
78 sinatra (2.0.3)83 sinatra (2.0.5)
79 mustermann (~> 1.0)84 mustermann (~> 1.0)
80 rack (~> 2.0)85 rack (~> 2.0)
81 rack-protection (= 2.0.3)86 rack-protection (= 2.0.5)
82 tilt (~> 2.0)87 tilt (~> 2.0)
83 sinatra-contrib (2.0.3)88 sinatra-contrib (2.0.5)
84 activesupport (>= 4.0.0)
85 backports (>= 2.8.2)89 backports (>= 2.8.2)
86 multi_json90 multi_json
87 mustermann (~> 1.0)91 mustermann (~> 1.0)
88 rack-protection (= 2.0.3)92 rack-protection (= 2.0.5)
89 sinatra (= 2.0.3)93 sinatra (= 2.0.5)
90 tilt (>= 1.3, < 3)94 tilt (>= 1.3, < 3)
91 sprockets (3.7.2)95 sprockets (3.7.2)
92 concurrent-ruby (~> 1.0)96 concurrent-ruby (~> 1.0)
93 rack (> 1, < 3)97 rack (> 1, < 3)
94 sprockets-helpers (1.2.1)98 sprockets-helpers (1.2.1)
95 sprockets (>= 2.2)99 sprockets (>= 2.2)
96 strings (0.1.1)100 sprockets-sass (2.0.0.beta2)
97 unicode-display_width (~> 1.3.0)101 sprockets (>= 2.0, < 4.0)
98 unicode_utils (~> 1.4.0)102 strings (0.1.5)
103 strings-ansi (~> 0.1)
104 unicode-display_width (~> 1.5)
105 unicode_utils (~> 1.4)
106 strings-ansi (0.1.0)
107 terminal-table (1.8.0)
108 unicode-display_width (~> 1.1, >= 1.1.1)
99 thin (1.7.2)109 thin (1.7.2)
100 daemons (~> 1.0, >= 1.0.9)110 daemons (~> 1.0, >= 1.0.9)
101 eventmachine (~> 1.0, >= 1.0.4)111 eventmachine (~> 1.0, >= 1.0.4)
102 rack (>= 1, < 3)112 rack (>= 1, < 3)
103 thor (0.20.0)113 thor (0.20.3)
104 thread_safe (0.3.6)114 thread_safe (0.3.6)
105 tilt (2.0.8)115 tilt (2.0.9)
106 tty-pager (0.11.0)116 tty-pager (0.12.1)
107 strings (~> 0.1.0)117 strings (~> 0.1.4)
108 tty-screen (~> 0.6.4)118 tty-screen (~> 0.6)
109 tty-which (~> 0.3.0)119 tty-which (~> 0.4)
110 tty-screen (0.6.4)120 tty-screen (0.7.0)
111 tty-which (0.3.0)121 tty-which (0.4.1)
112 typhoeus (1.3.0)122 typhoeus (1.3.1)
113 ethon (>= 0.9.0)123 ethon (>= 0.9.0)
114 tzinfo (1.2.5)124 tzinfo (1.2.5)
115 thread_safe (~> 0.1)125 thread_safe (~> 0.1)
116 uglifier (4.1.14)126 uglifier (4.1.20)
117 execjs (>= 0.3.0, < 3)127 execjs (>= 0.3.0, < 3)
118 unicode-display_width (1.3.3)128 unicode-display_width (1.6.0)
119 unicode_utils (1.4.0)129 unicode_utils (1.4.0)
120 unix_utils (0.0.15)130 unix_utils (0.0.15)
121 yajl-ruby (1.4.0)131 yajl-ruby (1.4.1)
122 132
123PLATFORMS133PLATFORMS
124 ruby134 ruby
@@ -127,17 +137,20 @@ DEPENDENCIES · 2 unmodified lines
127 activesupport (~> 5.2)137 activesupport (~> 5.2)
128 better_errors138 better_errors
129 browser139 browser
140 chunky_png
130 coffee-script141 coffee-script
131 erubi142 erubi
132 html-pipeline143 html-pipeline
133 image_optim144 image_optim
134 image_optim_pack145 image_optim_pack
135 minitest146 minitest
147 net-sftp (>= 2.1.3.rc2)
136 newrelic_rpm148 newrelic_rpm
137 nokogiri149 nokogiri
138 progress_bar150 progress_bar
139 pry (~> 0.11.0)151 pry (~> 0.12.0)
140 rack152 rack
153 rack-ssl-enforcer
141 rack-test154 rack-test
142 rake155 rake
143 rr156 rr
@@ -146,6 +159,8 @@ DEPENDENCIES · 2 unmodified lines
146 sinatra-contrib159 sinatra-contrib
147 sprockets160 sprockets
148 sprockets-helpers161 sprockets-helpers
162 sprockets-sass
163 terminal-table
149 thin164 thin
150 thor165 thor
151 tty-pager166 tty-pager
@@ -155,7 +170,7 @@ DEPENDENCIES · 3 unmodified lines
155 yajl-ruby170 yajl-ruby
156 171
157RUBY VERSION172RUBY VERSION
158 ruby 2.5.1p57173 ruby 2.6.3p62
159 174
160BUNDLED WITH175BUNDLED WITH
161 1.16.1176 2.0.2
ISSUE_TEMPLATE.md+0−7
@@ -1,7 +0,0 @@
1<!--
2 Please read the contributing guidelines before opening an issue:
3 https://github.com/freeCodeCamp/devdocs/blob/master/CONTRIBUTING.md
4
5 To request a new documentation, or an update of an existing documentation, go here:
6 https://trello.com/b/6BmTulfx/devdocs-documentation
7-->
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+46−25
@@ -1,25 +1,24 @@
1# [DevDocs](https://devdocs.io) [![Build Status](https://travis-ci.org/freeCodeCamp/devdocs.svg?branch=master)](https://travis-ci.org/freeCodeCamp/devdocs)1# [DevDocs](https://devdocs.io) — API Documentation Browser [![Build Status](https://travis-ci.org/freeCodeCamp/devdocs.svg?branch=master)](https://travis-ci.org/freeCodeCamp/devdocs)
2 2
3DevDocs combines multiple API documentations in a fast, organized, and searchable interface.3DevDocs combines multiple developer documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.
4 4
5* Created by [Thibaut Courouble](https://thibaut.me)5DevDocs was created by [Thibaut Courouble](https://thibaut.me) and is operated by [freeCodeCamp](https://www.freecodecamp.org).
6 6
7Keep track of development news:7Keep track of development news:
8 8
9* Join the contributor chat room on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs)9* Join the contributor chat room on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs)
10* Watch the repository on [GitHub](https://github.com/freeCodeCamp/devdocs/subscription)10* Watch the repository on [GitHub](https://github.com/freeCodeCamp/devdocs/subscription)
11* Follow [@DevDocs](https://twitter.com/DevDocs) on Twitter11* Follow [@DevDocs](https://twitter.com/DevDocs) on Twitter
12* Join the [mailing list](https://groups.google.com/d/forum/devdocs)
13 12
14**Table of Contents:** [Quick Start](#quick-start) · [Vision](#vision) · [App](#app) · [Scraper](#scraper) · [Commands](#available-commands) · [Contributing](#contributing) · [License](#copyright--license) · [Questions?](#questions)13**Table of Contents:** [Quick Start](#quick-start) · [Vision](#vision) · [App](#app) · [Scraper](#scraper) · [Commands](#available-commands) · [Contributing](#contributing) · [Documentation](#documentation) · [Plugins and Extensions](#plugins-and-extensions) · [License](#copyright--license) · [Questions?](#questions)
15 14
16## Quick Start15## Quick Start
17 16
18Unless you wish to contribute to the project, I recommend using the hosted version at [devdocs.io](https://devdocs.io). It's up-to-date and works offline out-of-the-box.17Unless you wish to contribute to the project, we recommend using the hosted version at [devdocs.io](https://devdocs.io). It's up-to-date and works offline out-of-the-box.
19 18
20DevDocs is made of two pieces: a Ruby scraper that generates the documentation and metadata, and a JavaScript app powered by a small Sinatra app.19DevDocs is made of two pieces: a Ruby scraper that generates the documentation and metadata, and a JavaScript app powered by a small Sinatra app.
21 20
22DevDocs requires Ruby 2.5.1, libcurl, and a JavaScript runtime supported by [ExecJS](https://github.com/rails/execjs#readme) (included in OS X and Windows; [Node.js](https://nodejs.org/en/) on Linux). Once you have these installed, run the following commands:21DevDocs requires Ruby 2.6.0, libcurl, and a JavaScript runtime supported by [ExecJS](https://github.com/rails/execjs#readme) (included in OS X and Windows; [Node.js](https://nodejs.org/en/) on Linux). Once you have these installed, run the following commands:
23 22
24```23```
25git clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs24git clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs
@@ -60,18 +59,14 @@ The web app is all client-side JavaScript, written in [CoffeeScript](http://coff · 34 unmodified lines
60 59
61Many 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.
62 61
63Another 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.
64 63
65DevDocs being a developer tool, the browser requirements are high:64DevDocs being a developer tool, the browser requirements are high:
66 65
671. On the desktop:66* Recent versions of Firefox, Chrome, or Opera
68 * Recent version of Chrome, Firefox, or Opera67* Safari 11.1+
69 * Safari 8+68* Edge 17+
70 * IE / Edge 10+69* iOS 11.3+
712. On mobile:
72 * iOS 8+
73 * Android 4.1+
74 * Windows Phone 8+
75 70
76This 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!
77 72
@@ -88,12 +83,13 @@ Modifications made to each document include: · 10 unmodified lines
88* replacing all external (not scraped) URLs with their fully qualified counterpart83* replacing all external (not scraped) URLs with their fully qualified counterpart
89* replacing all internal (scraped) URLs with their unqualified and relative counterpart84* replacing all internal (scraped) URLs with their unqualified and relative counterpart
90* 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/)
91 87
92These 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.
93 89
94The 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.).
95 91
96More 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.
97 93
98## Available Commands94## Available Commands
99 95
@@ -133,20 +129,45 @@ If multiple versions of Ruby are installed on your system, commands must be run · 33 unmodified lines
133 129
134## Contributing130## Contributing
135 131
136Contributions are welcome. Please read the [contributing guidelines](https://github.com/freeCodeCamp/devdocs/blob/master/CONTRIBUTING.md).132Contributions are welcome. Please read the [contributing guidelines](./.github/CONTRIBUTING.md).
137 133
138DevDocs's own documentation is available on the [wiki](https://github.com/freeCodeCamp/devdocs/wiki).134## Documentation
135
136* [Adding documentations to DevDocs](./docs/adding-docs.md)
137* [Scraper Reference](./docs/scraper-reference.md)
138* [Filter Reference](./docs/filter-reference.md)
139* [Maintainers’ Guide](./docs/maintainers.md)
140
141## Plugins and Extensions
142
143* [Chrome web app](https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe)
144* [Ubuntu Touch app](https://uappexplorer.com/app/devdocsunofficial.berkes)
145* [Sublime Text plugin](https://sublime.wbond.net/packages/DevDocs)
146* [Atom plugin](https://atom.io/packages/devdocs)
147* [Brackets extension](https://github.com/gruehle/dev-docs-viewer)
148* [Fluid](http://fluidapp.com) for turning DevDocs into a real OS X app
149* [GTK shell / Vim integration](https://github.com/naquad/devdocs-shell)
150* [Emacs lookup](https://github.com/skeeto/devdocs-lookup)
151* [Alfred Workflow](https://github.com/yannickglt/alfred-devdocs)
152* [Vim search plugin with Devdocs in its defaults](https://github.com/waiting-for-dev/vim-www) Just set `let g:www_shortcut_engines = { 'devdocs': ['Devdocs', '<leader>dd'] }` to have a `:Devdocs` command and a `<leader>dd` mapping.
153* [Visual Studio Code plugin](https://marketplace.visualstudio.com/items?itemName=akfish.vscode-devdocs ) (1)
154* [Visual Studio Code plugin](https://marketplace.visualstudio.com/items?itemName=deibit.devdocs) (2)
155* [Desktop application](https://github.com/egoist/devdocs-desktop)
156* [Doc Browser](https://github.com/qwfy/doc-browser) is a native Linux app that supports DevDocs docsets
157* [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
139 160
140## Copyright / License161## Copyright / License
141 162
142Copyright 2013-2018 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)
143 164
144This 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.
145 166
146Please do not use the name DevDocs to endorse or promote products derived from this software without my 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.
147 168
148I also wish that any documentation file generated using this software be attributed to DevDocs. Let's be fair to all contributors by giving credit where credit's due. Thanks!169We also wish that any documentation file generated using this software be attributed to DevDocs. Let's be fair to all contributors by giving credit where credit's due. Thanks!
149 170
150## Questions?171## Questions?
151 172
152If you have any questions, please feel free to ask them on the [mailing list](https://groups.google.com/d/forum/devdocs).173If you have any questions, please feel free to ask them on the contributor chat room on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs).
Rakefile+4−0
@@ -3,6 +3,7 @@ · 2 unmodified lines
3require 'bundler/setup'3require 'bundler/setup'
4require 'thor'4require 'thor'
5 5
6Bundler.require :default
6$LOAD_PATH.unshift 'lib'7$LOAD_PATH.unshift 'lib'
7 8
8task :default do9task :default do
@@ -13,6 +14,9 @@ end · 4 unmodified lines
13namespace :assets do14namespace :assets do
14 desc 'Compile all assets'15 desc 'Compile all assets'
15 task :precompile do16 task :precompile do
17 load 'tasks/docs.thor'
18 DocsCLI.new.prepare_deploy
19
16 load 'tasks/assets.thor'20 load 'tasks/assets.thor'
17 AssetsCLI.new.compile21 AssetsCLI.new.compile
18 end22 end
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+21−14
@@ -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
@@ -76,7 +78,7 @@ · 53 unmodified lines
76 .install()78 .install()
77 @previousErrorHandler = onerror79 @previousErrorHandler = onerror
78 window.onerror = @onWindowError.bind(@)80 window.onerror = @onWindowError.bind(@)
79 CookieStore.onBlocked = @onCookieBlocked81 CookiesStore.onBlocked = @onCookieBlocked
80 return82 return
81 83
82 bootOne: ->84 bootOne: ->
@@ -138,7 +140,10 @@ · 55 unmodified lines
138 @docs.sort()140 @docs.sort()
139 @initDoc(doc)141 @initDoc(doc)
140 @saveDocs()142 @saveDocs()
141 _onSuccess()143 if app.settings.get('autoInstall')
144 doc.install(_onSuccess, onError)
145 else
146 _onSuccess()
142 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')
@@ -157,17 +162,24 @@ · 3 unmodified lines
157 new app.views.Updates()162 new app.views.Updates()
158 @updateChecker = new app.UpdateChecker()163 @updateChecker = new app.UpdateChecker()
159 164
165 reboot: ->
166 if location.pathname isnt '/' and location.pathname isnt '/settings'
167 window.location = "/##{location.pathname}"
168 else
169 window.location = '/'
170 return
171
160 reload: ->172 reload: ->
161 @docs.clearCache()173 @docs.clearCache()
162 @disabledDocs.clearCache()174 @disabledDocs.clearCache()
163 if @appCache then @appCache.reload() else window.location = '/'175 if @serviceWorker then @serviceWorker.reload() else @reboot()
164 return176 return
165 177
166 reset: ->178 reset: ->
167 @localStorage.reset()179 @localStorage.reset()
168 @settings.reset()180 @settings.reset()
169 @db?.reset()181 @db?.reset()
170 @appCache?.update()182 @serviceWorker?.update()
171 window.location = '/'183 window.location = '/'
172 return184 return
173 185
@@ -186,9 +198,9 @@ · 12 unmodified lines
186 return198 return
187 199
188 indexHost: ->200 indexHost: ->
189 # 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
190 # enabled because it doesn't support caching URLs that use CORS.202 # enabled because it doesn't support caching URLs that use CORS.
191 @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']
192 204
193 onBootError: (args...) ->205 onBootError: (args...) ->
194 @trigger 'bootError'206 @trigger 'bootError'
@@ -245,7 +257,7 @@ · 50 unmodified lines
245 matchMedia: !!window.matchMedia257 matchMedia: !!window.matchMedia
246 insertAdjacentHTML: !!document.body.insertAdjacentHTML258 insertAdjacentHTML: !!document.body.insertAdjacentHTML
247 defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false259 defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false
248 cssGradients: supportsCssGradients()260 cssVariables: !!CSS?.supports?('(--t: 0)')
249 261
250 for key, value of features when not value262 for key, value of features when not value
251 Raven.captureMessage "unsupported/#{key}", level: 'info'263 Raven.captureMessage "unsupported/#{key}", level: 'info'
@@ -268,9 +280,4 @@ · 16 unmodified lines
268 isInvalidLocation: ->280 isInvalidLocation: ->
269 @config.env is 'production' and location.host.indexOf(app.config.production_host) isnt 0281 @config.env is 'production' and location.host.indexOf(app.config.production_host) isnt 0
270 282
271supportsCssGradients = ->
272 el = document.createElement('div')
273 el.style.cssText = "background-image: -webkit-linear-gradient(top, #000, #fff); background-image: linear-gradient(to top, #000, #fff);"
274 el.style.backgroundImage.indexOf('gradient') >= 0
275
276$.extend app, Events283$.extend app, Events
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', -> window.location = '/'
31 @notifyUpdate = false
32 @notifyProgress = true
33 try @cache.update() catch
34 return
35
36 onProgress: (event) =>
37 @trigger 'progress', event if @notifyProgress
38 return
39
40 onUpdateReady: =>
41 @trigger 'updateready' if @notifyUpdate
42 return
assets/javascripts/app/config.coffee.erb+3−0
@@ -13,3 +13,6 @@ app.config = · 12 unmodified lines
13 version: <%= Time.now.to_i %>13 version: <%= Time.now.to_i %>
14 release: <%= Time.now.utc.httpdate.to_json %>14 release: <%= Time.now.utc.httpdate.to_json %>
15 mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'15 mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'
16 favicon_spritesheet: '<%= image_path('sprites/docs.png') %>'
17 service_worker_path: '/service-worker.js'
18 service_worker_enabled: <%= App.environment == :production || ENV['ENABLE_SERVICE_WORKER'] == 'true' %>
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+34−1
@@ -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,9 +30,10 @@ 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 CookiesStore
32 @cache = {}37 @cache = {}
33 38
34 get: (key) ->39 get: (key) ->
@@ -38,6 +43,7 @@ class app.Settings · 3 unmodified lines
38 set: (key, value) ->43 set: (key, value) ->
39 @store.set(key, value)44 @store.set(key, value)
40 delete @cache[key]45 delete @cache[key]
46 @toggleDark(value) if key == 'dark'
41 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/shortcuts.coffee+3−0
@@ -149,6 +149,9 @@ class app.Shortcuts · 148 unmodified lines
149 when 40149 when 40
150 @trigger 'altDown'150 @trigger 'altDown'
151 false151 false
152 when 68
153 @trigger 'altD'
154 false
152 when 70155 when 70
153 @trigger 'altF', event156 @trigger 'altF', event
154 when 71157 when 71
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/cookies_store.coffeerenamed from assets/javascripts/lib/cookie_store.coffee+5−1
@@ -1,4 +1,8 @@
1class @CookieStore1class @CookiesStore
2 # Intentionally called CookiesStore instead of CookieStore
3 # Calling it CookieStore causes issues when the Experimental Web Platform features flag is enabled in Chrome
4 # Related issue: https://github.com/freeCodeCamp/devdocs/issues/932
5
2 INT = /^\d+$/6 INT = /^\d+$/
3 7
4 @onBlocked: ->8 @onBlocked: ->
assets/javascripts/lib/favicon.coffee+64−0
@@ -0,0 +1,64 @@
1defaultUrl = null
2currentSlug = null
3
4imageCache = {}
5urlCache = {}
6
7withImage = (url, action) ->
8 if imageCache[url]
9 action(imageCache[url])
10 else
11 img = new Image()
12 img.crossOrigin = 'anonymous'
13 img.src = url
14 img.onload = () =>
15 imageCache[url] = img
16 action(img)
17
18@setFaviconForDoc = (doc) ->
19 return if currentSlug == doc.slug
20
21 favicon = $('link[rel="icon"]')
22
23 if defaultUrl == null
24 defaultUrl = favicon.href
25
26 if urlCache[doc.slug]
27 favicon.href = urlCache[doc.slug]
28 currentSlug = doc.slug
29 return
30
31 styles = window.getComputedStyle($("._icon-#{doc.slug.split('~')[0]}"), ':before')
32
33 bgUrl = app.config.favicon_spritesheet
34 sourceSize = 16
35 sourceX = Math.abs(parseInt(styles['background-position-x'].slice(0, -2)))
36 sourceY = Math.abs(parseInt(styles['background-position-y'].slice(0, -2)))
37
38 withImage(bgUrl, (docImg) ->
39 withImage(defaultUrl, (defaultImg) ->
40 size = defaultImg.width
41
42 canvas = document.createElement('canvas')
43 ctx = canvas.getContext('2d')
44
45 canvas.width = size
46 canvas.height = size
47 ctx.drawImage(defaultImg, 0, 0)
48
49 docIconPercentage = 65
50 destinationCoords = size / 100 * (100 - docIconPercentage)
51 destinationSize = size / 100 * docIconPercentage
52 ctx.drawImage(docImg, sourceX, sourceY, sourceSize, sourceSize, destinationCoords, destinationCoords, destinationSize, destinationSize)
53
54 urlCache[doc.slug] = canvas.toDataURL()
55 favicon.href = urlCache[doc.slug]
56
57 currentSlug = doc.slug
58 )
59 )
60
61@resetFavicon = () ->
62 if defaultUrl != null and currentSlug != null
63 $('link[rel="icon"]').href = defaultUrl
64 currentSlug = null
assets/javascripts/lib/license.coffee+1−1
@@ -1,5 +1,5 @@
1###1###
2 * Copyright 2013-2018 Thibaut Courouble and other contributors2 * Copyright 2013-2019 Thibaut Courouble and other contributors
3 *3 *
4 * This source code is licensed under the terms of the Mozilla4 * This source code is licensed under the terms of the Mozilla
5 * Public License, v. 2.0, a copy of which may be obtained at:5 * Public License, v. 2.0, a copy of which may be obtained at:
assets/javascripts/lib/page.coffee+18−2
@@ -190,7 +190,7 @@ isSameOrigin = (url) -> · 189 unmodified lines
190 190
191updateCanonicalLink = ->191updateCanonicalLink = ->
192 @canonicalLink ||= document.head.querySelector('link[rel="canonical"]')192 @canonicalLink ||= document.head.querySelector('link[rel="canonical"]')
193 @canonicalLink.setAttribute('href', "http://#{location.host}#{location.pathname}")193 @canonicalLink.setAttribute('href', "https://#{location.host}#{location.pathname}")
194 194
195trackers = []195trackers = []
196 196
@@ -199,5 +199,21 @@ page.track = (fn) -> · 2 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] == '_' && name[1] != '_'
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+17−0
@@ -1,5 +1,22 @@
1[1[
2 [2 [
3 "2019-07-21",
4 "Fixed several bugs, added an option to automatically download documentation and <a href=\"https://github.com/freeCodeCamp/devdocs/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+sort%3Aupdated-desc+closed%3A%3E2019-07-18+977+988+986+870+886+1024+979+975+941+831+1005+848+942\" target=\"_blank\">more</a>."
5 ],
6 [
7 "2019-07-19",
8 "Replaced the AppCache with a Service Worker (which makes DevDocs an installable PWA) and fixed layout preferences on Firefox."
9 ],
10 [
11 "2018-09-23",
12 "New documentations: <a href=\"/puppeteer/\">Puppeteer</a> and <a href=\"/handlebars/\">Handlebars.js</a>"
13 ], [
14 "2018-08-12",
15 "New documentations: <a href=\"/dart/\">Dart</a> and <a href=\"/qt/\">Qt</a>"
16 ], [
17 "2018-07-29",
18 "New documentations: <a href=\"/bash/\">Bash</a>, <a href=\"/graphite/\">Graphite</a> and <a href=\"/pygame/\">Pygame</a>"
19 ], [
3 "2018-07-08",20 "2018-07-08",
4 "New documentations: <a href=\"/leaflet/\">Leaflet</a>, <a href=\"/terraform/\">Terraform</a> and <a href=\"/koa/\">Koa</a>"21 "New documentations: <a href=\"/leaflet/\">Leaflet</a>, <a href=\"/terraform/\">Terraform</a> and <a href=\"/koa/\">Koa</a>"
5 ], [22 ], [
assets/javascripts/templates/error_tmpl.coffee+9−12
@@ -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
@@ -56,21 +56,18 @@ app.templates.unsupportedBrowser = """ · 36 unmodified lines
56 <h1 class="_fail-title">Your browser is unsupported, sorry.</h1>56 <h1 class="_fail-title">Your browser is unsupported, sorry.</h1>
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 Chrome and Firefox59 <li>Recent versions of Firefox, Chrome, or Opera
60 <li>Safari 5.1+60 <li>Safari 11.1+
61 <li>Opera 12.1+61 <li>Edge 17+
62 <li>Internet Explorer 10+62 <li>iOS 11.3+
63 <li>iOS 6+
64 <li>Android 4.1+
65 <li>Windows Phone 8+
66 </ul>63 </ul>
67 <p class="_fail-text">64 <p class="_fail-text">
68 If you're unable to upgrade, I apologize.65 If you're unable to upgrade, we apologize.
69 I decided to prioritize speed and new features over support for older browsers.66 We decided to prioritize speed and new features over support for older browsers.
70 <p class="_fail-text">67 <p class="_fail-text">
71 Note: if you're already using one of the browsers above, check your settings and add-ons.68 Note: if you're already using one of the browsers above, check your settings and add-ons.
72 The app uses feature detection, not user agent sniffing.69 The app uses feature detection, not user agent sniffing.
73 <p class="_fail-text">70 <p class="_fail-text">
74 &mdash; Thibaut <a href="https://twitter.com/DevDocs" class="_fail-link">@DevDocs</a>71 &mdash; <a href="https://twitter.com/DevDocs">@DevDocs</a>
75 </div>72 </div>
76"""73"""
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+96−31
@@ -11,22 +11,18 @@ app.templates.aboutPage = -> """ · 10 unmodified lines
11 </nav>11 </nav>
12 12
13 <h1 class="_lined-heading">DevDocs: API Documentation Browser</h1>13 <h1 class="_lined-heading">DevDocs: API Documentation Browser</h1>
14 <p>DevDocs combines multiple API documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.14 <p>DevDocs combines multiple developer documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.
15 <ul>15 <p>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>. It was created by <a href="https://thibaut.me">Thibaut Courouble</a> and is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a>.
16 <li>Created and maintained by <a href="https://thibaut.me">Thibaut Courouble</a>
17 <li>Free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>
18 <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20" tabindex="-1"></iframe>
19 </ul>
20 <p>To keep up-to-date with the latest news:16 <p>To keep up-to-date with the latest news:
21 <ul>17 <ul>
22 <li>Follow <a href="https://twitter.com/DevDocs">@DevDocs</a> on Twitter18 <li>Follow <a href="https://twitter.com/DevDocs">@DevDocs</a> on Twitter
23 <li>Watch the repository on <a href="https://github.com/freeCodeCamp/devdocs/subscription">GitHub</a>19 <li>Watch the repository on <a href="https://github.com/freeCodeCamp/devdocs/subscription">GitHub</a> <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20" tabindex="-1"></iframe>
24 <li>Join the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>20 <li>Join the <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a> chat room
25 </ul>21 </ul>
26 22
27 <h2 class="_block-heading" id="copyright">Copyright and License</h2>23 <h2 class="_block-heading" id="copyright">Copyright and License</h2>
28 <p class="_note">24 <p class="_note">
29 <strong>Copyright 2013&ndash;2018 Thibaut Courouble and <a href="https://github.com/freeCodeCamp/devdocs/graphs/contributors">other contributors</a></strong><br>25 <strong>Copyright 2013&ndash;2019 Thibaut Courouble and <a href="https://github.com/freeCodeCamp/devdocs/graphs/contributors">other contributors</a></strong><br>
30 This software is licensed under the terms of the Mozilla Public License v2.0.<br>26 This software is licensed under the terms of the Mozilla Public License v2.0.<br>
31 You may obtain a copy of the source code at <a href="https://github.com/freeCodeCamp/devdocs">github.com/freeCodeCamp/devdocs</a>.<br>27 You may obtain a copy of the source code at <a href="https://github.com/freeCodeCamp/devdocs">github.com/freeCodeCamp/devdocs</a>.<br>
32 For more information, see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a>28 For more information, see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a>
@@ -48,11 +44,10 @@ app.templates.aboutPage = -> """ · 15 unmodified lines
48 <dt>Where can I suggest new docs and features?44 <dt>Where can I suggest new docs and features?
49 <dd>You can suggest and vote for new docs on the <a href="https://trello.com/b/6BmTulfx/devdocs-documentation">Trello board</a>.<br>45 <dd>You can suggest and vote for new docs on the <a href="https://trello.com/b/6BmTulfx/devdocs-documentation">Trello board</a>.<br>
50 If you have a specific feature request, add it to the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>.<br>46 If you have a specific feature request, add it to the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>.<br>
51 Otherwise use the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>.47 Otherwise, come talk to us in the <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a> chat room.
52 <dt>Where can I report bugs?48 <dt>Where can I report bugs?
53 <dd>In the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>. Thanks!49 <dd>In the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>. Thanks!
54 </dl>50 </dl>
55 <p>For anything else, feel free to email me at <a href="mailto:thibaut@devdocs.io">thibaut@devdocs.io</a>.
56 51
57 <h2 class="_block-heading" id="credits">Credits</h2>52 <h2 class="_block-heading" id="credits">Credits</h2>
58 53
@@ -76,12 +71,13 @@ app.templates.aboutPage = -> """ · 17 unmodified lines
76 71
77 <h2 class="_block-heading" id="privacy">Privacy Policy</h2>72 <h2 class="_block-heading" id="privacy">Privacy Policy</h2>
78 <ul>73 <ul>
79 <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by Thibaut Courouble ("We").74 <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a> ("We").
80 <li>We do not collect personal information.75 <li>We do not collect personal information through the app.
81 <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.
82 <li>The app uses cookies to store user preferences.78 <li>The app uses cookies to store user preferences.
83 <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.
84 <li>If you have any questions regarding privacy, please email <a href="mailto:thibaut@devdocs.io">thibaut@devdocs.io</a>.80 <li>If you have any questions regarding privacy, please email <a href="mailto:privacy@freecodecamp.org">privacy@freecodecamp.org</a>.
85 </ul>81 </ul>
86"""82"""
87 83
@@ -92,7 +88,7 @@ credits = [ · 4 unmodified lines
92 'https://creativecommons.org/licenses/by/4.0/'88 'https://creativecommons.org/licenses/by/4.0/'
93 ], [89 ], [
94 'Ansible',90 'Ansible',
95 '2012-2018 Michael DeHaan<br>&copy; 2018 Red Hat, Inc.',91 '2012-2018 Michael DeHaan<br>&copy; 2018–2019 Red Hat, Inc.',
96 'GPLv3',92 'GPLv3',
97 'https://raw.githubusercontent.com/ansible/ansible/devel/COPYING'93 'https://raw.githubusercontent.com/ansible/ansible/devel/COPYING'
98 ], [94 ], [
@@ -102,7 +98,7 @@ credits = [ · 3 unmodified lines
102 'https://www.apache.org/licenses/LICENSE-2.0'98 'https://www.apache.org/licenses/LICENSE-2.0'
103 ], [99 ], [
104 'Async',100 'Async',
105 '2010-2017 Caolan McMahon',101 '2010-2018 Caolan McMahon',
106 'MIT',102 'MIT',
107 'https://raw.githubusercontent.com/caolan/async/master/LICENSE'103 'https://raw.githubusercontent.com/caolan/async/master/LICENSE'
108 ], [104 ], [
@@ -115,6 +111,11 @@ credits = [ · 6 unmodified lines
115 '2010-2016 Jeremy Ashkenas, DocumentCloud',111 '2010-2016 Jeremy Ashkenas, DocumentCloud',
116 'MIT',112 'MIT',
117 'https://raw.githubusercontent.com/jashkenas/backbone/master/LICENSE'113 'https://raw.githubusercontent.com/jashkenas/backbone/master/LICENSE'
114 ], [
115 'Bash',
116 '2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.',
117 'GFDL',
118 'https://www.gnu.org/licenses/fdl-1.3.en.html'
118 ], [119 ], [
119 'Bluebird',120 'Bluebird',
120 '2013-2017 Petka Antonov',121 '2013-2017 Petka Antonov',
@@ -122,7 +123,7 @@ credits = [ · 1 unmodified line
122 'https://raw.githubusercontent.com/petkaantonov/bluebird/master/LICENSE'123 'https://raw.githubusercontent.com/petkaantonov/bluebird/master/LICENSE'
123 ], [124 ], [
124 'Bootstrap',125 'Bootstrap',
125 '2011-2018 Twitter, Inc.<br>2011-2018 The Bootstrap Authors',126 '2011-2019 Twitter, Inc.<br>2011-2019 The Bootstrap Authors',
126 'CC BY',127 'CC BY',
127 'https://creativecommons.org/licenses/by/3.0/'128 'https://creativecommons.org/licenses/by/3.0/'
128 ], [129 ], [
@@ -162,7 +163,7 @@ credits = [ · 33 unmodified lines
162 'https://github.com/clojure/clojure/blob/master/epl-v10.html'163 'https://github.com/clojure/clojure/blob/master/epl-v10.html'
163 ], [164 ], [
164 'CMake',165 'CMake',
165 '2000-2017 Kitware, Inc. and Contributors',166 '2000-2019 Kitware, Inc. and Contributors',
166 'BSD',167 'BSD',
167 'https://cmake.org/licensing/'168 'https://cmake.org/licensing/'
168 ], [169 ], [
@@ -185,9 +186,14 @@ credits = [ · 16 unmodified lines
185 '2009-2018 Jeremy Ashkenas',186 '2009-2018 Jeremy Ashkenas',
186 'MIT',187 'MIT',
187 'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE'188 'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE'
189 ], [
190 'Composer',
191 'Nils Adermann, Jordi Boggiano',
192 'MIT',
193 'https://raw.githubusercontent.com/composer/composer/master/LICENSE'
188 ], [194 ], [
189 'Cordova',195 'Cordova',
190 '2012-2017 The Apache Software Foundation',196 '2012-2018 The Apache Software Foundation',
191 'Apache',197 'Apache',
192 'https://raw.githubusercontent.com/apache/cordova-docs/master/LICENSE'198 'https://raw.githubusercontent.com/apache/cordova-docs/master/LICENSE'
193 ], [199 ], [
@@ -201,6 +207,12 @@ credits = [ · 7 unmodified lines
201 'Apache',207 'Apache',
202 'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE'208 'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE'
203 ], [209 ], [
210 'Cypress',
211 '2017 Cypress.io',
212 'MIT',
213 'https://raw.githubusercontent.com/cypress-io/cypress-documentation/develop/LICENSE.md'
214 ],
215 [
204 'D',216 'D',
205 '1999-2018 The D Language Foundation',217 '1999-2018 The D Language Foundation',
206 'Boost',218 'Boost',
@@ -210,6 +222,11 @@ credits = [ · 3 unmodified lines
210 '2010-2018 Michael Bostock',222 '2010-2018 Michael Bostock',
211 'BSD',223 'BSD',
212 'https://raw.githubusercontent.com/d3/d3/master/LICENSE'224 'https://raw.githubusercontent.com/d3/d3/master/LICENSE'
225 ], [
226 'Dart',
227 '2012 the Dart project authors',
228 'CC BY-SA',
229 'https://creativecommons.org/licenses/by-sa/4.0/'
213 ], [230 ], [
214 'Django',231 'Django',
215 'Django Software Foundation and individual contributors',232 'Django Software Foundation and individual contributors',
@@ -217,7 +234,7 @@ credits = [ · 1 unmodified line
217 'https://raw.githubusercontent.com/django/django/master/LICENSE'234 'https://raw.githubusercontent.com/django/django/master/LICENSE'
218 ], [235 ], [
219 'Docker',236 'Docker',
220 '2017 Docker, Inc.<br>Docker and the Docker logo are trademarks of Docker, Inc.',237 '2019 Docker, Inc.<br>Docker and the Docker logo are trademarks of Docker, Inc.',
221 'Apache',238 'Apache',
222 'https://raw.githubusercontent.com/docker/docker.github.io/master/LICENSE'239 'https://raw.githubusercontent.com/docker/docker.github.io/master/LICENSE'
223 ], [240 ], [
@@ -290,11 +307,21 @@ credits = [ · 66 unmodified lines
290 '2014-2018 Juan Linietsky, Ariel Manzur, Godot Engine contributors',307 '2014-2018 Juan Linietsky, Ariel Manzur, Godot Engine contributors',
291 'MIT',308 'MIT',
292 'https://raw.githubusercontent.com/godotengine/godot/master/LICENSE.txt'309 'https://raw.githubusercontent.com/godotengine/godot/master/LICENSE.txt'
310 ], [
311 'Graphite',
312 '2008-2012 Chris Davis<br>&copy; 2011-2016 The Graphite Project',
313 'Apache',
314 'https://raw.githubusercontent.com/graphite-project/graphite-web/master/LICENSE'
293 ], [315 ], [
294 'Grunt',316 'Grunt',
295 'GruntJS Team',317 'GruntJS Team',
296 'MIT',318 'MIT',
297 'https://github.com/gruntjs/grunt-docs/blob/master/package.json#L10'319 'https://github.com/gruntjs/grunt-docs/blob/master/package.json#L10'
320 ], [
321 'Handlebars',
322 '2011-2017 Yehuda Katz',
323 'MIT',
324 'https://raw.githubusercontent.com/wycats/handlebars.js/master/LICENSE'
298 ], [325 ], [
299 'Haskell',326 'Haskell',
300 'The University of Glasgow',327 'The University of Glasgow',
@@ -352,7 +379,7 @@ credits = [ · 51 unmodified lines
352 'https://raw.githubusercontent.com/jquery/api.jqueryui.com/master/LICENSE.txt'379 'https://raw.githubusercontent.com/jquery/api.jqueryui.com/master/LICENSE.txt'
353 ], [380 ], [
354 'Julia',381 'Julia',
355 '2009-2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors',382 '2009-2018 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors',
356 'MIT',383 'MIT',
357 'https://raw.githubusercontent.com/JuliaLang/julia/master/LICENSE.md'384 'https://raw.githubusercontent.com/JuliaLang/julia/master/LICENSE.md'
358 ], [385 ], [
@@ -372,7 +399,7 @@ credits = [ · 13 unmodified lines
372 'https://raw.githubusercontent.com/koajs/koa/master/LICENSE'399 'https://raw.githubusercontent.com/koajs/koa/master/LICENSE'
373 ], [400 ], [
374 'Kotlin',401 'Kotlin',
375 '2010-2018 JetBrains s.r.o.',402 '2010-2019 JetBrains s.r.o.',
376 'Apache',403 'Apache',
377 'https://raw.githubusercontent.com/JetBrains/kotlin/master/license/LICENSE.txt'404 'https://raw.githubusercontent.com/JetBrains/kotlin/master/license/LICENSE.txt'
378 ], [405 ], [
@@ -382,7 +409,7 @@ credits = [ · 3 unmodified lines
382 'https://raw.githubusercontent.com/laravel/framework/master/LICENSE.txt'409 'https://raw.githubusercontent.com/laravel/framework/master/LICENSE.txt'
383 ], [410 ], [
384 'Leaflet',411 'Leaflet',
385 '2010-2018 Vladimir Agafonkin<br>&copy; 2010-2011, CloudMade<br>Maps &copy; OpenStreetMap contributors.',412 '2010-2019 Vladimir Agafonkin<br>&copy; 2010-2011, CloudMade<br>Maps &copy; OpenStreetMap contributors.',
386 'BSD',413 'BSD',
387 'https://raw.githubusercontent.com/Leaflet/Leaflet/master/LICENSE'414 'https://raw.githubusercontent.com/Leaflet/Leaflet/master/LICENSE'
388 ], [415 ], [
@@ -422,7 +449,7 @@ credits = [ · 33 unmodified lines
422 'https://daringfireball.net/projects/markdown/license'449 'https://daringfireball.net/projects/markdown/license'
423 ], [450 ], [
424 'Matplotlib',451 'Matplotlib',
425 '2012-2017 Matplotlib Development Team. All rights reserved.',452 '2012-2018 Matplotlib Development Team. All rights reserved.',
426 'Custom',453 'Custom',
427 'https://raw.githubusercontent.com/matplotlib/matplotlib/master/LICENSE/LICENSE'454 'https://raw.githubusercontent.com/matplotlib/matplotlib/master/LICENSE/LICENSE'
428 ], [455 ], [
@@ -457,7 +484,7 @@ credits = [ · 28 unmodified lines
457 'https://github.com/LearnBoost/mongoose/blob/master/README.md#license'484 'https://github.com/LearnBoost/mongoose/blob/master/README.md#license'
458 ], [485 ], [
459 'nginx',486 'nginx',
460 '2002-2018 Igor Sysoev<br>&copy; 2011-2018 Nginx, Inc.',487 '2002-2019 Igor Sysoev<br>&copy; 2011-2019 Nginx, Inc.',
461 'BSD',488 'BSD',
462 'http://nginx.org/LICENSE'489 'http://nginx.org/LICENSE'
463 ], [490 ], [
@@ -502,7 +529,7 @@ credits = [ · 38 unmodified lines
502 'https://raw.githubusercontent.com/OpenTSDB/opentsdb.net/gh-pages/COPYING.LESSER'529 'https://raw.githubusercontent.com/OpenTSDB/opentsdb.net/gh-pages/COPYING.LESSER'
503 ], [530 ], [
504 'Padrino',531 'Padrino',
505 '2010-2016 Padrino',532 '2010-2019 Padrino',
506 'MIT',533 'MIT',
507 'https://raw.githubusercontent.com/padrino/padrino-framework/master/padrino/LICENSE.txt'534 'https://raw.githubusercontent.com/padrino/padrino-framework/master/padrino/LICENSE.txt'
508 ], [535 ], [
@@ -545,6 +572,16 @@ credits = [ · 36 unmodified lines
545 '1996-2018 The PostgreSQL Global Development Group<br>&copy; 1994 The Regents of the University of California',572 '1996-2018 The PostgreSQL Global Development Group<br>&copy; 1994 The Regents of the University of California',
546 'PostgreSQL',573 'PostgreSQL',
547 'https://www.postgresql.org/about/licence/'574 'https://www.postgresql.org/about/licence/'
575 ], [
576 'Puppeteer',
577 '2017 Google Inc',
578 'Apache',
579 'https://raw.githubusercontent.com/GoogleChrome/puppeteer/master/LICENSE'
580 ], [
581 'Pygame',
582 'Pygame Developers',
583 'LGPLv2.1',
584 'https://raw.githubusercontent.com/pygame/pygame/master/LICENSE'
548 ], [585 ], [
549 'Python',586 'Python',
550 '2001-2018 Python Software Foundation<br>Python is a trademark of the Python Software Foundation.',587 '2001-2018 Python Software Foundation<br>Python is a trademark of the Python Software Foundation.',
@@ -555,6 +592,11 @@ credits = [ · 4 unmodified lines
555 '2009-2017 Kristopher Michael Kowal',592 '2009-2017 Kristopher Michael Kowal',
556 'MIT',593 'MIT',
557 'https://raw.githubusercontent.com/kriskowal/q/master/LICENSE'594 'https://raw.githubusercontent.com/kriskowal/q/master/LICENSE'
595 ], [
596 'Qt',
597 '2012-2018 The Qt Company Ltd',
598 'GFDL',
599 'https://doc.qt.io/qt-5/licensing.html'
558 ], [600 ], [
559 'Ramda',601 'Ramda',
560 '2013-2016 Scott Sauyet and Michael Hurley',602 '2013-2016 Scott Sauyet and Michael Hurley',
@@ -601,18 +643,30 @@ credits = [ · 40 unmodified lines
601 'MIT',643 'MIT',
602 'https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT'644 'https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT'
603 ], [645 ], [
646 'Salt Stack',
647 '2019 SaltStack',
648 'Apache',
649 'https://raw.githubusercontent.com/saltstack/salt/develop/LICENSE'
650 ],
651 [
604 'Sass',652 'Sass',
605 '2006-2016 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein',653 '2006-2016 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein',
606 'MIT',654 'MIT',
607 'https://raw.githubusercontent.com/sass/sass/stable/MIT-LICENSE'655 'https://raw.githubusercontent.com/sass/sass/stable/MIT-LICENSE'
608 ], [656 ], [
657 'Scala',
658 '2002-2019 EPFL, with contributions from Lightbend',
659 'Apache',
660 'https://raw.githubusercontent.com/scala/scala-lang/master/license.md'
661 ],
662 [
609 'scikit-image',663 'scikit-image',
610 '2011 the scikit-image team',664 '2011 the scikit-image team',
611 'BSD',665 'BSD',
612 'http://scikit-image.org/docs/dev/license.html'666 'http://scikit-image.org/docs/dev/license.html'
613 ], [667 ], [
614 'scikit-learn',668 'scikit-learn',
615 '2007-2017 The scikit-learn developers',669 '2007-2018 The scikit-learn developers',
616 'BSD',670 'BSD',
617 'https://raw.githubusercontent.com/scikit-learn/scikit-learn/master/COPYING'671 'https://raw.githubusercontent.com/scikit-learn/scikit-learn/master/COPYING'
618 ], [672 ], [
@@ -657,9 +711,9 @@ credits = [ · 38 unmodified lines
657 'https://raw.githubusercontent.com/hashicorp/terraform-website/master/LICENSE.md'711 'https://raw.githubusercontent.com/hashicorp/terraform-website/master/LICENSE.md'
658 ], [712 ], [
659 'Twig',713 'Twig',
660 '2009-2017 The Twig Team',714 '2009-2018 The Twig Team',
661 'BSD',715 'BSD',
662 'https://twig.sensiolabs.org/license'716 'https://twig.symfony.com/license'
663 ], [717 ], [
664 'TypeScript',718 'TypeScript',
665 'Microsoft and other contributors',719 'Microsoft and other contributors',
@@ -667,7 +721,7 @@ credits = [ · 1 unmodified line
667 'https://raw.githubusercontent.com/Microsoft/TypeScript-Handbook/master/LICENSE'721 'https://raw.githubusercontent.com/Microsoft/TypeScript-Handbook/master/LICENSE'
668 ], [722 ], [
669 'Underscore.js',723 'Underscore.js',
670 '2009-2017 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors',724 '2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors',
671 'MIT',725 'MIT',
672 'https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE'726 'https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE'
673 ], [727 ], [
@@ -685,6 +739,11 @@ credits = [ · 11 unmodified lines
685 '2015-2018 Evan You, Vue.js contributors',739 '2015-2018 Evan You, Vue.js contributors',
686 'MIT',740 'MIT',
687 'https://raw.githubusercontent.com/vuejs/vuex/master/LICENSE'741 'https://raw.githubusercontent.com/vuejs/vuex/master/LICENSE'
742 ], [
743 'Vue Router',
744 '2013-present Evan You',
745 'MIT',
746 'https://raw.githubusercontent.com/vuejs/vue-router/dev/LICENSE'
688 ], [747 ], [
689 'Vulkan',748 'Vulkan',
690 '2014-2017 Khronos Group Inc.<br>Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc.',749 '2014-2017 Khronos Group Inc.<br>Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc.',
@@ -696,6 +755,12 @@ credits = [ · 5 unmodified lines
696 'CC BY',755 'CC BY',
697 'https://creativecommons.org/licenses/by/4.0/'756 'https://creativecommons.org/licenses/by/4.0/'
698 ], [757 ], [
758 'Wordpress',
759 '2003-2019 WordPress Foundation',
760 'GPLv2+',
761 'https://wordpress.org/about/license/'
762 ],
763 [
699 'Yarn',764 'Yarn',
700 '2016-present Yarn Contributors',765 '2016-present Yarn Contributors',
701 'BSD',766 'BSD',
assets/javascripts/templates/pages/help_tmpl.coffee+3−0
@@ -131,6 +131,9 @@ app.templates.helpPage = -> · 130 unmodified lines
131 <dt class="_shortcuts-dt">131 <dt class="_shortcuts-dt">
132 <code class="_shortcut-code">alt + s</code>132 <code class="_shortcut-code">alt + s</code>
133 <dd class="_shortcuts-dd">Search on Stack Overflow133 <dd class="_shortcuts-dd">Search on Stack Overflow
134 <dt class="_shortcuts-dt">
135 <code class="_shortcut-code">alt + d</code>
136 <dd class="_shortcuts-dd">Search on DuckDuckGo
134 </dl>137 </dl>
135 <p class="_note _note-green">138 <p class="_note _note-green">
136 <strong>Tip:</strong> If the cursor is no longer in the search field, press <code class="_label">/</code> or139 <strong>Tip:</strong> If the cursor is no longer in the search field, press <code class="_label">/</code> or
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+3−14
@@ -8,13 +8,13 @@ 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.
15 <li>To be notified about new versions, don't forget to <a href="https://github.com/freeCodeCamp/devdocs/subscription">watch the repository</a> on GitHub.15 <li>To be notified about new versions, don't forget to <a href="https://github.com/freeCodeCamp/devdocs/subscription">watch the repository</a> on GitHub.
16 <li>The <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a> is the preferred channel for bug reports and16 <li>The <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a> is the preferred channel for bug reports and
17 feature requests. For everything else, use the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>.17 feature requests. For everything else, use <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a>.
18 <li>Contributions are welcome. See the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/CONTRIBUTING.md">guidelines</a>.18 <li>Contributions are welcome. See the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/CONTRIBUTING.md">guidelines</a>.
19 <li>DevDocs is licensed under the terms of the Mozilla Public License v2.0. For more information,19 <li>DevDocs is licensed under the terms of the Mozilla Public License v2.0. For more information,
20 see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a> and20 see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a> and
@@ -39,7 +39,7 @@ app.templates.intro = """ · 18 unmodified lines
39 <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!
@@ -72,14 +72,3 @@ app.templates.androidWarning = """ · 26 unmodified lines
72 <p>To install DevDocs on your phone, visit <a href="https://devdocs.io" target="_blank" rel="noopener">devdocs.io</a> in Chrome and select "Add to home screen" in the menu.72 <p>To install DevDocs on your phone, visit <a href="https://devdocs.io" target="_blank" rel="noopener">devdocs.io</a> in Chrome and select "Add to home screen" in the menu.
73 </div>73 </div>
74"""74"""
75
76app.templates.httpWarning = """
77 <div class="_intro"><div class="_intro-message">
78 <h2 class="_intro-title">Hi there!</h2>
79 <p>DevDocs is migrating to HTTPS.
80 <p>Please update your bookmarks to point to <a href="https://devdocs.io">https://devdocs.io</a>.
81 <p>When using the HTTPS version, your preferences will carry over automatically, but your offline data will be reset. Simply re-download documentation in the <a href="https://devdocs.io/offline">Offline area</a>, and you'll be all set to use DevDocs securely offline.
82 <p>Sorry for the inconvenience. This migration is needed because browsers are removing support for certain DOM APIs that power DevDocs's offline mode over non-secure origins.
83 <p>Thanks for using DevDocs, and happy coding!
84 </div></div>
85"""
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/vendor/fastclick.js+2−2
@@ -413,7 +413,7 @@ · 412 unmodified lines
413 // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched413 // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
414 // with the same identifier as the touch event that previously triggered the click that triggered the alert.414 // with the same identifier as the touch event that previously triggered the click that triggered the alert.
415 // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an415 // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
416 // immediately preceeding touch event (issue #52), so this fix is unavailable on that platform.416 // immediately preceding touch event (issue #52), so this fix is unavailable on that platform.
417 // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,417 // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,
418 // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,418 // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,
419 // random integers, it's safe to to continue if the identifier is 0 here.419 // random integers, it's safe to to continue if the identifier is 0 here.
@@ -805,7 +805,7 @@ · 385 unmodified lines
805 }805 }
806 }806 }
807 807
808 // IE11: prefixed -ms-touch-action is no longer supported and it's recomended to use non-prefixed version808 // IE11: prefixed -ms-touch-action is no longer supported and it's recommended to use non-prefixed version
809 // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx809 // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx
810 if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {810 if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {
811 return true;811 return true;
assets/javascripts/vendor/prism.js+633−352
@@ -1,4 +1,5 @@
1/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+c+bash+cpp+coffeescript+ruby+d+django+elixir+erlang+go+java+json+kotlin+lua+nginx+nim+perl+php+python+jsx+crystal+rust+scss+sql+typescript+yaml */1/* PrismJS 1.15.0
2https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+c+bash+cpp+coffeescript+ruby+d+dart+django+elixir+markup-templating+erlang+go+java+json+kotlin+lua+crystal+nginx+nim+perl+php+sql+scss+python+jsx+typescript+rust+yaml */
2var _self = (typeof window !== 'undefined')3var _self = (typeof window !== 'undefined')
3 ? window // if in browser4 ? window // if in browser
4 : (5 : (
@@ -16,11 +17,12 @@ var _self = (typeof window !== 'undefined') · 11 unmodified lines
16var Prism = (function(){17var Prism = (function(){
17 18
18// Private helper vars19// Private helper vars
19var lang = /\blang(?:uage)?-(\w+)\b/i;20var lang = /\blang(?:uage)?-([\w-]+)\b/i;
20var uniqueId = 0;21var uniqueId = 0;
21 22
22var _ = _self.Prism = {23var _ = _self.Prism = {
23 manual: _self.Prism && _self.Prism.manual,24 manual: _self.Prism && _self.Prism.manual,
25 disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
24 util: {26 util: {
25 encode: function (tokens) {27 encode: function (tokens) {
26 if (tokens instanceof Token) {28 if (tokens instanceof Token) {
@@ -44,23 +46,38 @@ var _ = _self.Prism = { · 17 unmodified lines
44 },46 },
45 47
46 // Deep clone a language definition (e.g. to extend it)48 // Deep clone a language definition (e.g. to extend it)
47 clone: function (o) {49 clone: function (o, visited) {
48 var type = _.util.type(o);50 var type = _.util.type(o);
51 visited = visited || {};
49 52
50 switch (type) {53 switch (type) {
51 case 'Object':54 case 'Object':
55 if (visited[_.util.objId(o)]) {
56 return visited[_.util.objId(o)];
57 }
52 var clone = {};58 var clone = {};
59 visited[_.util.objId(o)] = clone;
53 60
54 for (var key in o) {61 for (var key in o) {
55 if (o.hasOwnProperty(key)) {62 if (o.hasOwnProperty(key)) {
56 clone[key] = _.util.clone(o[key]);63 clone[key] = _.util.clone(o[key], visited);
57 }64 }
58 }65 }
59 66
60 return clone;67 return clone;
61 68
62 case 'Array':69 case 'Array':
63 return o.map(function(v) { return _.util.clone(v); });70 if (visited[_.util.objId(o)]) {
71 return visited[_.util.objId(o)];
72 }
73 var clone = [];
74 visited[_.util.objId(o)] = clone;
75
76 o.forEach(function (v, i) {
77 clone[i] = _.util.clone(v, visited);
78 });
79
80 return clone;
64 }81 }
65 82
66 return o;83 return o;
@@ -155,6 +172,10 @@ var _ = _self.Prism = { · 88 unmodified lines
155 plugins: {},172 plugins: {},
156 173
157 highlightAll: function(async, callback) {174 highlightAll: function(async, callback) {
175 _.highlightAllUnder(document, async, callback);
176 },
177
178 highlightAllUnder: function(container, async, callback) {
158 var env = {179 var env = {
159 callback: callback,180 callback: callback,
160 selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'181 selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
@@ -162,7 +183,7 @@ var _ = _self.Prism = { · 1 unmodified line
162 183
163 _.hooks.run("before-highlightall", env);184 _.hooks.run("before-highlightall", env);
164 185
165 var elements = env.elements || document.querySelectorAll(env.selector);186 var elements = env.elements || container.querySelectorAll(env.selector);
166 187
167 for (var i=0, element; element = elements[i++];) {188 for (var i=0, element; element = elements[i++];) {
168 _.highlightElement(element, async === true, env.callback);189 _.highlightElement(element, async === true, env.callback);
@@ -185,11 +206,13 @@ var _ = _self.Prism = { · 16 unmodified lines
185 // Set language on the element, if not present206 // Set language on the element, if not present
186 element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;207 element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
187 208
188 // Set language on the parent, for styling209 if (element.parentNode) {
189 parent = element.parentNode;210 // Set language on the parent, for styling
211 parent = element.parentNode;
190 212
191 if (/pre/i.test(parent.nodeName)) {213 if (/pre/i.test(parent.nodeName)) {
192 parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;214 parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
215 }
193 }216 }
194 217
195 var code = element.textContent;218 var code = element.textContent;
@@ -251,8 +274,15 @@ var _ = _self.Prism = { · 55 unmodified lines
251 },274 },
252 275
253 highlight: function (text, grammar, language) {276 highlight: function (text, grammar, language) {
254 var tokens = _.tokenize(text, grammar);277 var env = {
255 return Token.stringify(_.util.encode(tokens), language);278 code: text,
279 grammar: grammar,
280 language: language
281 };
282 _.hooks.run('before-tokenize', env);
283 env.tokens = _.tokenize(env.code, env.grammar);
284 _.hooks.run('after-tokenize', env);
285 return Token.stringify(_.util.encode(env.tokens), env.language);
256 },286 },
257 287
258 matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {288 matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
@@ -300,15 +330,9 @@ var _ = _self.Prism = { · 41 unmodified lines
300 continue;330 continue;
301 }331 }
302 332
303 pattern.lastIndex = 0;333 if (greedy && i != strarr.length - 1) {
304
305 var match = pattern.exec(str),
306 delNum = 1;
307
308 // Greedy patterns can override/remove up to two previously matched tokens
309 if (!match && greedy && i != strarr.length - 1) {
310 pattern.lastIndex = pos;334 pattern.lastIndex = pos;
311 match = pattern.exec(text);335 var match = pattern.exec(text);
312 if (!match) {336 if (!match) {
313 break;337 break;
314 }338 }
@@ -327,11 +351,8 @@ var _ = _self.Prism = { · 12 unmodified lines
327 }351 }
328 }352 }
329 353
330 /*354 // If strarr[i] is a Token, then the match starts inside another Token, which is invalid
331 * If strarr[i] is a Token, then the match starts inside another Token, which is invalid355 if (strarr[i] instanceof Token) {
332 * If strarr[k - 1] is greedy we are in conflict with another greedy pattern
333 */
334 if (strarr[i] instanceof Token || strarr[k - 1].greedy) {
335 continue;356 continue;
336 }357 }
337 358
@@ -339,6 +360,11 @@ var _ = _self.Prism = { · 1 unmodified line
339 delNum = k - i;360 delNum = k - i;
340 str = text.slice(pos, p);361 str = text.slice(pos, p);
341 match.index -= pos;362 match.index -= pos;
363 } else {
364 pattern.lastIndex = 0;
365
366 var match = pattern.exec(str),
367 delNum = 1;
342 }368 }
343 369
344 if (!match) {370 if (!match) {
@@ -350,7 +376,7 @@ var _ = _self.Prism = { · 5 unmodified lines
350 }376 }
351 377
352 if(lookbehind) {378 if(lookbehind) {
353 lookbehindLength = match[1].length;379 lookbehindLength = match[1] ? match[1].length : 0;
354 }380 }
355 381
356 var from = match.index + lookbehindLength,382 var from = match.index + lookbehindLength,
@@ -480,18 +506,21 @@ Token.stringify = function(o, language, parent) { · 123 unmodified lines
480// // in Node.js506// // in Node.js
481// return _self.Prism;507// return _self.Prism;
482// }508// }
483// // In worker509
484// _self.addEventListener('message', function(evt) {510// if (!_.disableWorkerMessageHandler) {
485// var message = JSON.parse(evt.data),511// // In worker
486// lang = message.language,512// _self.addEventListener('message', function (evt) {
487// code = message.code,513// var message = JSON.parse(evt.data),
488// immediateClose = message.immediateClose;514// lang = message.language,
489 515// code = message.code,
490// _self.postMessage(_.highlight(code, _.languages[lang], lang));516// immediateClose = message.immediateClose;
491// if (immediateClose) {517
492// _self.close();518// _self.postMessage(_.highlight(code, _.languages[lang], lang));
493// }519// if (immediateClose) {
494// }, false);520// _self.close();
521// }
522// }, false);
523// }
495 524
496// return _self.Prism;525// return _self.Prism;
497// }526// }
@@ -535,7 +564,8 @@ Prism.languages.markup = { · 37 unmodified lines
535 'doctype': /<!DOCTYPE[\s\S]+?>/i,564 'doctype': /<!DOCTYPE[\s\S]+?>/i,
536 'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,565 'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,
537 'tag': {566 'tag': {
538 pattern: /<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,567 pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,
568 greedy: true,
539 inside: {569 inside: {
540 'tag': {570 'tag': {
541 pattern: /^<\/?[^\s>\/]+/i,571 pattern: /^<\/?[^\s>\/]+/i,
@@ -600,13 +630,13 @@ Prism.languages.css = { · 58 unmodified lines
600 pattern: /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,630 pattern: /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
601 greedy: true631 greedy: true
602 },632 },
603 'property': /[\w-]+(?=\s*:)/i,633 'property': /[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,
604 'important': /\B!important\b/i,634 'important': /\B!important\b/i,
605 'function': /[-a-z0-9]+(?=\()/i,635 'function': /[-a-z0-9]+(?=\()/i,
606 'punctuation': /[(){};:]/636 'punctuation': /[(){};:]/
607};637};
608 638
609Prism.languages.css['atrule'].inside.rest = Prism.util.clone(Prism.languages.css);639Prism.languages.css['atrule'].inside.rest = Prism.languages.css;
610 640
611if (Prism.languages.markup) {641if (Prism.languages.markup) {
612 Prism.languages.insertBefore('markup', 'tag', {642 Prism.languages.insertBefore('markup', 'tag', {
@@ -614,10 +644,11 @@ if (Prism.languages.markup) { · 1 unmodified line
614 pattern: /(<style[\s\S]*?>)[\s\S]*?(?=<\/style>)/i,644 pattern: /(<style[\s\S]*?>)[\s\S]*?(?=<\/style>)/i,
615 lookbehind: true,645 lookbehind: true,
616 inside: Prism.languages.css,646 inside: Prism.languages.css,
617 alias: 'language-css'647 alias: 'language-css',
648 greedy: true
618 }649 }
619 });650 });
620 651
621 Prism.languages.insertBefore('inside', 'attr-value', {652 Prism.languages.insertBefore('inside', 'attr-value', {
622 'style-attr': {653 'style-attr': {
623 pattern: /\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,654 pattern: /\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,
@@ -644,7 +675,8 @@ Prism.languages.clike = { · 20 unmodified lines
644 },675 },
645 {676 {
646 pattern: /(^|[^\\:])\/\/.*/,677 pattern: /(^|[^\\:])\/\/.*/,
647 lookbehind: true678 lookbehind: true,
679 greedy: true
648 }680 }
649 ],681 ],
650 'string': {682 'string': {
@@ -661,51 +693,53 @@ Prism.languages.clike = { · 10 unmodified lines
661 'keyword': /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,693 'keyword': /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
662 'boolean': /\b(?:true|false)\b/,694 'boolean': /\b(?:true|false)\b/,
663 'function': /[a-z0-9_]+(?=\()/i,695 'function': /[a-z0-9_]+(?=\()/i,
664 'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,696 'number': /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,
665 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,697 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,
666 'punctuation': /[{}[\];(),.:]/698 'punctuation': /[{}[\];(),.:]/
667};699};
668 700
669Prism.languages.javascript = Prism.languages.extend('clike', {701Prism.languages.javascript = Prism.languages.extend('clike', {
670 'keyword': /\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,702 'keyword': /\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,
671 'number': /\b-?(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|\d*\.?\d+(?:[Ee][+-]?\d+)?|NaN|Infinity)\b/,703 'number': /\b(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,
672 // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)704 // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
673 'function': /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\s*\()/i,705 'function': /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\()/i,
674 'operator': /-[-=]?|\+[+=]?|!=?=?|<<?=?|>>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/706 'operator': /-[-=]?|\+[+=]?|!=?=?|<<?=?|>>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/
675});707});
676 708
677Prism.languages.insertBefore('javascript', 'keyword', {709Prism.languages.insertBefore('javascript', 'keyword', {
678 'regex': {710 'regex': {
679 pattern: /(^|[^/])\/(?!\/)(\[[^\]\r\n]+]|\\.|[^/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,711 pattern: /((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[[^\]\r\n]+]|\\.|[^/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/,
680 lookbehind: true,712 lookbehind: true,
681 greedy: true713 greedy: true
682 },714 },
683 // This must be declared before keyword because we use "function" inside the look-forward715 // This must be declared before keyword because we use "function" inside the look-forward
684 'function-variable': {716 'function-variable': {
685 pattern: /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\s*=\s*(?:function\b|(?:\([^()]*\)|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)\s*=>))/i,717 pattern: /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=\s*(?:function\b|(?:\([^()]*\)|[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/i,
686 alias: 'function'718 alias: 'function'
687 }719 },
720 'constant': /\b[A-Z][A-Z\d_]*\b/
688});721});
689 722
690Prism.languages.insertBefore('javascript', 'string', {723Prism.languages.insertBefore('javascript', 'string', {
691 'template-string': {724 'template-string': {
692 pattern: /`(?:\\[\s\S]|[^\\`])*`/,725 pattern: /`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,
693 greedy: true,726 greedy: true,
694 inside: {727 inside: {
695 'interpolation': {728 'interpolation': {
696 pattern: /\$\{[^}]+\}/,729 pattern: /\${[^}]+}/,
697 inside: {730 inside: {
698 'interpolation-punctuation': {731 'interpolation-punctuation': {
699 pattern: /^\$\{|\}$/,732 pattern: /^\${|}$/,
700 alias: 'punctuation'733 alias: 'punctuation'
701 },734 },
702 rest: Prism.languages.javascript735 rest: null // See below
703 }736 }
704 },737 },
705 'string': /[\s\S]+/738 'string': /[\s\S]+/
706 }739 }
707 }740 }
708});741});
742Prism.languages.javascript['template-string'].inside['interpolation'].inside.rest = Prism.languages.javascript;
709 743
710if (Prism.languages.markup) {744if (Prism.languages.markup) {
711 Prism.languages.insertBefore('markup', 'tag', {745 Prism.languages.insertBefore('markup', 'tag', {
@@ -713,7 +747,8 @@ if (Prism.languages.markup) { · 1 unmodified line
713 pattern: /(<script[\s\S]*?>)[\s\S]*?(?=<\/script>)/i,747 pattern: /(<script[\s\S]*?>)[\s\S]*?(?=<\/script>)/i,
714 lookbehind: true,748 lookbehind: true,
715 inside: Prism.languages.javascript,749 inside: Prism.languages.javascript,
716 alias: 'language-javascript'750 alias: 'language-javascript',
751 greedy: true
717 }752 }
718 });753 });
719}754}
@@ -723,7 +758,7 @@ Prism.languages.js = Prism.languages.javascript; · 3 unmodified lines
723Prism.languages.c = Prism.languages.extend('clike', {758Prism.languages.c = Prism.languages.extend('clike', {
724 'keyword': /\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,759 'keyword': /\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,
725 'operator': /-[>-]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/]/,760 'operator': /-[>-]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/]/,
726 'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i761 'number': /(?:\b0x[\da-f]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?)[ful]*/i
727});762});
728 763
729Prism.languages.insertBefore('c', 'string', {764Prism.languages.insertBefore('c', 'string', {
@@ -768,7 +803,7 @@ delete Prism.languages.c['boolean']; · 38 unmodified lines
768 },803 },
769 /^\$\(\(/804 /^\$\(\(/
770 ],805 ],
771 number: /\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+(?:[Ee]-?\d+)?)\b/,806 number: /\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee]-?\d+)?/,
772 // Operators according to https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic807 // Operators according to https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic
773 operator: /--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,808 operator: /--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,
774 // If there is no $ sign at the beginning highlight (( and )) as punctuation809 // If there is no $ sign at the beginning highlight (( and )) as punctuation
@@ -778,6 +813,7 @@ delete Prism.languages.c['boolean']; · 3 unmodified lines
778 // Command Substitution813 // Command Substitution
779 {814 {
780 pattern: /\$\([^)]+\)|`[^`]+`/,815 pattern: /\$\([^)]+\)|`[^`]+`/,
816 greedy: true,
781 inside: {817 inside: {
782 variable: /^\$\(|^`|\)$|`$/818 variable: /^\$\(|^`|\)$|`$/
783 }819 }
@@ -804,7 +840,7 @@ delete Prism.languages.c['boolean']; · 20 unmodified lines
804 inside: insideString840 inside: insideString
805 },841 },
806 {842 {
807 pattern: /(["'])(?:\\[\s\S]|(?!\1)[^\\])*\1/,843 pattern: /(["'])(?:\\[\s\S]|\$\([^)]+\)|`[^`]+`|(?!\1)[^\\])*\1/,
808 greedy: true,844 greedy: true,
809 inside: insideString845 inside: insideString
810 }846 }
@@ -812,7 +848,7 @@ delete Prism.languages.c['boolean']; · 1 unmodified line
812 'variable': insideString.variable,848 'variable': insideString.variable,
813 // Originally based on http://ss64.com/bash/849 // Originally based on http://ss64.com/bash/
814 'function': {850 'function': {
815 pattern: /(^|[\s;|&])(?:alias|apropos|apt-get|aptitude|aspell|awk|basename|bash|bc|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chmod|chown|chroot|chkconfig|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|cut|date|dc|dd|ddrescue|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|grep|groupadd|groupdel|groupmod|groups|gzip|hash|head|help|hg|history|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|jobs|join|kill|killall|less|link|ln|locate|logname|logout|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|make|man|mkdir|mkfifo|mkisofs|mknod|more|most|mount|mtools|mtr|mv|mmv|nano|netstat|nice|nl|nohup|notify-send|npm|nslookup|open|op|passwd|paste|pathchk|ping|pkill|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|rename|renice|remsync|rev|rm|rmdir|rsync|screen|scp|sdiff|sed|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|sync|tail|tar|tee|test|time|timeout|times|touch|top|traceroute|trap|tr|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|uptime|useradd|userdel|usermod|users|uuencode|uudecode|v|vdir|vi|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yes|zip)(?=$|[\s;|&])/,851 pattern: /(^|[\s;|&])(?:alias|apropos|apt-get|aptitude|aspell|awk|basename|bash|bc|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chmod|chown|chroot|chkconfig|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|grep|groupadd|groupdel|groupmod|groups|gzip|hash|head|help|hg|history|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|jobs|join|kill|killall|less|link|ln|locate|logname|logout|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|make|man|mkdir|mkfifo|mkisofs|mknod|more|most|mount|mtools|mtr|mv|mmv|nano|netstat|nice|nl|nohup|notify-send|npm|nslookup|open|op|passwd|paste|pathchk|ping|pkill|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|rename|renice|remsync|rev|rm|rmdir|rsync|screen|scp|sdiff|sed|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|sync|tail|tar|tee|test|time|timeout|times|touch|top|traceroute|trap|tr|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|uptime|useradd|userdel|usermod|users|uuencode|uudecode|v|vdir|vi|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yes|zip)(?=$|[\s;|&])/,
816 lookbehind: true852 lookbehind: true
817 },853 },
818 'keyword': {854 'keyword': {
@@ -828,15 +864,18 @@ delete Prism.languages.c['boolean']; · 9 unmodified lines
828 };864 };
829 865
830 var inside = insideString.variable[1].inside;866 var inside = insideString.variable[1].inside;
867 inside.string = Prism.languages.bash.string;
831 inside['function'] = Prism.languages.bash['function'];868 inside['function'] = Prism.languages.bash['function'];
832 inside.keyword = Prism.languages.bash.keyword;869 inside.keyword = Prism.languages.bash.keyword;
833 inside.boolean = Prism.languages.bash.boolean;870 inside['boolean'] = Prism.languages.bash['boolean'];
834 inside.operator = Prism.languages.bash.operator;871 inside.operator = Prism.languages.bash.operator;
835 inside.punctuation = Prism.languages.bash.punctuation;872 inside.punctuation = Prism.languages.bash.punctuation;
873
874 Prism.languages.shell = Prism.languages.bash;
836})(Prism);875})(Prism);
837 876
838Prism.languages.cpp = Prism.languages.extend('c', {877Prism.languages.cpp = Prism.languages.extend('c', {
839 'keyword': /\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,878 'keyword': /\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,
840 'boolean': /\b(?:true|false)\b/,879 'boolean': /\b(?:true|false)\b/,
841 'operator': /--?|\+\+?|!=?|<{1,2}=?|>{1,2}=?|->|:{1,2}|={1,2}|\^|~|%|&{1,2}|\|\|?|\?|\*|\/|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/880 'operator': /--?|\+\+?|!=?|<{1,2}=?|>{1,2}=?|->|:{1,2}|={1,2}|\^|~|%|&{1,2}|\|\|?|\?|\*|\/|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/
842});881});
@@ -847,6 +886,15 @@ Prism.languages.insertBefore('cpp', 'keyword', { · 4 unmodified lines
847 lookbehind: true886 lookbehind: true
848 }887 }
849});888});
889
890Prism.languages.insertBefore('cpp', 'string', {
891 'raw-string': {
892 pattern: /R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,
893 alias: 'string',
894 greedy: true
895 }
896});
897
850(function(Prism) {898(function(Prism) {
851 899
852// Ignore comments starting with { to privilege string interpolation highlighting900// Ignore comments starting with { to privilege string interpolation highlighting
@@ -947,10 +995,13 @@ delete Prism.languages.coffeescript['template-string']; · 94 unmodified lines
947(function(Prism) {995(function(Prism) {
948 Prism.languages.ruby = Prism.languages.extend('clike', {996 Prism.languages.ruby = Prism.languages.extend('clike', {
949 'comment': [997 'comment': [
950 /#(?!\{[^\r\n]*?\}).*/,998 /#.*/,
951 /^=begin(?:\r?\n|\r)(?:.*(?:\r?\n|\r))*?=end/m999 {
1000 pattern: /^=begin(?:\r?\n|\r)(?:.*(?:\r?\n|\r))*?=end/m,
1001 greedy: true
1002 }
952 ],1003 ],
953 'keyword': /\b(?:alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|false|for|if|in|module|new|next|nil|not|or|raise|redo|require|rescue|retry|return|self|super|then|throw|true|undef|unless|until|when|while|yield)\b/1004 'keyword': /\b(?:alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|false|for|if|in|module|new|next|nil|not|or|protected|private|public|raise|redo|require|rescue|retry|return|self|super|then|throw|true|undef|unless|until|when|while|yield)\b/
954 });1005 });
955 1006
956 var interpolation = {1007 var interpolation = {
@@ -960,7 +1011,7 @@ delete Prism.languages.coffeescript['template-string']; · 3 unmodified lines
960 pattern: /^#\{|\}$/,1011 pattern: /^#\{|\}$/,
961 alias: 'tag'1012 alias: 'tag'
962 },1013 },
963 rest: Prism.util.clone(Prism.languages.ruby)1014 rest: Prism.languages.ruby
964 }1015 }
965 };1016 };
966 1017
@@ -1009,7 +1060,10 @@ delete Prism.languages.coffeescript['template-string']; · 42 unmodified lines
1009 }1060 }
1010 ],1061 ],
1011 'variable': /[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,1062 'variable': /[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,
1012 'symbol': /:[a-zA-Z_]\w*(?:[?!]|\b)/1063 'symbol': {
1064 pattern: /(^|[^:]):[a-zA-Z_]\w*(?:[?!]|\b)/,
1065 lookbehind: true
1066 }
1013 });1067 });
1014 1068
1015 Prism.languages.insertBefore('ruby', 'number', {1069 Prism.languages.insertBefore('ruby', 'number', {
@@ -1127,6 +1181,30 @@ Prism.languages.insertBefore('d', 'function', { · 111 unmodified lines
1127 alias: 'variable'1181 alias: 'variable'
1128 }1182 }
1129});1183});
1184Prism.languages.dart = Prism.languages.extend('clike', {
1185 'string': [
1186 {
1187 pattern: /r?("""|''')[\s\S]*?\1/,
1188 greedy: true
1189 },
1190 {
1191 pattern: /r?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,
1192 greedy: true
1193 }
1194 ],
1195 'keyword': [
1196 /\b(?:async|sync|yield)\*/,
1197 /\b(?:abstract|assert|async|await|break|case|catch|class|const|continue|default|deferred|do|dynamic|else|enum|export|external|extends|factory|final|finally|for|get|if|implements|import|in|library|new|null|operator|part|rethrow|return|set|static|super|switch|this|throw|try|typedef|var|void|while|with|yield)\b/
1198 ],
1199 'operator': /\bis!|\b(?:as|is)\b|\+\+|--|&&|\|\||<<=?|>>=?|~(?:\/=?)?|[+\-*\/%&^|=!<>]=?|\?/
1200});
1201
1202Prism.languages.insertBefore('dart','function',{
1203 'metadata': {
1204 pattern: /@\w+/,
1205 alias: 'symbol'
1206 }
1207});
1130// Django/Jinja2 syntax definition for Prism.js <http://prismjs.com> syntax highlighter.1208// Django/Jinja2 syntax definition for Prism.js <http://prismjs.com> syntax highlighter.
1131// Mostly it works OK but can paint code incorrectly on complex html/template tag combinations.1209// Mostly it works OK but can paint code incorrectly on complex html/template tag combinations.
1132 1210
@@ -1170,15 +1248,15 @@ if (Prism.languages.css) { · 37 unmodified lines
1170Prism.languages.jinja2 = Prism.languages.django;1248Prism.languages.jinja2 = Prism.languages.django;
1171 1249
1172Prism.languages.elixir = {1250Prism.languages.elixir = {
1173 // Negative look-ahead is needed for string interpolation
1174 // Negative look-behind is needed to avoid highlighting markdown headers in
1175 // multi-line doc strings
1176 'comment': {1251 'comment': {
1177 pattern: /(^|[^#])#(?![{#]).*/m,1252 pattern: /#.*/m,
1178 lookbehind: true1253 lookbehind: true
1179 },1254 },
1180 // ~r"""foo""" (multi-line), ~r'''foo''' (multi-line), ~r/foo/, ~r|foo|, ~r"foo", ~r'foo', ~r(foo), ~r[foo], ~r{foo}, ~r<foo>1255 // ~r"""foo""" (multi-line), ~r'''foo''' (multi-line), ~r/foo/, ~r|foo|, ~r"foo", ~r'foo', ~r(foo), ~r[foo], ~r{foo}, ~r<foo>
1181 'regex': /~[rR](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|[^\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[uismxfr]*/,1256 'regex': {
1257 pattern: /~[rR](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|[^\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[uismxfr]*/,
1258 greedy: true
1259 },
1182 'string': [1260 'string': [
1183 {1261 {
1184 // ~s"""foo""" (multi-line), ~s'''foo''' (multi-line), ~s/foo/, ~s|foo|, ~s"foo", ~s'foo', ~s(foo), ~s[foo], ~s{foo} (with interpolation care), ~s<foo>1262 // ~s"""foo""" (multi-line), ~s'''foo''' (multi-line), ~s/foo/, ~s|foo|, ~s"foo", ~s'foo', ~s(foo), ~s[foo], ~s{foo} (with interpolation care), ~s<foo>
@@ -1225,7 +1303,7 @@ Prism.languages.elixir = { · 40 unmodified lines
1225 alias: 'variable'1303 alias: 'variable'
1226 },1304 },
1227 'attribute': {1305 'attribute': {
1228 pattern: /@[\S]+/,1306 pattern: /@\w+/,
1229 alias: 'variable'1307 alias: 'variable'
1230 },1308 },
1231 'number': /\b(?:0[box][a-f\d_]+|\d[\d_]*)(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?\b/i,1309 'number': /\b(?:0[box][a-f\d_]+|\d[\d_]*)(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?\b/i,
@@ -1256,13 +1334,102 @@ Prism.languages.elixir.string.forEach(function(o) { · 24 unmodified lines
1256 pattern: /^#\{|\}$/,1334 pattern: /^#\{|\}$/,
1257 alias: 'punctuation'1335 alias: 'punctuation'
1258 },1336 },
1259 rest: Prism.util.clone(Prism.languages.elixir)1337 rest: Prism.languages.elixir
1260 }1338 }
1261 }1339 }
1262 };1340 };
1263});1341});
1264 1342
1265 1343
1344Prism.languages['markup-templating'] = {};
1345
1346Object.defineProperties(Prism.languages['markup-templating'], {
1347 buildPlaceholders: {
1348 // Tokenize all inline templating expressions matching placeholderPattern
1349 // If the replaceFilter function is provided, it will be called with every match.
1350 // If it returns false, the match will not be replaced.
1351 value: function (env, language, placeholderPattern, replaceFilter) {
1352 if (env.language !== language) {
1353 return;
1354 }
1355
1356 env.tokenStack = [];
1357
1358 env.code = env.code.replace(placeholderPattern, function(match) {
1359 if (typeof replaceFilter === 'function' && !replaceFilter(match)) {
1360 return match;
1361 }
1362 var i = env.tokenStack.length;
1363 // Check for existing strings
1364 while (env.code.indexOf('___' + language.toUpperCase() + i + '___') !== -1)
1365 ++i;
1366
1367 // Create a sparse array
1368 env.tokenStack[i] = match;
1369
1370 return '___' + language.toUpperCase() + i + '___';
1371 });
1372
1373 // Switch the grammar to markup
1374 env.grammar = Prism.languages.markup;
1375 }
1376 },
1377 tokenizePlaceholders: {
1378 // Replace placeholders with proper tokens after tokenizing
1379 value: function (env, language) {
1380 if (env.language !== language || !env.tokenStack) {
1381 return;
1382 }
1383
1384 // Switch the grammar back
1385 env.grammar = Prism.languages[language];
1386
1387 var j = 0;
1388 var keys = Object.keys(env.tokenStack);
1389 var walkTokens = function (tokens) {
1390 if (j >= keys.length) {
1391 return;
1392 }
1393 for (var i = 0; i < tokens.length; i++) {
1394 var token = tokens[i];
1395 if (typeof token === 'string' || (token.content && typeof token.content === 'string')) {
1396 var k = keys[j];
1397 var t = env.tokenStack[k];
1398 var s = typeof token === 'string' ? token : token.content;
1399
1400 var index = s.indexOf('___' + language.toUpperCase() + k + '___');
1401 if (index > -1) {
1402 ++j;
1403 var before = s.substring(0, index);
1404 var middle = new Prism.Token(language, Prism.tokenize(t, env.grammar, language), 'language-' + language, t);
1405 var after = s.substring(index + ('___' + language.toUpperCase() + k + '___').length);
1406 var replacement;
1407 if (before || after) {
1408 replacement = [before, middle, after].filter(function (v) { return !!v; });
1409 walkTokens(replacement);
1410 } else {
1411 replacement = middle;
1412 }
1413 if (typeof token === 'string') {
1414 Array.prototype.splice.apply(tokens, [i, 1].concat(replacement));
1415 } else {
1416 token.content = replacement;
1417 }
1418
1419 if (j >= keys.length) {
1420 break;
1421 }
1422 }
1423 } else if (token.content && typeof token.content !== 'string') {
1424 walkTokens(token.content);
1425 }
1426 }
1427 };
1428
1429 walkTokens(env.tokens);
1430 }
1431 }
1432});
1266Prism.languages.erlang = {1433Prism.languages.erlang = {
1267 'comment': /%.+/,1434 'comment': /%.+/,
1268 'string': {1435 'string': {
@@ -1282,7 +1449,7 @@ Prism.languages.erlang = { · 13 unmodified lines
1282 'number': [1449 'number': [
1283 /\$\\?./,1450 /\$\\?./,
1284 /\d+#[a-z0-9]+/i,1451 /\d+#[a-z0-9]+/i,
1285 /(?:\b|-)\d*\.?\d+([Ee][+-]?\d+)?\b/1452 /(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i
1286 ],1453 ],
1287 'function': /\b[a-z][\w@]*(?=\()/,1454 'function': /\b[a-z][\w@]*(?=\()/,
1288 'variable': {1455 'variable': {
@@ -1312,7 +1479,7 @@ Prism.languages.go = Prism.languages.extend('clike', { · 23 unmodified lines
1312 'builtin': /\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/,1479 'builtin': /\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/,
1313 'boolean': /\b(?:_|iota|nil|true|false)\b/,1480 'boolean': /\b(?:_|iota|nil|true|false)\b/,
1314 'operator': /[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,1481 'operator': /[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,
1315 'number': /\b(-?(0x[a-f\d]+|(\d+\.?\d*|\.\d+)(e[-+]?\d+)?)i?)\b/i,1482 'number': /(?:\b0x[a-f\d]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e[-+]?\d+)?)i?/i,
1316 'string': {1483 'string': {
1317 pattern: /(["'`])(\\[\s\S]|(?!\1)[^\\])*\1/,1484 pattern: /(["'`])(\\[\s\S]|(?!\1)[^\\])*\1/,
1318 greedy: true1485 greedy: true
@@ -1322,7 +1489,7 @@ delete Prism.languages.go['class-name']; · 3 unmodified lines
1322 1489
1323Prism.languages.java = Prism.languages.extend('clike', {1490Prism.languages.java = Prism.languages.extend('clike', {
1324 'keyword': /\b(?:abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,1491 'keyword': /\b(?:abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,
1325 'number': /\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,1492 'number': /\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp-]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?[df]?/i,
1326 'operator': {1493 'operator': {
1327 pattern: /(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<<?=?|>>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,1494 pattern: /(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<<?=?|>>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,
1328 lookbehind: true1495 lookbehind: true
@@ -1337,14 +1504,25 @@ Prism.languages.insertBefore('java','function', { · 8 unmodified lines
1337 }1504 }
1338});1505});
1339 1506
1507Prism.languages.insertBefore('java', 'class-name', {
1508 'generics': {
1509 pattern: /<\s*\w+(?:\.\w+)?(?:\s*,\s*\w+(?:\.\w+)?)*>/i,
1510 alias: 'function',
1511 inside: {
1512 keyword: Prism.languages.java.keyword,
1513 punctuation: /[<>(),.:]/
1514 }
1515 }
1516});
1517
1340Prism.languages.json = {1518Prism.languages.json = {
1341 'property': /"(?:\\.|[^\\"\r\n])*"(?=\s*:)/i,1519 'property': /"(?:\\.|[^\\"\r\n])*"(?=\s*:)/i,
1342 'string': {1520 'string': {
1343 pattern: /"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,1521 pattern: /"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,
1344 greedy: true1522 greedy: true
1345 },1523 },
1346 'number': /\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+(?:[Ee][+-]?\d+)?)\b/,1524 'number': /-?\d+\.?\d*([Ee][+-]?\d+)?/,
1347 'punctuation': /[{}[\]);,]/,1525 'punctuation': /[{}[\],]/,
1348 'operator': /:/g,1526 'operator': /:/g,
1349 'boolean': /\b(?:true|false)\b/i,1527 'boolean': /\b(?:true|false)\b/i,
1350 'null': /\bnull\b/i1528 'null': /\bnull\b/i
@@ -1356,7 +1534,7 @@ Prism.languages.jsonp = Prism.languages.json; · 5 unmodified lines
1356 Prism.languages.kotlin = Prism.languages.extend('clike', {1534 Prism.languages.kotlin = Prism.languages.extend('clike', {
1357 'keyword': {1535 'keyword': {
1358 // The lookbehind prevents wrong highlighting of e.g. kotlin.properties.get1536 // The lookbehind prevents wrong highlighting of e.g. kotlin.properties.get
1359 pattern: /(^|[^.])\b(?:abstract|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|else|enum|final|finally|for|fun|get|if|import|in|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|out|override|package|private|protected|public|reified|return|sealed|set|super|tailrec|this|throw|to|try|val|var|when|where|while)\b/,1537 pattern: /(^|[^.])\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\b/,
1360 lookbehind: true1538 lookbehind: true
1361 },1539 },
1362 'function': [1540 'function': [
@@ -1366,7 +1544,7 @@ Prism.languages.jsonp = Prism.languages.json; · 3 unmodified lines
1366 lookbehind: true1544 lookbehind: true
1367 }1545 }
1368 ],1546 ],
1369 'number': /\b(?:0[bx][\da-fA-F]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?[fFL]?)\b/,1547 'number': /\b(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?[fFL]?)\b/,
1370 'operator': /\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/1548 'operator': /\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/
1371 });1549 });
1372 1550
@@ -1400,7 +1578,7 @@ Prism.languages.jsonp = Prism.languages.json; · 27 unmodified lines
1400 pattern: /^\$\{|\}$/,1578 pattern: /^\$\{|\}$/,
1401 alias: 'variable'1579 alias: 'variable'
1402 },1580 },
1403 rest: Prism.util.clone(Prism.languages.kotlin)1581 rest: Prism.languages.kotlin
1404 }1582 }
1405 },1583 },
1406 {1584 {
@@ -1414,6 +1592,7 @@ Prism.languages.jsonp = Prism.languages.json; · 7 unmodified lines
1414 };1592 };
1415 1593
1416}(Prism));1594}(Prism));
1595
1417Prism.languages.lua = {1596Prism.languages.lua = {
1418 'comment': /^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,1597 'comment': /^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,
1419 // \z may be used to skip the following space1598 // \z may be used to skip the following space
@@ -1434,16 +1613,68 @@ Prism.languages.lua = { · 14 unmodified lines
1434 ],1613 ],
1435 'punctuation': /[\[\](){},;]|\.+|:+/1614 'punctuation': /[\[\](){},;]|\.+|:+/
1436};1615};
1616(function(Prism) {
1617 Prism.languages.crystal = Prism.languages.extend('ruby', {
1618 keyword: [
1619 /\b(?:abstract|alias|as|asm|begin|break|case|class|def|do|else|elsif|end|ensure|enum|extend|for|fun|if|include|instance_sizeof|lib|macro|module|next|of|out|pointerof|private|protected|rescue|return|require|select|self|sizeof|struct|super|then|type|typeof|uninitialized|union|unless|until|when|while|with|yield|__DIR__|__END_LINE__|__FILE__|__LINE__)\b/,
1620 {
1621 pattern: /(\.\s*)(?:is_a|responds_to)\?/,
1622 lookbehind: true
1623 }
1624 ],
1625
1626 number: /\b(?:0b[01_]*[01]|0o[0-7_]*[0-7]|0x[\da-fA-F_]*[\da-fA-F]|(?:\d(?:[\d_]*\d)?)(?:\.[\d_]*\d)?(?:[eE][+-]?[\d_]*\d)?)(?:_(?:[uif](?:8|16|32|64))?)?\b/
1627 });
1628
1629 Prism.languages.insertBefore('crystal', 'string', {
1630 attribute: {
1631 pattern: /@\[.+?\]/,
1632 alias: 'attr-name',
1633 inside: {
1634 delimiter: {
1635 pattern: /^@\[|\]$/,
1636 alias: 'tag'
1637 },
1638 rest: Prism.languages.crystal
1639 }
1640 },
1641
1642 expansion: [
1643 {
1644 pattern: /\{\{.+?\}\}/,
1645 inside: {
1646 delimiter: {
1647 pattern: /^\{\{|\}\}$/,
1648 alias: 'tag'
1649 },
1650 rest: Prism.languages.crystal
1651 }
1652 },
1653 {
1654 pattern: /\{%.+?%\}/,
1655 inside: {
1656 delimiter: {
1657 pattern: /^\{%|%\}$/,
1658 alias: 'tag'
1659 },
1660 rest: Prism.languages.crystal
1661 }
1662 }
1663 ]
1664 });
1665
1666}(Prism));
1667
1437Prism.languages.nginx = Prism.languages.extend('clike', {1668Prism.languages.nginx = Prism.languages.extend('clike', {
1438 'comment': {1669 'comment': {
1439 pattern: /(^|[^"{\\])#.*/,1670 pattern: /(^|[^"{\\])#.*/,
1440 lookbehind: true1671 lookbehind: true
1441 },1672 },
1442 'keyword': /\b(?:CONTENT_|DOCUMENT_|GATEWAY_|HTTP_|HTTPS|if_not_empty|PATH_|QUERY_|REDIRECT_|REMOTE_|REQUEST_|SCGI|SCRIPT_|SERVER_|http|events|accept_mutex|accept_mutex_delay|access_log|add_after_body|add_before_body|add_header|addition_types|aio|alias|allow|ancient_browser|ancient_browser_value|auth|auth_basic|auth_basic_user_file|auth_http|auth_http_header|auth_http_timeout|autoindex|autoindex_exact_size|autoindex_localtime|break|charset|charset_map|charset_types|chunked_transfer_encoding|client_body_buffer_size|client_body_in_file_only|client_body_in_single_buffer|client_body_temp_path|client_body_timeout|client_header_buffer_size|client_header_timeout|client_max_body_size|connection_pool_size|create_full_put_path|daemon|dav_access|dav_methods|debug_connection|debug_points|default_type|deny|devpoll_changes|devpoll_events|directio|directio_alignment|disable_symlinks|empty_gif|env|epoll_events|error_log|error_page|expires|fastcgi_buffer_size|fastcgi_buffers|fastcgi_busy_buffers_size|fastcgi_cache|fastcgi_cache_bypass|fastcgi_cache_key|fastcgi_cache_lock|fastcgi_cache_lock_timeout|fastcgi_cache_methods|fastcgi_cache_min_uses|fastcgi_cache_path|fastcgi_cache_purge|fastcgi_cache_use_stale|fastcgi_cache_valid|fastcgi_connect_timeout|fastcgi_hide_header|fastcgi_ignore_client_abort|fastcgi_ignore_headers|fastcgi_index|fastcgi_intercept_errors|fastcgi_keep_conn|fastcgi_max_temp_file_size|fastcgi_next_upstream|fastcgi_no_cache|fastcgi_param|fastcgi_pass|fastcgi_pass_header|fastcgi_read_timeout|fastcgi_redirect_errors|fastcgi_send_timeout|fastcgi_split_path_info|fastcgi_store|fastcgi_store_access|fastcgi_temp_file_write_size|fastcgi_temp_path|flv|geo|geoip_city|geoip_country|google_perftools_profiles|gzip|gzip_buffers|gzip_comp_level|gzip_disable|gzip_http_version|gzip_min_length|gzip_proxied|gzip_static|gzip_types|gzip_vary|if|if_modified_since|ignore_invalid_headers|image_filter|image_filter_buffer|image_filter_jpeg_quality|image_filter_sharpen|image_filter_transparency|imap_capabilities|imap_client_buffer|include|index|internal|ip_hash|keepalive|keepalive_disable|keepalive_requests|keepalive_timeout|kqueue_changes|kqueue_events|large_client_header_buffers|limit_conn|limit_conn_log_level|limit_conn_zone|limit_except|limit_rate|limit_rate_after|limit_req|limit_req_log_level|limit_req_zone|limit_zone|lingering_close|lingering_time|lingering_timeout|listen|location|lock_file|log_format|log_format_combined|log_not_found|log_subrequest|map|map_hash_bucket_size|map_hash_max_size|master_process|max_ranges|memcached_buffer_size|memcached_connect_timeout|memcached_next_upstream|memcached_pass|memcached_read_timeout|memcached_send_timeout|merge_slashes|min_delete_depth|modern_browser|modern_browser_value|mp4|mp4_buffer_size|mp4_max_buffer_size|msie_padding|msie_refresh|multi_accept|open_file_cache|open_file_cache_errors|open_file_cache_min_uses|open_file_cache_valid|open_log_file_cache|optimize_server_names|override_charset|pcre_jit|perl|perl_modules|perl_require|perl_set|pid|pop3_auth|pop3_capabilities|port_in_redirect|post_action|postpone_output|protocol|proxy|proxy_buffer|proxy_buffer_size|proxy_buffering|proxy_buffers|proxy_busy_buffers_size|proxy_cache|proxy_cache_bypass|proxy_cache_key|proxy_cache_lock|proxy_cache_lock_timeout|proxy_cache_methods|proxy_cache_min_uses|proxy_cache_path|proxy_cache_use_stale|proxy_cache_valid|proxy_connect_timeout|proxy_cookie_domain|proxy_cookie_path|proxy_headers_hash_bucket_size|proxy_headers_hash_max_size|proxy_hide_header|proxy_http_version|proxy_ignore_client_abort|proxy_ignore_headers|proxy_intercept_errors|proxy_max_temp_file_size|proxy_method|proxy_next_upstream|proxy_no_cache|proxy_pass|proxy_pass_error_message|proxy_pass_header|proxy_pass_request_body|proxy_pass_request_headers|proxy_read_timeout|proxy_redirect|proxy_redirect_errors|proxy_send_lowat|proxy_send_timeout|proxy_set_body|proxy_set_header|proxy_ssl_session_reuse|proxy_store|proxy_store_access|proxy_temp_file_write_size|proxy_temp_path|proxy_timeout|proxy_upstream_fail_timeout|proxy_upstream_max_fails|random_index|read_ahead|real_ip_header|recursive_error_pages|request_pool_size|reset_timedout_connection|resolver|resolver_timeout|return|rewrite|root|rtsig_overflow_events|rtsig_overflow_test|rtsig_overflow_threshold|rtsig_signo|satisfy|satisfy_any|secure_link_secret|send_lowat|send_timeout|sendfile|sendfile_max_chunk|server|server_name|server_name_in_redirect|server_names_hash_bucket_size|server_names_hash_max_size|server_tokens|set|set_real_ip_from|smtp_auth|smtp_capabilities|so_keepalive|source_charset|split_clients|ssi|ssi_silent_errors|ssi_types|ssi_value_length|ssl|ssl_certificate|ssl_certificate_key|ssl_ciphers|ssl_client_certificate|ssl_crl|ssl_dhparam|ssl_engine|ssl_prefer_server_ciphers|ssl_protocols|ssl_session_cache|ssl_session_timeout|ssl_verify_client|ssl_verify_depth|starttls|stub_status|sub_filter|sub_filter_once|sub_filter_types|tcp_nodelay|tcp_nopush|timeout|timer_resolution|try_files|types|types_hash_bucket_size|types_hash_max_size|underscores_in_headers|uninitialized_variable_warn|upstream|use|user|userid|userid_domain|userid_expires|userid_name|userid_p3p|userid_path|userid_service|valid_referers|variables_hash_bucket_size|variables_hash_max_size|worker_connections|worker_cpu_affinity|worker_priority|worker_processes|worker_rlimit_core|worker_rlimit_nofile|worker_rlimit_sigpending|working_directory|xclient|xml_entities|xslt_entities|xslt_stylesheet|xslt_types)\b/i1673 'keyword': /\b(?:CONTENT_|DOCUMENT_|GATEWAY_|HTTP_|HTTPS|if_not_empty|PATH_|QUERY_|REDIRECT_|REMOTE_|REQUEST_|SCGI|SCRIPT_|SERVER_|http|events|accept_mutex|accept_mutex_delay|access_log|add_after_body|add_before_body|add_header|addition_types|aio|alias|allow|ancient_browser|ancient_browser_value|auth|auth_basic|auth_basic_user_file|auth_http|auth_http_header|auth_http_timeout|autoindex|autoindex_exact_size|autoindex_localtime|break|charset|charset_map|charset_types|chunked_transfer_encoding|client_body_buffer_size|client_body_in_file_only|client_body_in_single_buffer|client_body_temp_path|client_body_timeout|client_header_buffer_size|client_header_timeout|client_max_body_size|connection_pool_size|create_full_put_path|daemon|dav_access|dav_methods|debug_connection|debug_points|default_type|deny|devpoll_changes|devpoll_events|directio|directio_alignment|disable_symlinks|empty_gif|env|epoll_events|error_log|error_page|expires|fastcgi_buffer_size|fastcgi_buffers|fastcgi_busy_buffers_size|fastcgi_cache|fastcgi_cache_bypass|fastcgi_cache_key|fastcgi_cache_lock|fastcgi_cache_lock_timeout|fastcgi_cache_methods|fastcgi_cache_min_uses|fastcgi_cache_path|fastcgi_cache_purge|fastcgi_cache_use_stale|fastcgi_cache_valid|fastcgi_connect_timeout|fastcgi_hide_header|fastcgi_ignore_client_abort|fastcgi_ignore_headers|fastcgi_index|fastcgi_intercept_errors|fastcgi_keep_conn|fastcgi_max_temp_file_size|fastcgi_next_upstream|fastcgi_no_cache|fastcgi_param|fastcgi_pass|fastcgi_pass_header|fastcgi_read_timeout|fastcgi_redirect_errors|fastcgi_send_timeout|fastcgi_split_path_info|fastcgi_store|fastcgi_store_access|fastcgi_temp_file_write_size|fastcgi_temp_path|flv|geo|geoip_city|geoip_country|google_perftools_profiles|gzip|gzip_buffers|gzip_comp_level|gzip_disable|gzip_http_version|gzip_min_length|gzip_proxied|gzip_static|gzip_types|gzip_vary|if|if_modified_since|ignore_invalid_headers|image_filter|image_filter_buffer|image_filter_jpeg_quality|image_filter_sharpen|image_filter_transparency|imap_capabilities|imap_client_buffer|include|index|internal|ip_hash|keepalive|keepalive_disable|keepalive_requests|keepalive_timeout|kqueue_changes|kqueue_events|large_client_header_buffers|limit_conn|limit_conn_log_level|limit_conn_zone|limit_except|limit_rate|limit_rate_after|limit_req|limit_req_log_level|limit_req_zone|limit_zone|lingering_close|lingering_time|lingering_timeout|listen|location|lock_file|log_format|log_format_combined|log_not_found|log_subrequest|map|map_hash_bucket_size|map_hash_max_size|master_process|max_ranges|memcached_buffer_size|memcached_connect_timeout|memcached_next_upstream|memcached_pass|memcached_read_timeout|memcached_send_timeout|merge_slashes|min_delete_depth|modern_browser|modern_browser_value|mp4|mp4_buffer_size|mp4_max_buffer_size|msie_padding|msie_refresh|multi_accept|open_file_cache|open_file_cache_errors|open_file_cache_min_uses|open_file_cache_valid|open_log_file_cache|optimize_server_names|override_charset|pcre_jit|perl|perl_modules|perl_require|perl_set|pid|pop3_auth|pop3_capabilities|port_in_redirect|post_action|postpone_output|protocol|proxy|proxy_buffer|proxy_buffer_size|proxy_buffering|proxy_buffers|proxy_busy_buffers_size|proxy_cache|proxy_cache_bypass|proxy_cache_key|proxy_cache_lock|proxy_cache_lock_timeout|proxy_cache_methods|proxy_cache_min_uses|proxy_cache_path|proxy_cache_use_stale|proxy_cache_valid|proxy_connect_timeout|proxy_cookie_domain|proxy_cookie_path|proxy_headers_hash_bucket_size|proxy_headers_hash_max_size|proxy_hide_header|proxy_http_version|proxy_ignore_client_abort|proxy_ignore_headers|proxy_intercept_errors|proxy_max_temp_file_size|proxy_method|proxy_next_upstream|proxy_no_cache|proxy_pass|proxy_pass_error_message|proxy_pass_header|proxy_pass_request_body|proxy_pass_request_headers|proxy_read_timeout|proxy_redirect|proxy_redirect_errors|proxy_send_lowat|proxy_send_timeout|proxy_set_body|proxy_set_header|proxy_ssl_session_reuse|proxy_store|proxy_store_access|proxy_temp_file_write_size|proxy_temp_path|proxy_timeout|proxy_upstream_fail_timeout|proxy_upstream_max_fails|random_index|read_ahead|real_ip_header|recursive_error_pages|request_pool_size|reset_timedout_connection|resolver|resolver_timeout|return|rewrite|root|rtsig_overflow_events|rtsig_overflow_test|rtsig_overflow_threshold|rtsig_signo|satisfy|satisfy_any|secure_link_secret|send_lowat|send_timeout|sendfile|sendfile_max_chunk|server|server_name|server_name_in_redirect|server_names_hash_bucket_size|server_names_hash_max_size|server_tokens|set|set_real_ip_from|smtp_auth|smtp_capabilities|so_keepalive|source_charset|split_clients|ssi|ssi_silent_errors|ssi_types|ssi_value_length|ssl|ssl_certificate|ssl_certificate_key|ssl_ciphers|ssl_client_certificate|ssl_crl|ssl_dhparam|ssl_engine|ssl_prefer_server_ciphers|ssl_protocols|ssl_session_cache|ssl_session_timeout|ssl_verify_client|ssl_verify_depth|starttls|stub_status|sub_filter|sub_filter_once|sub_filter_types|tcp_nodelay|tcp_nopush|timeout|timer_resolution|try_files|types|types_hash_bucket_size|types_hash_max_size|underscores_in_headers|uninitialized_variable_warn|upstream|use|user|userid|userid_domain|userid_expires|userid_name|userid_p3p|userid_path|userid_service|valid_referers|variables_hash_bucket_size|variables_hash_max_size|worker_connections|worker_cpu_affinity|worker_priority|worker_processes|worker_rlimit_core|worker_rlimit_nofile|worker_rlimit_sigpending|working_directory|xclient|xml_entities|xslt_entities|xslt_stylesheet|xslt_types)\b/i
1443});1674});
1444 1675
1445Prism.languages.insertBefore('nginx', 'keyword', {1676Prism.languages.insertBefore('nginx', 'keyword', {
1446 'variable': /\$[a-z_]+/i1677 'variable': /\$[a-z_]+/i
1447});1678});
1448Prism.languages.nim = {1679Prism.languages.nim = {
1449 'comment': /#.*/,1680 'comment': /#.*/,
@@ -1497,31 +1728,31 @@ Prism.languages.perl = { · 47 unmodified lines
1497 pattern: /\b(?:q|qq|qx|qw)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,1728 pattern: /\b(?:q|qq|qx|qw)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,
1498 greedy: true1729 greedy: true
1499 },1730 },
1500 1731
1501 // q a...a1732 // q a...a
1502 {1733 {
1503 pattern: /\b(?:q|qq|qx|qw)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1/,1734 pattern: /\b(?:q|qq|qx|qw)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1/,
1504 greedy: true1735 greedy: true
1505 },1736 },
1506 1737
1507 // q(...)1738 // q(...)
1508 {1739 {
1509 pattern: /\b(?:q|qq|qx|qw)\s*\((?:[^()\\]|\\[\s\S])*\)/,1740 pattern: /\b(?:q|qq|qx|qw)\s*\((?:[^()\\]|\\[\s\S])*\)/,
1510 greedy: true1741 greedy: true
1511 },1742 },
1512 1743
1513 // q{...}1744 // q{...}
1514 {1745 {
1515 pattern: /\b(?:q|qq|qx|qw)\s*\{(?:[^{}\\]|\\[\s\S])*\}/,1746 pattern: /\b(?:q|qq|qx|qw)\s*\{(?:[^{}\\]|\\[\s\S])*\}/,
1516 greedy: true1747 greedy: true
1517 },1748 },
1518 1749
1519 // q[...]1750 // q[...]
1520 {1751 {
1521 pattern: /\b(?:q|qq|qx|qw)\s*\[(?:[^[\]\\]|\\[\s\S])*\]/,1752 pattern: /\b(?:q|qq|qx|qw)\s*\[(?:[^[\]\\]|\\[\s\S])*\]/,
1522 greedy: true1753 greedy: true
1523 },1754 },
1524 1755
1525 // q<...>1756 // q<...>
1526 {1757 {
1527 pattern: /\b(?:q|qq|qx|qw)\s*<(?:[^<>\\]|\\[\s\S])*>/,1758 pattern: /\b(?:q|qq|qx|qw)\s*<(?:[^<>\\]|\\[\s\S])*>/,
@@ -1547,31 +1778,31 @@ Prism.languages.perl = { · 19 unmodified lines
1547 pattern: /\b(?:m|qr)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,1778 pattern: /\b(?:m|qr)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,
1548 greedy: true1779 greedy: true
1549 },1780 },
1550 1781
1551 // m a...a1782 // m a...a
1552 {1783 {
1553 pattern: /\b(?:m|qr)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,1784 pattern: /\b(?:m|qr)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,
1554 greedy: true1785 greedy: true
1555 },1786 },
1556 1787
1557 // m(...)1788 // m(...)
1558 {1789 {
1559 pattern: /\b(?:m|qr)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngc]*/,1790 pattern: /\b(?:m|qr)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngc]*/,
1560 greedy: true1791 greedy: true
1561 },1792 },
1562 1793
1563 // m{...}1794 // m{...}
1564 {1795 {
1565 pattern: /\b(?:m|qr)\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngc]*/,1796 pattern: /\b(?:m|qr)\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngc]*/,
1566 greedy: true1797 greedy: true
1567 },1798 },
1568 1799
1569 // m[...]1800 // m[...]
1570 {1801 {
1571 pattern: /\b(?:m|qr)\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngc]*/,1802 pattern: /\b(?:m|qr)\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngc]*/,
1572 greedy: true1803 greedy: true
1573 },1804 },
1574 1805
1575 // m<...>1806 // m<...>
1576 {1807 {
1577 pattern: /\b(?:m|qr)\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngc]*/,1808 pattern: /\b(?:m|qr)\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngc]*/,
@@ -1586,42 +1817,42 @@ Prism.languages.perl = { · 8 unmodified lines
1586 lookbehind: true,1817 lookbehind: true,
1587 greedy: true1818 greedy: true
1588 },1819 },
1589 1820
1590 // s a...a...a1821 // s a...a...a
1591 {1822 {
1592 pattern: /(^|[^-]\b)(?:s|tr|y)\s+([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,1823 pattern: /(^|[^-]\b)(?:s|tr|y)\s+([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,
1593 lookbehind: true,1824 lookbehind: true,
1594 greedy: true1825 greedy: true
1595 },1826 },
1596 1827
1597 // s(...)(...)1828 // s(...)(...)
1598 {1829 {
1599 pattern: /(^|[^-]\b)(?:s|tr|y)\s*\((?:[^()\\]|\\[\s\S])*\)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngcer]*/,1830 pattern: /(^|[^-]\b)(?:s|tr|y)\s*\((?:[^()\\]|\\[\s\S])*\)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngcer]*/,
1600 lookbehind: true,1831 lookbehind: true,
1601 greedy: true1832 greedy: true
1602 },1833 },
1603 1834
1604 // s{...}{...}1835 // s{...}{...}
1605 {1836 {
1606 pattern: /(^|[^-]\b)(?:s|tr|y)\s*\{(?:[^{}\\]|\\[\s\S])*\}\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngcer]*/,1837 pattern: /(^|[^-]\b)(?:s|tr|y)\s*\{(?:[^{}\\]|\\[\s\S])*\}\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngcer]*/,
1607 lookbehind: true,1838 lookbehind: true,
1608 greedy: true1839 greedy: true
1609 },1840 },
1610 1841
1611 // s[...][...]1842 // s[...][...]
1612 {1843 {
1613 pattern: /(^|[^-]\b)(?:s|tr|y)\s*\[(?:[^[\]\\]|\\[\s\S])*\]\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngcer]*/,1844 pattern: /(^|[^-]\b)(?:s|tr|y)\s*\[(?:[^[\]\\]|\\[\s\S])*\]\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngcer]*/,
1614 lookbehind: true,1845 lookbehind: true,
1615 greedy: true1846 greedy: true
1616 },1847 },
1617 1848
1618 // s<...><...>1849 // s<...><...>
1619 {1850 {
1620 pattern: /(^|[^-]\b)(?:s|tr|y)\s*<(?:[^<>\\]|\\[\s\S])*>\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngcer]*/,1851 pattern: /(^|[^-]\b)(?:s|tr|y)\s*<(?:[^<>\\]|\\[\s\S])*>\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngcer]*/,
1621 lookbehind: true,1852 lookbehind: true,
1622 greedy: true1853 greedy: true
1623 },1854 },
1624 1855
1625 // /.../1856 // /.../
1626 // The look-ahead tries to prevent two divisions on1857 // The look-ahead tries to prevent two divisions on
1627 // the same line from being highlighted as regex.1858 // the same line from being highlighted as regex.
@@ -1665,7 +1896,7 @@ Prism.languages.perl = { · 37 unmodified lines
1665 }1896 }
1666 },1897 },
1667 'keyword': /\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|say|state|sub|switch|undef|unless|until|use|when|while)\b/,1898 'keyword': /\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|say|state|sub|switch|undef|unless|until|use|when|while)\b/,
1668 'number': /\b-?(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,1899 'number': /\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,
1669 'operator': /-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,1900 'operator': /-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,
1670 'punctuation': /[{}[\];(),:]/1901 'punctuation': /[{}[\];(),:]/
1671};1902};
@@ -1682,110 +1913,211 @@ Prism.languages.perl = { · 10 unmodified lines
1682 * Adds the following new token classes:1913 * Adds the following new token classes:
1683 * constant, delimiter, variable, function, package1914 * constant, delimiter, variable, function, package
1684 */1915 */
1916(function (Prism) {
1917 Prism.languages.php = Prism.languages.extend('clike', {
1918 'keyword': /\b(?:and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|private|protected|parent|throw|null|echo|print|trait|namespace|final|yield|goto|instanceof|finally|try|catch)\b/i,
1919 'constant': /\b[A-Z0-9_]{2,}\b/,
1920 'comment': {
1921 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,
1922 lookbehind: true
1923 }
1924 });
1685 1925
1686Prism.languages.php = Prism.languages.extend('clike', {1926 Prism.languages.insertBefore('php', 'string', {
1687 'keyword': /\b(?:and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|private|protected|parent|throw|null|echo|print|trait|namespace|final|yield|goto|instanceof|finally|try|catch)\b/i,1927 'shell-comment': {
1688 'constant': /\b[A-Z0-9_]{2,}\b/,1928 pattern: /(^|[^\\])#.*/,
1689 'comment': {1929 lookbehind: true,
1690 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,1930 alias: 'comment'
1691 lookbehind: true1931 }
1692 }1932 });
1693});
1694 1933
1695// Shell-like comments are matched after strings, because they are less1934 Prism.languages.insertBefore('php', 'keyword', {
1696// common than strings containing hashes...1935 'delimiter': {
1697Prism.languages.insertBefore('php', 'class-name', {1936 pattern: /\?>|<\?(?:php|=)?/i,
1698 'shell-comment': {1937 alias: 'important'
1699 pattern: /(^|[^\\])#.*/,1938 },
1700 lookbehind: true,1939 'variable': /\$+(?:\w+\b|(?={))/i,
1701 alias: 'comment'1940 'package': {
1702 }1941 pattern: /(\\|namespace\s+|use\s+)[\w\\]+/,
1703});1942 lookbehind: true,
1943 inside: {
1944 punctuation: /\\/
1945 }
1946 }
1947 });
1704 1948
1705Prism.languages.insertBefore('php', 'keyword', {1949 // Must be defined after the function pattern
1706 'delimiter': {1950 Prism.languages.insertBefore('php', 'operator', {
1707 pattern: /\?>|<\?(?:php|=)?/i,1951 'property': {
1708 alias: 'important'1952 pattern: /(->)[\w]+/,
1709 },1953 lookbehind: true
1710 'variable': /\$\w+\b/i,
1711 'package': {
1712 pattern: /(\\|namespace\s+|use\s+)[\w\\]+/,
1713 lookbehind: true,
1714 inside: {
1715 punctuation: /\\/
1716 }1954 }
1717 }1955 });
1718});
1719 1956
1720// Must be defined after the function pattern1957 Prism.languages.insertBefore('php', 'string', {
1721Prism.languages.insertBefore('php', 'operator', {1958 'nowdoc-string': {
1722 'property': {1959 pattern: /<<<'([^']+)'(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;/,
1723 pattern: /(->)[\w]+/,1960 greedy: true,
1724 lookbehind: true1961 alias: 'string',
1725 }1962 inside: {
1726});1963 'delimiter': {
1964 pattern: /^<<<'[^']+'|[a-z_]\w*;$/i,
1965 alias: 'symbol',
1966 inside: {
1967 'punctuation': /^<<<'?|[';]$/
1968 }
1969 }
1970 }
1971 },
1972 'heredoc-string': {
1973 pattern: /<<<(?:"([^"]+)"(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;|([a-z_]\w*)(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\2;)/i,
1974 greedy: true,
1975 alias: 'string',
1976 inside: {
1977 'delimiter': {
1978 pattern: /^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,
1979 alias: 'symbol',
1980 inside: {
1981 'punctuation': /^<<<"?|[";]$/
1982 }
1983 },
1984 'interpolation': null // See below
1985 }
1986 },
1987 'single-quoted-string': {
1988 pattern: /'(?:\\[\s\S]|[^\\'])*'/,
1989 greedy: true,
1990 alias: 'string'
1991 },
1992 'double-quoted-string': {
1993 pattern: /"(?:\\[\s\S]|[^\\"])*"/,
1994 greedy: true,
1995 alias: 'string',
1996 inside: {
1997 'interpolation': null // See below
1998 }
1999 }
2000 });
2001 // The different types of PHP strings "replace" the C-like standard string
2002 delete Prism.languages.php['string'];
1727 2003
1728// Add HTML support if the markup language exists2004 var string_interpolation = {
1729if (Prism.languages.markup) {2005 pattern: /{\$(?:{(?:{[^{}]+}|[^{}]+)}|[^{}])+}|(^|[^\\{])\$+(?:\w+(?:\[.+?]|->\w+)*)/,
2006 lookbehind: true,
2007 inside: {
2008 rest: Prism.languages.php
2009 }
2010 };
2011 Prism.languages.php['heredoc-string'].inside['interpolation'] = string_interpolation;
2012 Prism.languages.php['double-quoted-string'].inside['interpolation'] = string_interpolation;
1730 2013
1731 // Tokenize all inline PHP blocks that are wrapped in <?php ?>2014 Prism.hooks.add('before-tokenize', function(env) {
1732 // This allows for easy PHP + markup highlighting2015 if (!/(?:<\?php|<\?)/ig.test(env.code)) {
1733 Prism.hooks.add('before-highlight', function(env) {
1734 if (env.language !== 'php' || !/(?:<\?php|<\?)/ig.test(env.code)) {
1735 return;2016 return;
1736 }2017 }
1737 2018
1738 env.tokenStack = [];2019 var phpPattern = /(?:<\?php|<\?)[\s\S]*?(?:\?>|$)/ig;
1739 2020 Prism.languages['markup-templating'].buildPlaceholders(env, 'php', phpPattern);
1740 env.backupCode = env.code;2021 });
1741 env.code = env.code.replace(/(?:<\?php|<\?)[\s\S]*?(?:\?>|$)/ig, function(match) {
1742 var i = env.tokenStack.length;
1743 // Check for existing strings
1744 while (env.backupCode.indexOf('___PHP' + i + '___') !== -1)
1745 ++i;
1746
1747 // Create a sparse array
1748 env.tokenStack[i] = match;
1749
1750 return '___PHP' + i + '___';
1751 });
1752 2022
1753 // Switch the grammar to markup2023 Prism.hooks.add('after-tokenize', function(env) {
1754 env.grammar = Prism.languages.markup;2024 Prism.languages['markup-templating'].tokenizePlaceholders(env, 'php');
1755 });2025 });
1756 2026
1757 // Restore env.code for other plugins (e.g. line-numbers)2027}(Prism));
1758 Prism.hooks.add('before-insert', function(env) {2028Prism.languages.sql= {
1759 if (env.language === 'php' && env.backupCode) {2029 'comment': {
1760 env.code = env.backupCode;2030 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,
1761 delete env.backupCode;2031 lookbehind: true
2032 },
2033 'string' : {
2034 pattern: /(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\])*\2/,
2035 greedy: true,
2036 lookbehind: true
2037 },
2038 'variable': /@[\w.$]+|@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,
2039 'function': /\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i, // Should we highlight user defined functions too?
2040 'keyword': /\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:_INSERT|COL)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURNS?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,
2041 'boolean': /\b(?:TRUE|FALSE|NULL)\b/i,
2042 'number': /\b0x[\da-f]+\b|\b\d+\.?\d*|\B\.\d+\b/i,
2043 'operator': /[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,
2044 'punctuation': /[;[\]()`,.]/
2045};
2046Prism.languages.scss = Prism.languages.extend('css', {
2047 'comment': {
2048 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,
2049 lookbehind: true
2050 },
2051 'atrule': {
2052 pattern: /@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,
2053 inside: {
2054 'rule': /@[\w-]+/
2055 // See rest below
1762 }2056 }
1763 });2057 },
2058 // url, compassified
2059 'url': /(?:[-a-z]+-)*url(?=\()/i,
2060 // CSS selector regex is not appropriate for Sass
2061 // since there can be lot more things (var, @ directive, nesting..)
2062 // a selector must start at the end of a property or after a brace (end of other rules or nesting)
2063 // it can contain some characters that aren't used for defining rules or end of selector, & (parent selector), or interpolated variable
2064 // the end of a selector is found when there is no rules in it ( {} or {\s}) or if there is a property (because an interpolated var
2065 // can "pass" as a selector- e.g: proper#{$erty})
2066 // this one was hard to do, so please be careful if you edit this one :)
2067 'selector': {
2068 // Initial look-ahead is used to prevent matching of blank selectors
2069 pattern: /(?=\S)[^@;{}()]?(?:[^@;{}()]|&|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}]+[:{][^}]+))/m,
2070 inside: {
2071 'parent': {
2072 pattern: /&/,
2073 alias: 'important'
2074 },
2075 'placeholder': /%[-\w]+/,
2076 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2077 }
2078 }
2079});
1764 2080
1765 // Re-insert the tokens after highlighting2081Prism.languages.insertBefore('scss', 'atrule', {
1766 Prism.hooks.add('after-highlight', function(env) {2082 'keyword': [
1767 if (env.language !== 'php' || !env.tokenStack) {2083 /@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,
1768 return;2084 {
2085 pattern: /( +)(?:from|through)(?= )/,
2086 lookbehind: true
1769 }2087 }
2088 ]
2089});
1770 2090
1771 // Switch the grammar back2091Prism.languages.scss.property = {
1772 env.grammar = Prism.languages.php;2092 pattern: /(?:[\w-]|\$[-\w]+|#\{\$[-\w]+\})+(?=\s*:)/i,
2093 inside: {
2094 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2095 }
2096};
1773 2097
1774 for (var i = 0, keys = Object.keys(env.tokenStack); i < keys.length; ++i) {2098Prism.languages.insertBefore('scss', 'important', {
1775 var k = keys[i];2099 // var and interpolated vars
1776 var t = env.tokenStack[k];2100 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2101});
1777 2102
1778 // The replace prevents $$, $&, $`, $', $n, $nn from being interpreted as special patterns2103Prism.languages.insertBefore('scss', 'function', {
1779 env.highlightedCode = env.highlightedCode.replace('___PHP' + k + '___',2104 'placeholder': {
1780 "<span class=\"token php language-php\">" +2105 pattern: /%[-\w]+/,
1781 Prism.highlight(t, env.grammar, 'php').replace(/\$/g, '$$$$') +2106 alias: 'selector'
1782 "</span>");2107 },
1783 }2108 'statement': {
2109 pattern: /\B!(?:default|optional)\b/i,
2110 alias: 'keyword'
2111 },
2112 'boolean': /\b(?:true|false)\b/,
2113 'null': /\bnull\b/,
2114 'operator': {
2115 pattern: /(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,
2116 lookbehind: true
2117 }
2118});
1784 2119
1785 env.element.innerHTML = env.highlightedCode;2120Prism.languages.scss['atrule'].inside.rest = Prism.languages.scss;
1786 });
1787}
1788;
1789Prism.languages.python = {2121Prism.languages.python = {
1790 'comment': {2122 'comment': {
1791 pattern: /(^|[^\\])#.*/,2123 pattern: /(^|[^\\])#.*/,
@@ -1811,7 +2143,7 @@ Prism.languages.python = { · 19 unmodified lines
1811 'keyword': /\b(?:as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|pass|print|raise|return|try|while|with|yield)\b/,2143 'keyword': /\b(?:as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|pass|print|raise|return|try|while|with|yield)\b/,
1812 'builtin':/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,2144 'builtin':/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,
1813 'boolean': /\b(?:True|False|None)\b/,2145 'boolean': /\b(?:True|False|None)\b/,
1814 'number': /\b-?(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,2146 'number': /(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,
1815 'operator': /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,2147 'operator': /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,
1816 'punctuation': /[{}[\];(),.:]/2148 'punctuation': /[{}[\];(),.:]/
1817};2149};
@@ -1821,93 +2153,134 @@ Prism.languages.python = { · 3 unmodified lines
1821var javascript = Prism.util.clone(Prism.languages.javascript);2153var javascript = Prism.util.clone(Prism.languages.javascript);
1822 2154
1823Prism.languages.jsx = Prism.languages.extend('markup', javascript);2155Prism.languages.jsx = Prism.languages.extend('markup', javascript);
1824Prism.languages.jsx.tag.pattern= /<\/?[\w.:-]+\s*(?:\s+(?:[\w\.:-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+|(?:\{[^}]*\})))?|\{\.{3}\w+\}))*\s*\/?>/i;2156Prism.languages.jsx.tag.pattern= /<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s{'">=]+|\{(?:\{(?:\{[^}]*\}|[^{}])*\}|[^{}])+\}))?|\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}))*\s*\/?)?>/i;
1825 2157
2158Prism.languages.jsx.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/i;
1826Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i;2159Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i;
1827 2160
1828Prism.languages.insertBefore('inside', 'attr-name', {2161Prism.languages.insertBefore('inside', 'attr-name', {
1829 'spread': {2162 'spread': {
1830 pattern: /\{\.{3}\w+\}/,2163 pattern: /\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}/,
1831 inside: {2164 inside: {
1832 'punctuation': /[{}]|\.{3}/,2165 'punctuation': /\.{3}|[{}.]/,
1833 'attr-value': /\w+/2166 'attr-value': /\w+/
1834 }2167 }
1835 }2168 }
1836}, Prism.languages.jsx.tag);2169}, Prism.languages.jsx.tag);
1837 2170
1838var jsxExpression = Prism.util.clone(Prism.languages.jsx);
1839
1840delete jsxExpression.punctuation;
1841
1842jsxExpression = Prism.languages.insertBefore('jsx', 'operator', {
1843 'punctuation': /=(?={)|[{}[\];(),.:]/
1844}, { jsx: jsxExpression });
1845
1846Prism.languages.insertBefore('inside', 'attr-value',{2171Prism.languages.insertBefore('inside', 'attr-value',{
1847 'script': {2172 'script': {
1848 // Allow for one level of nesting2173 // Allow for two levels of nesting
1849 pattern: /=(\{(?:\{[^}]*\}|[^}])+\})/i,2174 pattern: /=(\{(?:\{(?:\{[^}]*\}|[^}])*\}|[^}])+\})/i,
1850 inside: jsxExpression,2175 inside: {
2176 'script-punctuation': {
2177 pattern: /^=(?={)/,
2178 alias: 'punctuation'
2179 },
2180 rest: Prism.languages.jsx
2181 },
1851 'alias': 'language-javascript'2182 'alias': 'language-javascript'
1852 }2183 }
1853}, Prism.languages.jsx.tag);2184}, Prism.languages.jsx.tag);
1854 2185
1855}(Prism));2186// The following will handle plain text inside tags
2187var stringifyToken = function (token) {
2188 if (!token) {
2189 return '';
2190 }
2191 if (typeof token === 'string') {
2192 return token;
2193 }
2194 if (typeof token.content === 'string') {
2195 return token.content;
2196 }
2197 return token.content.map(stringifyToken).join('');
2198};
1856 2199
1857(function(Prism) {2200var walkTokens = function (tokens) {
1858 Prism.languages.crystal = Prism.languages.extend('ruby', {2201 var openedTags = [];
1859 keyword: [2202 for (var i = 0; i < tokens.length; i++) {
1860 /\b(?:abstract|alias|as|asm|begin|break|case|class|def|do|else|elsif|end|ensure|enum|extend|for|fun|if|include|instance_sizeof|lib|macro|module|next|of|out|pointerof|private|protected|rescue|return|require|select|self|sizeof|struct|super|then|type|typeof|uninitialized|union|unless|until|when|while|with|yield|__DIR__|__END_LINE__|__FILE__|__LINE__)\b/,2203 var token = tokens[i];
1861 {2204 var notTagNorBrace = false;
1862 pattern: /(\.\s*)(?:is_a|responds_to)\?/,2205
1863 lookbehind: true2206 if (typeof token !== 'string') {
1864 }2207 if (token.type === 'tag' && token.content[0] && token.content[0].type === 'tag') {
1865 ],2208 // We found a tag, now find its kind
2209
2210 if (token.content[0].content[0].content === '</') {
2211 // Closing tag
2212 if (openedTags.length > 0 && openedTags[openedTags.length - 1].tagName === stringifyToken(token.content[0].content[1])) {
2213 // Pop matching opening tag
2214 openedTags.pop();
2215 }
2216 } else {
2217 if (token.content[token.content.length - 1].content === '/>') {
2218 // Autoclosed tag, ignore
2219 } else {
2220 // Opening tag
2221 openedTags.push({
2222 tagName: stringifyToken(token.content[0].content[1]),
2223 openedBraces: 0
2224 });
2225 }
2226 }
2227 } else if (openedTags.length > 0 && token.type === 'punctuation' && token.content === '{') {
1866 2228
1867 number: /\b(?:0b[01_]*[01]|0o[0-7_]*[0-7]|0x[0-9a-fA-F_]*[0-9a-fA-F]|(?:\d(?:[0-9_]*\d)?)(?:\.[0-9_]*\d)?(?:[eE][+-]?[0-9_]*\d)?)(?:_(?:[uif](?:8|16|32|64))?)?\b/2229 // Here we might have entered a JSX context inside a tag
1868 });2230 openedTags[openedTags.length - 1].openedBraces++;
1869 2231
1870 var rest = Prism.util.clone(Prism.languages.crystal);2232 } else if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces > 0 && token.type === 'punctuation' && token.content === '}') {
1871 2233
1872 Prism.languages.insertBefore('crystal', 'string', {2234 // Here we might have left a JSX context inside a tag
1873 attribute: {2235 openedTags[openedTags.length - 1].openedBraces--;
1874 pattern: /@\[.+?\]/,
1875 alias: 'attr-name',
1876 inside: {
1877 delimiter: {
1878 pattern: /^@\[|\]$/,
1879 alias: 'tag'
1880 },
1881 rest: rest
1882 }
1883 },
1884 2236
1885 expansion: [2237 } else {
1886 {2238 notTagNorBrace = true
1887 pattern: /\{\{.+?\}\}/,
1888 inside: {
1889 delimiter: {
1890 pattern: /^\{\{|\}\}$/,
1891 alias: 'tag'
1892 },
1893 rest: rest
1894 }2239 }
1895 },2240 }
1896 {2241 if (notTagNorBrace || typeof token === 'string') {
1897 pattern: /\{%.+?%\}/,2242 if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces === 0) {
1898 inside: {2243 // Here we are inside a tag, and not inside a JSX context.
1899 delimiter: {2244 // That's plain text: drop any tokens matched.
1900 pattern: /^\{%|%\}$/,2245 var plainText = stringifyToken(token);
1901 alias: 'tag'2246
1902 },2247 // And merge text with adjacent text
1903 rest: rest2248 if (i < tokens.length - 1 && (typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')) {
2249 plainText += stringifyToken(tokens[i + 1]);
2250 tokens.splice(i + 1, 1);
2251 }
2252 if (i > 0 && (typeof tokens[i - 1] === 'string' || tokens[i - 1].type === 'plain-text')) {
2253 plainText = stringifyToken(tokens[i - 1]) + plainText;
2254 tokens.splice(i - 1, 1);
2255 i--;
2256 }
2257
2258 tokens[i] = new Prism.Token('plain-text', plainText, null, plainText);
1904 }2259 }
1905 }2260 }
1906 ]2261
1907 });2262 if (token.content && typeof token.content !== 'string') {
2263 walkTokens(token.content);
2264 }
2265 }
2266};
2267
2268Prism.hooks.add('after-tokenize', function (env) {
2269 if (env.language !== 'jsx' && env.language !== 'tsx') {
2270 return;
2271 }
2272 walkTokens(env.tokens);
2273});
1908 2274
1909}(Prism));2275}(Prism));
1910 2276
2277Prism.languages.typescript = Prism.languages.extend('javascript', {
2278 // From JavaScript Prism keyword list and TypeScript language spec: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#221-reserved-words
2279 'keyword': /\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield|module|declare|constructor|namespace|abstract|require|type)\b/,
2280 'builtin': /\b(?:string|Function|any|number|boolean|Array|symbol|console)\b/,
2281});
2282
2283Prism.languages.ts = Prism.languages.typescript;
1911/* TODO2284/* TODO
1912 Add support for Markdown notation inside doc comments2285 Add support for Markdown notation inside doc comments
1913 Add support for nested block comments...2286 Add support for nested block comments...
@@ -1932,10 +2305,18 @@ Prism.languages.rust = { · 18 unmodified lines
1932 greedy: true2305 greedy: true
1933 },2306 },
1934 {2307 {
1935 pattern: /b?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,2308 pattern: /b?"(?:\\.|[^\\\r\n"])*"/,
1936 greedy: true2309 greedy: true
1937 }2310 }
1938 ],2311 ],
2312 'char': {
2313 pattern: /b?'(?:\\(?:x[0-7][\da-fA-F]|u{(?:[\da-fA-F]_*){1,6}|.)|[^\\\r\n\t'])'/,
2314 alias: 'string'
2315 },
2316 'lifetime-annotation': {
2317 pattern: /'[^\s>']+/,
2318 alias: 'symbol'
2319 },
1939 'keyword': /\b(?:abstract|alignof|as|be|box|break|const|continue|crate|do|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|match|mod|move|mut|offsetof|once|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/,2320 'keyword': /\b(?:abstract|alignof|as|be|box|break|const|continue|crate|do|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|match|mod|move|mut|offsetof|once|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/,
1940 2321
1941 'attribute': {2322 'attribute': {
@@ -1955,7 +2336,7 @@ Prism.languages.rust = { · 13 unmodified lines
1955 },2336 },
1956 2337
1957 // Hex, oct, bin, dec numbers with visual separators and type suffix2338 // Hex, oct, bin, dec numbers with visual separators and type suffix
1958 'number': /\b-?(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:[iu](?:8|16|32|64)?|f32|f64))?\b/,2339 'number': /\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:[iu](?:8|16|32|64)?|f32|f64))?\b/,
1959 2340
1960 // Closure params should not be confused with bitwise OR |2341 // Closure params should not be confused with bitwise OR |
1961 'closure-params': {2342 'closure-params': {
@@ -1966,107 +2347,8 @@ Prism.languages.rust = { · 4 unmodified lines
1966 }2347 }
1967 },2348 },
1968 'punctuation': /[{}[\];(),:]|\.+|->/,2349 'punctuation': /[{}[\];(),:]|\.+|->/,
1969 'operator': /[-+*\/%!^=]=?|@|&[&=]?|\|[|=]?|<<?=?|>>?=?/2350 'operator': /[-+*\/%!^]=?|=[=>]?|@|&[&=]?|\|[|=]?|<<?=?|>>?=?/
1970};2351};
1971Prism.languages.scss = Prism.languages.extend('css', {
1972 'comment': {
1973 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,
1974 lookbehind: true
1975 },
1976 'atrule': {
1977 pattern: /@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,
1978 inside: {
1979 'rule': /@[\w-]+/
1980 // See rest below
1981 }
1982 },
1983 // url, compassified
1984 'url': /(?:[-a-z]+-)*url(?=\()/i,
1985 // CSS selector regex is not appropriate for Sass
1986 // since there can be lot more things (var, @ directive, nesting..)
1987 // a selector must start at the end of a property or after a brace (end of other rules or nesting)
1988 // it can contain some characters that aren't used for defining rules or end of selector, & (parent selector), or interpolated variable
1989 // the end of a selector is found when there is no rules in it ( {} or {\s}) or if there is a property (because an interpolated var
1990 // can "pass" as a selector- e.g: proper#{$erty})
1991 // this one was hard to do, so please be careful if you edit this one :)
1992 'selector': {
1993 // Initial look-ahead is used to prevent matching of blank selectors
1994 pattern: /(?=\S)[^@;{}()]?(?:[^@;{}()]|&|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}]+[:{][^}]+))/m,
1995 inside: {
1996 'parent': {
1997 pattern: /&/,
1998 alias: 'important'
1999 },
2000 'placeholder': /%[-\w]+/,
2001 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2002 }
2003 }
2004});
2005
2006Prism.languages.insertBefore('scss', 'atrule', {
2007 'keyword': [
2008 /@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,
2009 {
2010 pattern: /( +)(?:from|through)(?= )/,
2011 lookbehind: true
2012 }
2013 ]
2014});
2015
2016Prism.languages.scss.property = {
2017 pattern: /(?:[\w-]|\$[-\w]+|#\{\$[-\w]+\})+(?=\s*:)/i,
2018 inside: {
2019 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2020 }
2021};
2022
2023Prism.languages.insertBefore('scss', 'important', {
2024 // var and interpolated vars
2025 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2026});
2027
2028Prism.languages.insertBefore('scss', 'function', {
2029 'placeholder': {
2030 pattern: /%[-\w]+/,
2031 alias: 'selector'
2032 },
2033 'statement': {
2034 pattern: /\B!(?:default|optional)\b/i,
2035 alias: 'keyword'
2036 },
2037 'boolean': /\b(?:true|false)\b/,
2038 'null': /\bnull\b/,
2039 'operator': {
2040 pattern: /(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,
2041 lookbehind: true
2042 }
2043});
2044
2045Prism.languages.scss['atrule'].inside.rest = Prism.util.clone(Prism.languages.scss);
2046Prism.languages.sql= {
2047 'comment': {
2048 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,
2049 lookbehind: true
2050 },
2051 'string' : {
2052 pattern: /(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\])*\2/,
2053 greedy: true,
2054 lookbehind: true
2055 },
2056 'variable': /@[\w.$]+|@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,
2057 'function': /\b(?:COUNT|SUM|AVG|MIN|MAX|FIRST|LAST|UCASE|LCASE|MID|LEN|ROUND|NOW|FORMAT)(?=\s*\()/i, // Should we highlight user defined functions too?
2058 'keyword': /\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR VARYING|CHARACTER (?:SET|VARYING)|CHARSET|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|DATA(?:BASES?)?|DATE(?:TIME)?|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITER(?:S)?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE(?: PRECISION)?|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE KEY|ELSE|ENABLE|ENCLOSED BY|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPE(?:D BY)?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|IDENTITY(?:_INSERT|COL)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTO|INVOKER|ISOLATION LEVEL|JOIN|KEYS?|KILL|LANGUAGE SQL|LAST|LEFT|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MODIFIES SQL DATA|MODIFY|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL(?: CHAR VARYING| CHARACTER(?: VARYING)?| VARCHAR)?|NATURAL|NCHAR(?: VARCHAR)?|NEXT|NO(?: SQL|CHECK|CYCLE)?|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READ(?:S SQL DATA|TEXT)?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEATABLE|REPLICATION|REQUIRE|RESTORE|RESTRICT|RETURNS?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE MODE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|START(?:ING BY)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED BY|TEXT(?:SIZE)?|THEN|TIMESTAMP|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNPIVOT|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?)\b/i,
2059 'boolean': /\b(?:TRUE|FALSE|NULL)\b/i,
2060 'number': /\b-?(?:0x)?\d*\.?[\da-f]+\b/,
2061 'operator': /[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,
2062 'punctuation': /[;[\]()`,.]/
2063};
2064Prism.languages.typescript = Prism.languages.extend('javascript', {
2065 // From JavaScript Prism keyword list and TypeScript language spec: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#221-reserved-words
2066 'keyword': /\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield|false|true|module|declare|constructor|string|Function|any|number|boolean|Array|symbol|namespace|abstract|require|type)\b/
2067});
2068
2069Prism.languages.ts = Prism.languages.typescript;
2070Prism.languages.yaml = {2352Prism.languages.yaml = {
2071 'scalar': {2353 'scalar': {
2072 pattern: /([\-:]\s*(?:![^\s]+)?[ \t]*[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\2[^\r\n]+)*)/,2354 pattern: /([\-:]\s*(?:![^\s]+)?[ \t]*[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\2[^\r\n]+)*)/,
@@ -2105,11 +2387,10 @@ Prism.languages.yaml = { · 32 unmodified lines
2105 greedy: true2387 greedy: true
2106 },2388 },
2107 'number': {2389 'number': {
2108 pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)[+\-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+\.?\d*|\.?\d+)(?:e[+-]?\d+)?|\.inf|\.nan)[ \t]*(?=$|,|]|})/im,2390 pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+\.?\d*|\.?\d+)(?:e[+-]?\d+)?|\.inf|\.nan)[ \t]*(?=$|,|]|})/im,
2109 lookbehind: true2391 lookbehind: true
2110 },2392 },
2111 'tag': /![^\s]+/,2393 'tag': /![^\s]+/,
2112 'important': /[&*][\w]+/,2394 'important': /[&*][\w]+/,
2113 'punctuation': /---|[:[\]{}\-,|>?]|\.\.\./2395 'punctuation': /---|[:[\]{}\-,|>?]|\.\.\./
2114};2396};
2115
assets/javascripts/vendor/raven.js+1−1
@@ -856,7 +856,7 @@ Raven.prototype = { · 855 unmodified lines
856 },856 },
857 857
858 _triggerEvent: function(eventType, options) {858 _triggerEvent: function(eventType, options) {
859 // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it859 // NOTE: `event` is a native browser thing, so let's avoid conflicting with it
860 var evt, key;860 var evt, key;
861 861
862 if (!this._hasDocument) return;862 if (!this._hasDocument) return;
assets/javascripts/views/content/content.coffee+3−0
@@ -153,6 +153,9 @@ class app.views.Content extends app.View · 152 unmodified lines
153 return153 return
154 154
155 afterRoute: (route, context) =>155 afterRoute: (route, context) =>
156 if route != 'entry' and route != 'type'
157 resetFavicon()
158
156 switch route159 switch route
157 when 'root'160 when 'root'
158 @show @rootPage161 @show @rootPage
assets/javascripts/views/content/entry_page.coffee+2−1
@@ -40,6 +40,7 @@ class app.views.EntryPage extends app.View · 39 unmodified lines
40 if app.disabledDocs.findBy 'slug', @entry.doc.slug40 if app.disabledDocs.findBy 'slug', @entry.doc.slug
41 @hiddenView = new app.views.HiddenPage @el, @entry41 @hiddenView = new app.views.HiddenPage @el, @entry
42 42
43 setFaviconForDoc(@entry.doc)
43 @delay @polyfillMathML44 @delay @polyfillMathML
44 @trigger 'loaded'45 @trigger 'loaded'
45 return46 return
@@ -123,7 +124,7 @@ class app.views.EntryPage extends app.View · 77 unmodified lines
123 @render @tmpl('pageLoadError')124 @render @tmpl('pageLoadError')
124 @resetClass()125 @resetClass()
125 @addClass @constructor.errorClass126 @addClass @constructor.errorClass
126 app.appCache?.update()127 app.serviceWorker?.update()
127 return128 return
128 129
129 cache: ->130 cache: ->
assets/javascripts/views/content/offline_page.coffee+1−0
@@ -57,6 +57,7 @@ class app.views.OfflinePage extends app.View · 56 unmodified lines
57 doc[action](@onInstallSuccess.bind(@, doc), @onInstallError.bind(@, doc), @onInstallProgress.bind(@, doc))57 doc[action](@onInstallSuccess.bind(@, doc), @onInstallError.bind(@, doc), @onInstallProgress.bind(@, doc))
58 el.parentNode.innerHTML = "#{el.textContent.replace(/e$/, '')}ing…"58 el.parentNode.innerHTML = "#{el.textContent.replace(/e$/, '')}ing…"
59 else if action = el.getAttribute('data-action-all')59 else if action = el.getAttribute('data-action-all')
60 return unless action isnt 'uninstall' or window.confirm('Uninstall all docs?')
60 app.db.migrate()61 app.db.migrate()
61 $.click(el) for el in @findAll("[data-action='#{action}']")62 $.click(el) for el in @findAll("[data-action='#{action}']")
62 return63 return
assets/javascripts/views/content/root_page.coffee+0−4
@@ -19,10 +19,6 @@ class app.views.RootPage extends app.View · 18 unmodified lines
19 else19 else
20 'intro'20 'intro'
21 21
22 # temporary
23 if location.host is 'devdocs.io' and location.protocol is 'http:'
24 tmpl = 'httpWarning'
25
26 @append @tmpl(tmpl)22 @append @tmpl(tmpl)
27 return23 return
28 24
assets/javascripts/views/content/settings_page.coffee+10−12
@@ -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,32 +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 css = $('link[rel="stylesheet"][data-alt]')
28 alt = css.getAttribute('data-alt')
29 css.setAttribute('data-alt', css.getAttribute('href'))
30 css.setAttribute('href', alt)
31 app.settings.set('dark', !!enable)26 app.settings.set('dark', !!enable)
32 app.appCache?.updateInBackground()
33 return27 return
34 28
35 toggleLayout: (layout, enable) ->29 toggleLayout: (layout, enable) ->
36 document.body.classList[if enable then 'add' else 'remove'](layout) unless layout is SIDEBAR_HIDDEN_LAYOUT
37 document.body.classList[if $.overlayScrollbarsEnabled() then 'add' else 'remove']('_overlay-scrollbars')
38 app.settings.setLayout(layout, enable)30 app.settings.setLayout(layout, enable)
39 app.appCache?.updateInBackground()
40 return31 return
41 32
42 toggleSmoothScroll: (enable) ->33 toggleSmoothScroll: (enable) ->
43 app.settings.set('fastScroll', !enable)34 app.settings.set('fastScroll', !enable)
44 return35 return
45 36
37 toggleAnalyticsConsent: (enable) ->
38 app.settings.set('analyticsConsent', if enable then '1' else '0')
39 resetAnalytics() unless enable
40 return
41
46 toggle: (name, enable) ->42 toggle: (name, enable) ->
47 app.settings.set(name, enable)43 app.settings.set(name, enable)
48 return44 return
@@ -86,6 +82,8 @@ class app.views.SettingsPage extends app.View · 37 unmodified lines
86 @toggleSmoothScroll input.checked82 @toggleSmoothScroll input.checked
87 when 'import'83 when 'import'
88 @import input.files[0], input84 @import input.files[0], input
85 when 'analyticsConsent'
86 @toggleAnalyticsConsent input.checked
89 else87 else
90 @toggle input.name, input.checked88 @toggle input.name, input.checked
91 return89 return
assets/javascripts/views/content/type_page.coffee+1−0
@@ -9,6 +9,7 @@ class app.views.TypePage extends app.View · 8 unmodified lines
9 9
10 render: (@type) ->10 render: (@type) ->
11 @html @tmpl('typePage', @type)11 @html @tmpl('typePage', @type)
12 setFaviconForDoc(@type.doc)
12 return13 return
13 14
14 getTitle: ->15 getTitle: ->
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 window.location = '/'80 when 'reboot' then app.reboot()
81 when 'hard-reload' then app.reload()81 when 'hard-reload' then app.reload()
82 when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?')82 when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?')
83 when 'accept-analytics' then Cookies.set('analyticsConsent', '1', expires: 1e8) && app.reboot()
84 when 'decline-analytics' then Cookies.set('analyticsConsent', '0', expires: 1e8) && app.reboot()
83 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+13−0
@@ -17,6 +17,7 @@ class app.views.Search extends app.View · 16 unmodified lines
17 typing: 'focus'17 typing: 'focus'
18 altG: 'google'18 altG: 'google'
19 altS: 'stackoverflow'19 altS: 'stackoverflow'
20 altD: 'duckduckgo'
20 21
21 @routes:22 @routes:
22 after: 'afterRoute'23 after: 'afterRoute'
@@ -29,6 +30,9 @@ class app.views.Search extends app.View · 6 unmodified lines
29 .on 'results', @onResults30 .on 'results', @onResults
30 .on 'end', @onEnd31 .on 'end', @onEnd
31 32
33 @scope
34 .on 'change', @onScopeChange
35
32 app.on 'ready', @onReady36 app.on 'ready', @onReady
33 $.on window, 'hashchange', @searchUrl37 $.on window, 'hashchange', @searchUrl
34 $.on window, 'focus', @onWindowFocus38 $.on window, 'focus', @onWindowFocus
@@ -113,6 +117,10 @@ class app.views.Search extends app.View · 78 unmodified lines
113 @externalSearch "https://stackoverflow.com/search?q="117 @externalSearch "https://stackoverflow.com/search?q="
114 return118 return
115 119
120 duckduckgo: =>
121 @externalSearch "https://duckduckgo.com/?t=devdocs&q="
122 return
123
116 onResults: (results) =>124 onResults: (results) =>
117 @hasResults = true if results.length125 @hasResults = true if results.length
118 @trigger 'results', results, @flags126 @trigger 'results', results, @flags
@@ -133,6 +141,11 @@ class app.views.Search extends app.View · 14 unmodified lines
133 $.stopEvent(event)141 $.stopEvent(event)
134 return142 return
135 143
144 onScopeChange: =>
145 @value = ''
146 @onInput()
147 return
148
136 afterRoute: (name, context) =>149 afterRoute: (name, context) =>
137 return if app.shortcuts.eventInProgress?.name is 'escape'150 return if app.shortcuts.eventInProgress?.name is 'escape'
138 @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-dark.css.scss+0−104
@@ -1,104 +0,0 @@
1//= depend_on docs-1.png
2//= depend_on docs-1@2x.png
3//= depend_on docs-2.png
4//= depend_on docs-2@2x.png
5
6/*!
7 * Copyright 2013-2018 Thibaut Courouble and other contributors
8 *
9 * This source code is licensed under the terms of the Mozilla
10 * Public License, v. 2.0, a copy of which may be obtained at:
11 * http://mozilla.org/MPL/2.0/
12 */
13
14@import 'global/variables-dark',
15 'global/mixins',
16 'global/icons',
17 'global/classes',
18 'global/base';
19
20@import 'components/app',
21 'components/header',
22 'components/notif',
23 'components/sidebar',
24 'components/settings',
25 'components/content',
26 'components/page',
27 'components/fail',
28 'components/path',
29 'components/notice',
30 'components/prism',
31 'components/mobile';
32
33@import 'pages/simple',
34 'pages/angular',
35 'pages/angularjs',
36 'pages/apache',
37 'pages/async',
38 'pages/bootstrap',
39 'pages/c',
40 'pages/cakephp',
41 'pages/clojure',
42 'pages/codeception',
43 'pages/coffeescript',
44 'pages/cordova',
45 'pages/crystal',
46 'pages/d',
47 'pages/d3',
48 'pages/dojo',
49 'pages/drupal',
50 'pages/elixir',
51 'pages/ember',
52 'pages/erlang',
53 'pages/express',
54 'pages/git',
55 'pages/github',
56 'pages/go',
57 'pages/haskell',
58 'pages/jekyll',
59 'pages/jquery',
60 'pages/julia',
61 'pages/knockout',
62 'pages/kotlin',
63 'pages/laravel',
64 'pages/liquid',
65 'pages/love',
66 'pages/lua',
67 'pages/mdn',
68 'pages/meteor',
69 'pages/modernizr',
70 'pages/moment',
71 'pages/nginx',
72 'pages/node',
73 'pages/npm',
74 'pages/openjdk',
75 'pages/perl',
76 'pages/phalcon',
77 'pages/phaser',
78 'pages/php',
79 'pages/phpunit',
80 'pages/postgres',
81 'pages/pug',
82 'pages/python',
83 'pages/ramda',
84 'pages/rdoc',
85 'pages/react_native',
86 'pages/redis',
87 'pages/rethinkdb',
88 'pages/rfc',
89 'pages/rubydoc',
90 'pages/rust',
91 'pages/sinon',
92 'pages/socketio',
93 'pages/sphinx',
94 'pages/sphinx_simple',
95 'pages/sqlite',
96 'pages/support_tables',
97 'pages/tcl_tk',
98 'pages/tensorflow',
99 'pages/terraform',
100 'pages/underscore',
101 'pages/vue',
102 'pages/webpack',
103 'pages/yard',
104 'pages/yii';
assets/stylesheets/application.css.scss+14−6
@@ -1,17 +1,18 @@
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-2018 Thibaut Courouble and other contributors6 * Copyright 2013-2019 Thibaut Courouble and other contributors
8 *7 *
9 * This source code is licensed under the terms of the Mozilla8 * This source code is licensed under the terms of the Mozilla
10 * Public License, v. 2.0, a copy of which may be obtained at:9 * Public License, v. 2.0, a copy of which may be obtained at:
11 * http://mozilla.org/MPL/2.0/10 * http://mozilla.org/MPL/2.0/
12 */11 */
13 12
14@import 'global/variables',13@import 'global/variables-light',
14 'global/variables-dark',
15 'global/variables',
15 'global/mixins',16 'global/mixins',
16 'global/icons',17 'global/icons',
17 'global/classes',18 'global/classes',
@@ -35,6 +36,7 @@ · 17 unmodified lines
35 'pages/angularjs',36 'pages/angularjs',
36 'pages/apache',37 'pages/apache',
37 'pages/async',38 'pages/async',
39 'pages/bash',
38 'pages/bootstrap',40 'pages/bootstrap',
39 'pages/c',41 'pages/c',
40 'pages/cakephp',42 'pages/cakephp',
@@ -43,8 +45,10 @@ · 2 unmodified lines
43 'pages/coffeescript',45 'pages/coffeescript',
44 'pages/cordova',46 'pages/cordova',
45 'pages/crystal',47 'pages/crystal',
48 'pages/cypress',
46 'pages/d',49 'pages/d',
47 'pages/d3',50 'pages/d3',
51 'pages/dart',
48 'pages/dojo',52 'pages/dojo',
49 'pages/drupal',53 'pages/drupal',
50 'pages/elixir',54 'pages/elixir',
@@ -54,6 +58,7 @@ · 3 unmodified lines
54 'pages/git',58 'pages/git',
55 'pages/github',59 'pages/github',
56 'pages/go',60 'pages/go',
61 'pages/graphite',
57 'pages/haskell',62 'pages/haskell',
58 'pages/jekyll',63 'pages/jekyll',
59 'pages/jquery',64 'pages/jquery',
@@ -80,6 +85,7 @@ · 20 unmodified lines
80 'pages/postgres',85 'pages/postgres',
81 'pages/pug',86 'pages/pug',
82 'pages/python',87 'pages/python',
88 'pages/qt',
83 'pages/ramda',89 'pages/ramda',
84 'pages/rdoc',90 'pages/rdoc',
85 'pages/react_native',91 'pages/react_native',
@@ -88,6 +94,7 @@ · 2 unmodified lines
88 'pages/rfc',94 'pages/rfc',
89 'pages/rubydoc',95 'pages/rubydoc',
90 'pages/rust',96 'pages/rust',
97 'pages/scala',
91 'pages/sinon',98 'pages/sinon',
92 'pages/socketio',99 'pages/socketio',
93 'pages/sphinx',100 'pages/sphinx',
@@ -100,5 +107,6 @@ · 6 unmodified lines
100 'pages/underscore',107 'pages/underscore',
101 'pages/vue',108 'pages/vue',
102 'pages/webpack',109 'pages/webpack',
110 'pages/wordpress',
103 'pages/yard',111 'pages/yard',
104 'pages/yii';112 'pages/yii';
assets/stylesheets/components/_app.scss+5−5
@@ -1,6 +1,6 @@
1html._booting { background: $contentBackground; }1html._booting { background: var(--contentBackground); }
2body._max-width { background: none; }2body._max-width { background: none; }
3html._booting body._max-width { background: $documentBackground; }3html._booting body._max-width { background: var(--documentBackground); }
4 4
5._app {5._app {
6 position: relative;6 position: relative;
@@ -15,8 +15,8 @@ html._booting body._max-width { background: $documentBackground; } · 8 unmodified lines
15 15
16 ._max-width & {16 ._max-width & {
17 margin: 0 auto;17 margin: 0 auto;
18 max-width: $maxWidth;18 max-width: var(--maxWidth);
19 background: $contentBackground;19 background: var(--contentBackground);
20 box-shadow: 1px 0 $headerBorder, -1px 0 $headerBorder;20 box-shadow: 1px 0 var(--headerBorder), -1px 0 var(--headerBorder);
21 }21 }
22}22}
assets/stylesheets/components/_content.scss+28−32
@@ -4,13 +4,13 @@ · 3 unmodified lines
4 4
5._container {5._container {
6 position: relative;6 position: relative;
7 z-index: $contentZ;7 z-index: var(--contentZ);
8 height: 100%;8 height: 100%;
9 margin-left: $sidebarWidth;9 margin-left: var(--sidebarWidth);
10 pointer-events: none;10 pointer-events: none;
11 @extend %border-box;11 @extend %border-box;
12 12
13 @media #{$mediumScreen} { margin-left: $sidebarMediumWidth; }13 @include mobile { margin-left: var(--sidebarMediumWidth); }
14 14
15 ._sidebar-hidden & { margin-left: 0; }15 ._sidebar-hidden & { margin-left: 0; }
16 body:not(._native-scrollbars) & { -webkit-margin-end: -1px; }16 body:not(._native-scrollbars) & { -webkit-margin-end: -1px; }
@@ -30,7 +30,7 @@ · 13 unmodified lines
30 ._sidebar-hidden &:before {30 ._sidebar-hidden &:before {
31 content: '';31 content: '';
32 display: block;32 display: block;
33 margin-top: $headerHeight;33 margin-top: var(--headerHeight);
34 }34 }
35 35
36 ._overlay-scrollbars & { padding-left: .625rem; }36 ._overlay-scrollbars & { padding-left: .625rem; }
@@ -54,7 +54,7 @@ · 17 unmodified lines
54 font-size: 4rem;54 font-size: 4rem;
55 font-weight: 300;55 font-weight: 300;
56 letter-spacing: -.125rem;56 letter-spacing: -.125rem;
57 color: $loadingText;57 color: var(--loadingText);
58 text-align: center;58 text-align: center;
59 cursor: default;59 cursor: default;
60}60}
@@ -68,7 +68,7 @@ · 7 unmodified lines
68//68//
69 69
70._splash-title {70._splash-title {
71 color: $splashText;71 color: var(--splashText);
72 @extend %loading, %user-select-none;72 @extend %loading, %user-select-none;
73}73}
74 74
@@ -88,7 +88,7 @@ · 13 unmodified lines
88 min-height: calc(100vh - 2.375rem);88 min-height: calc(100vh - 2.375rem);
89 89
90 ._sidebar-hidden & {90 ._sidebar-hidden & {
91 min-height: calc(100vh - 2.375rem - #{$headerHeight});91 min-height: calc(100vh - 2.375rem - var(--headerHeight));
92 }92 }
93}93}
94 94
@@ -140,12 +140,12 @@ · 45 unmodified lines
140 140
141._error-text {141._error-text {
142 margin: 0 0 1rem;142 margin: 0 0 1rem;
143 color: $textColorLight;143 color: var(--textColorLight);
144}144}
145 145
146._error-links {146._error-links {
147 font-size: 1rem;147 font-size: 1rem;
148 font-weight: $boldFontWeight;148 font-weight: var(--boldFontWeight);
149}149}
150 150
151._error-link { padding: 0 .5rem; }151._error-link { padding: 0 .5rem; }
@@ -167,7 +167,7 @@ · 15 unmodified lines
167 height: 1px;167 height: 1px;
168 margin-top: .25rem;168 margin-top: .25rem;
169 margin-left: 1rem;169 margin-left: 1rem;
170 background: $boxBorderLight;170 background: var(--boxBorderLight);
171 }171 }
172}172}
173 173
@@ -197,7 +197,7 @@ · 23 unmodified lines
197._toc-title {197._toc-title {
198 margin: 0 0 .5rem;198 margin: 0 0 .5rem;
199 font-size: inherit;199 font-size: inherit;
200 font-weight: $boldFontWeight;200 font-weight: var(--boldFontWeight);
201}201}
202 202
203._toc-list {203._toc-list {
@@ -252,7 +252,7 @@ · 48 unmodified lines
252._docs-size {252._docs-size {
253 text-align: right;253 text-align: right;
254 254
255 > small { color: $textColorLight; }255 > small { color: var(--textColorLight); }
256}256}
257 257
258._docs-tools {258._docs-tools {
@@ -285,7 +285,7 @@ · 26 unmodified lines
285 padding: 0 .75rem;285 padding: 0 .75rem;
286 }286 }
287 ._btn-link:not(._show) { display: none; }287 ._btn-link:not(._show) { display: none; }
288 ._btn-link._show ~ ._btn-link._show { border-left: 1px solid $boxBorder; }288 ._btn-link._show ~ ._btn-link._show { border-left: 1px solid var(--boxBorder); }
289}289}
290 290
291//291//
@@ -297,7 +297,7 @@ · 5 unmodified lines
297 position: relative;297 position: relative;
298 padding-left: 10em;298 padding-left: 10em;
299 font-size: .8125rem;299 font-size: .8125rem;
300 color: $textColorLight;300 color: var(--textColorLight);
301 301
302 + ._news-row { margin-top: 1em; }302 + ._news-row { margin-top: 1em; }
303 }303 }
@@ -305,7 +305,7 @@ · 1 unmodified line
305 ._news-title {305 ._news-title {
306 display: block;306 display: block;
307 font-size: .875rem;307 font-size: .875rem;
308 color: $textColor;308 color: var(--textColor);
309 }309 }
310 310
311 ._news-date {311 ._news-date {
@@ -372,12 +372,12 @@ · 60 unmodified lines
372// Utilities372// Utilities
373//373//
374 374
375._bold { font-weight: $boldFontWeight; }375._bold { font-weight: var(--boldFontWeight); }
376._note { @extend %note; }376._note { @extend %note; }
377._note-green { @extend %note-green; }377._note-green { @extend %note-green; }
378._label { @extend %label; }378._label { @extend %label; }
379._code { @extend %code; }379._code { @extend %code; }
380._highlight { background: $highlightBackground !important; }380._highlight { background: var(--highlightBackground) !important; }
381 381
382._table { width: 100%; }382._table { width: 100%; }
383._mobile ._table { overflow-x: auto; }383._mobile ._table { overflow-x: auto; }
@@ -396,12 +396,9 @@ · 12 unmodified lines
396 pre:hover > & { display: block; }396 pre:hover > & { display: block; }
397 &:hover { opacity: 1; }397 &:hover { opacity: 1; }
398 398
399 > svg { @extend %svg-icon; }399 > svg {
400 400 @extend %svg-icon;
401 @if $style == 'dark' {401 fill: var(--absolute);
402 > svg { fill: white; }
403 } @else {
404 > svg { fill: black; }
405 }402 }
406 403
407 &._pre-clip-success > svg,404 &._pre-clip-success > svg,
@@ -419,15 +416,13 @@ · 11 unmodified lines
419 line-height: normal;416 line-height: normal;
420 white-space: nowrap;417 white-space: nowrap;
421 padding: .375rem .675rem;418 padding: .375rem .675rem;
422 background-image: linear-gradient(lighten($boxBackground, 4%), darken($boxBackground, 2%));419 background-color: var(--boxBackground);
423 border: 1px solid $boxBorder;420 border: 1px solid var(--boxBorder);
424 border-radius: 3px;421 border-radius: 3px;
425 box-shadow: 0 1px rgba($boxBorder, .08);
426 cursor: pointer;422 cursor: pointer;
427 423
428 &:active {424 &:active {
429 background-color: $boxBackground;425 box-shadow: inset 0 1px 1px rgba(black, .05), inset 0 1px 4px var(--boxBorder);
430 box-shadow: inset 0 1px 1px rgba(black, .05), inset 0 1px 4px $boxBorder;
431 }426 }
432}427}
433 428
@@ -447,22 +442,23 @@ · 13 unmodified lines
447 442
448._btn-link {443._btn-link {
449 line-height: inherit;444 line-height: inherit;
450 color: $linkColor;445 color: var(--linkColor);
451 text-decoration: $linkTextDecoration;446 text-decoration: var(--linkTextDecoration);
452 447
453 &:hover {448 &:hover {
454 color: $linkColorHover;449 color: var(--linkColorHover);
455 text-decoration: underline;450 text-decoration: underline;
456 }451 }
457}452}
458 453
459._reset-btn,454._reset-btn,
460._reset-btn:hover {455._reset-btn:hover {
461 color: $textColorRed;456 color: var(--textColorRed);
462}457}
463 458
464._github-btn {459._github-btn {
465 display: inline-block;460 display: inline-block;
466 vertical-align: text-top;461 vertical-align: text-top;
467 margin-left: .25rem;462 margin-left: .25rem;
463 background: inherit;
468}464}
assets/stylesheets/components/_fail.scss+2−4
@@ -1,4 +1,4 @@
1._fail {1._fail { // Don't use CSS variables, in case the browser doesn't support them.
2 display: block;2 display: block;
3 position: relative;3 position: relative;
4 top: 1.5rem;4 top: 1.5rem;
@@ -23,7 +23,7 @@ · 18 unmodified lines
23._fail-title {23._fail-title {
24 margin: 0 0 1rem;24 margin: 0 0 1rem;
25 font-size: 1rem;25 font-size: 1rem;
26 font-weight: $boldFontWeight;26 font-weight: bold;
27}27}
28 28
29._fail-text, ._fail-list {29._fail-text, ._fail-list {
@@ -32,5 +32,3 @@ · 2 unmodified lines
32}32}
33 33
34._fail-text:last-child { margin: 0; }34._fail-text:last-child { margin: 0; }
35
36._fail-link { float: right; }
assets/stylesheets/components/_header.scss+26−35
@@ -4,20 +4,20 @@ · 3 unmodified lines
4 4
5._header {5._header {
6 position: absolute;6 position: absolute;
7 z-index: $headerZ;7 z-index: var(--headerZ);
8 top: 0;8 top: 0;
9 left: 0;9 left: 0;
10 display: -ms-flexbox;10 display: -ms-flexbox;
11 display: flex;11 display: flex;
12 width: $sidebarWidth;12 width: var(--sidebarWidth);
13 height: $headerHeight;13 height: var(--headerHeight);
14 background: $headerBackground;14 background: var(--headerBackground);
15 border-bottom: 1px solid $headerBorder;15 border-bottom: 1px solid var(--headerBorder);
16 border-right: 1px solid $headerBorder;16 border-right: 1px solid var(--headerBorder);
17 @extend %border-box;17 @extend %border-box;
18 @extend %user-select-none;18 @extend %user-select-none;
19 19
20 @media #{$mediumScreen} { width: $sidebarMediumWidth; }20 @include mobile { width: var(--sidebarMediumWidth); }
21}21}
22 22
23._header-left {23._header-left {
@@ -35,7 +35,7 @@ · 11 unmodified lines
35 flex: 0 0 auto;35 flex: 0 0 auto;
36 width: 2.25rem;36 width: 2.25rem;
37 height: 100%;37 height: 100%;
38 color: $textColorLight;38 color: var(--textColorLight);
39 text-align: center;39 text-align: center;
40 40
41 &[hidden] { display: none; }41 &[hidden] { display: none; }
@@ -67,8 +67,8 @@ · 25 unmodified lines
67 word-wrap: normal;67 word-wrap: normal;
68 overflow-wrap: normal;68 overflow-wrap: normal;
69 font-size: .875rem;69 font-size: .875rem;
70 background: $contentBackground;70 background: var(--contentBackground);
71 border: 1px solid $headerBorder;71 border: 1px solid var(--headerBorder);
72 border-radius: 3px;72 border-radius: 3px;
73 box-shadow: -1px 1px 1px rgba(black, .05);73 box-shadow: -1px 1px 1px rgba(black, .05);
74 transition: all 0ms cubic-bezier(0.23, 1, 0.32, 1) 1ms;74 transition: all 0ms cubic-bezier(0.23, 1, 0.32, 1) 1ms;
@@ -98,10 +98,10 @@ · 23 unmodified lines
98 margin: 0;98 margin: 0;
99 line-height: 1.5rem;99 line-height: 1.5rem;
100 font-size: 1rem;100 font-size: 1rem;
101 font-weight: $boldFontWeight;101 font-weight: var(--boldFontWeight);
102 letter-spacing: -.5px;102 letter-spacing: -.5px;
103 background: $sidebarBackground;103 background: var(--sidebarBackground);
104 border-bottom: 1px solid $sidebarBorder;104 border-bottom: 1px solid var(--sidebarBorder);
105 border-radius: 2px 2px 0 0;105 border-radius: 2px 2px 0 0;
106}106}
107 107
@@ -109,7 +109,7 @@ · 1 unmodified line
109._menu-title-link:hover {109._menu-title-link:hover {
110 display: block;110 display: block;
111 padding: .5rem 1rem;111 padding: .5rem 1rem;
112 color: $focusText;112 color: var(--focusText);
113 text-decoration: none;113 text-decoration: none;
114}114}
115 115
@@ -121,9 +121,9 @@ · 5 unmodified lines
121 text-decoration: none;121 text-decoration: none;
122 122
123 &:hover {123 &:hover {
124 color: $focusText;124 color: var(--focusText);
125 text-decoration: none;125 text-decoration: none;
126 background: $sidebarBackground;126 background: var(--sidebarBackground);
127 }127 }
128 128
129 &:last-child { border-radius: 0 0 2px 2px; }129 &:last-child { border-radius: 0 0 2px 2px; }
@@ -151,11 +151,7 @@ · 21 unmodified lines
151 opacity: .42;151 opacity: .42;
152 @extend %svg-icon;152 @extend %svg-icon;
153 153
154 @if $style == 'dark' {154 fill: var(--absolute);
155 fill: white;
156 } @else {
157 fill: black;
158 }
159 }155 }
160}156}
161 157
@@ -166,21 +162,19 @@ · 4 unmodified lines
166 height: 100%;162 height: 100%;
167 padding: 0 .75rem 1px 1.75rem;163 padding: 0 .75rem 1px 1.75rem;
168 font-size: .875rem;164 font-size: .875rem;
169 background: $contentBackground;165 background: var(--contentBackground);
170 border: 1px solid;166 border: 1px solid;
171 border-color: darken($headerBorder, 2%);167 border-color: var(--searchBorder);
172 border-radius: 3px;168 border-radius: 3px;
173 169
174 &:focus {170 &:focus {
175 outline: 0;171 outline: 0;
176 @if $inputFocusBorder {172 border-color: var(--inputFocusBorder);
177 border-color: $inputFocusBorder;173 box-shadow: 0 0 1px var(--inputFocusBorder);
178 box-shadow: 0 0 1px $inputFocusBorder;
179 }
180 }174 }
181 175
182 &[disabled] {176 &[disabled] {
183 background: $sidebarBackground;177 background: var(--sidebarBackground);
184 cursor: not-allowed;178 cursor: not-allowed;
185 }179 }
186}180}
@@ -202,13 +196,9 @@ · 15 unmodified lines
202 top: .5rem;196 top: .5rem;
203 left: .375rem;197 left: .375rem;
204 @extend %svg-icon;198 @extend %svg-icon;
199 fill: var(--absolute);
205 }200 }
206 201
207 @if $style == 'dark' {
208 > svg { fill: white; }
209 } @else {
210 > svg { fill: black; }
211 }
212 ._search-active > & { display: block; }202 ._search-active > & { display: block; }
213}203}
214 204
@@ -222,8 +212,9 @@ · 7 unmodified lines
222 line-height: 1.25rem;212 line-height: 1.25rem;
223 max-width: 50%;213 max-width: 50%;
224 font-size: .8125rem;214 font-size: .8125rem;
225 color: $textColorLight;215 color: var(--textColorLight);
226 background: darken($headerBackground, 5%);216 background: var(--searchTagBackground);
227 border-radius: 2px;217 border-radius: 2px;
218 cursor: pointer;
228 @extend %truncate-text;219 @extend %truncate-text;
229}220}
assets/stylesheets/components/_mobile.scss+2−2
@@ -4,7 +4,7 @@ · 3 unmodified lines
4 4
5._mobile {5._mobile {
6 font-size: 100%;6 font-size: 100%;
7 background: $contentBackground;7 background: var(--contentBackground);
8 8
9 ._hide-on-mobile { display: none; }9 ._hide-on-mobile { display: none; }
10 10
@@ -18,7 +18,7 @@ · 7 unmodified lines
18 18
19 ._container {19 ._container {
20 margin: 0;20 margin: 0;
21 padding-top: $headerHeight;21 padding-top: var(--headerHeight);
22 }22 }
23 23
24 ._content {24 ._content {
assets/stylesheets/components/_notice.scss+5−5
@@ -1,15 +1,15 @@
1._notice {1._notice {
2 position: absolute;2 position: absolute;
3 z-index: $noticeZ;3 z-index: var(--noticeZ);
4 bottom: 0;4 bottom: 0;
5 left: $sidebarWidth;5 left: var(--sidebarWidth);
6 right: 0;6 right: 0;
7 height: 2.5rem;7 height: 2.5rem;
8 padding: 0 1.25rem;8 padding: 0 1.25rem;
9 background: $noticeBackground;9 background: var(--noticeBackground);
10 box-shadow: inset 0 1px $noticeBorder;10 box-shadow: inset 0 1px var(--noticeBorder);
11 11
12 @media #{$mediumScreen} { left: $sidebarMediumWidth; }12 @include mobile { left: var(--sidebarMediumWidth); }
13 13
14 ._sidebar-hidden & { left: 0; }14 ._sidebar-hidden & { left: 0; }
15 15
assets/stylesheets/components/_notif.scss+16−12
@@ -7,9 +7,9 @@ · 6 unmodified lines
7 max-width: 90%;7 max-width: 90%;
8 padding: .625rem 1rem;8 padding: .625rem 1rem;
9 font-size: .75rem;9 font-size: .75rem;
10 color: $notifColor;10 color: var(--notifColor);
11 background: $notifBackground;11 background: var(--notifBackground);
12 border: $notifBorder;12 border: var(--notifBorder);
13 border-radius: .25rem;13 border-radius: .25rem;
14 transition: opacity .2s;14 transition: opacity .2s;
15 opacity: 0;15 opacity: 0;
@@ -30,7 +30,7 @@ · 14 unmodified lines
30 30
31._notif-info {31._notif-info {
32 float: right;32 float: right;
33 color: $notifColorLight;33 color: var(--notifColorLight);
34}34}
35 35
36._notif-link,36._notif-link,
@@ -69,13 +69,13 @@ · 32 unmodified lines
69 &::-webkit-scrollbar { width: 10px !important; }69 &::-webkit-scrollbar { width: 10px !important; }
70 70
71 &::-webkit-scrollbar-track {71 &::-webkit-scrollbar-track {
72 background: $notifBackground !important;72 background: var(--notifBackground) !important;
73 border: 0 !important;73 border: 0 !important;
74 border-radius: 5px !important;74 border-radius: 5px !important;
75 }75 }
76 76
77 &::-webkit-scrollbar-thumb {77 &::-webkit-scrollbar-thumb {
78 border: 3px solid $notifBackground !important;78 border: 3px solid var(--notifBackground) !important;
79 79
80 &:hover, &:active { border-width: 2px !important; }80 &:hover, &:active { border-width: 2px !important; }
81 }81 }
@@ -90,7 +90,7 @@ · 8 unmodified lines
90 > ._news-row {90 > ._news-row {
91 line-height: 1.125rem;91 line-height: 1.125rem;
92 font-size: .6875rem;92 font-size: .6875rem;
93 color: $notifColorLight;93 color: var(--notifColorLight);
94 margin-bottom: .25rem;94 margin-bottom: .25rem;
95 95
96 + ._news-row { margin-top: .625rem; }96 + ._news-row { margin-top: .625rem; }
@@ -107,7 +107,7 @@ · 10 unmodified lines
107 ._news-date {107 ._news-date {
108 float: right;108 float: right;
109 margin-left: 1rem;109 margin-left: 1rem;
110 font-weight: $boldFontWeight;110 font-weight: var(--boldFontWeight);
111 }111 }
112 112
113 code {113 code {
@@ -128,9 +128,13 @@ · 14 unmodified lines
128}128}
129 129
130._notif-tip {130._notif-tip {
131 color: $textColor;131 color: var(--textColor);
132 background: $tipBackground;132 background: var(--tipBackground);
133 border: $tipBorder;133 border: var(--tipBorder);
134 134
135 ._notif-info { color: $textColorLight; }135 ._notif-info { color: var(--textColorLight); }
136}
137
138._notif-right {
139 float: right;
136}140}
assets/stylesheets/components/_page.scss+4−4
@@ -23,7 +23,7 @@ · 22 unmodified lines
23 max-width: 100%;23 max-width: 100%;
24 margin-bottom: 1em;24 margin-bottom: 1em;
25 padding: 1px;25 padding: 1px;
26 border: 1px dotted $boxBorder;26 border: 1px dotted var(--boxBorder);
27 border-radius: 3px;27 border-radius: 3px;
28 @extend %border-box;28 @extend %border-box;
29 }29 }
@@ -50,7 +50,7 @@ · 20 unmodified lines
50 display: inline-block;50 display: inline-block;
51 vertical-align: top;51 vertical-align: top;
52 padding: 0 .5rem;52 padding: 0 .5rem;
53 background: $contentBackground;53 background: var(--contentBackground);
54 @extend %internal-link;54 @extend %internal-link;
55 55
56 & + & { margin-left: .75rem; }56 & + & { margin-left: .75rem; }
@@ -66,7 +66,7 @@ · 9 unmodified lines
66 clear: both;66 clear: both;
67 margin: 2rem 0 1.5rem;67 margin: 2rem 0 1.5rem;
68 font-size: .75rem;68 font-size: .75rem;
69 color: $textColorLight;69 color: var(--textColorLight);
70 text-align: center;70 text-align: center;
71 -webkit-font-smoothing: subpixel-antialiased;71 -webkit-font-smoothing: subpixel-antialiased;
72 72
@@ -78,7 +78,7 @@ · 5 unmodified lines
78 display: inline-block;78 display: inline-block;
79 margin: 0;79 margin: 0;
80 padding: .25rem .75rem;80 padding: .25rem .75rem;
81 background: $labelBackground;81 background: var(--labelBackground);
82 border-radius: 3px;82 border-radius: 3px;
83}83}
84 84
assets/stylesheets/components/_path.scss+6−6
@@ -1,17 +1,17 @@
1._path {1._path {
2 position: absolute;2 position: absolute;
3 z-index: $noticeZ - 1;3 z-index: var(--headerZ);
4 bottom: 0;4 bottom: 0;
5 left: $sidebarWidth;5 left: var(--sidebarWidth);
6 right: 0;6 right: 0;
7 height: 2rem;7 height: 2rem;
8 line-height: 2rem;8 line-height: 2rem;
9 padding: 0 .375rem;9 padding: 0 .375rem;
10 font-size: .875rem;10 font-size: .875rem;
11 background: $pathBackground;11 background: var(--pathBackground);
12 box-shadow: inset 0 1px $pathBorder;12 box-shadow: inset 0 1px var(--pathBorder);
13 13
14 @media #{$mediumScreen} { left: $sidebarMediumWidth; }14 @include mobile { left: var(--sidebarMediumWidth); }
15 15
16 ._sidebar-hidden & { left: 0; }16 ._sidebar-hidden & { left: 0; }
17 17
@@ -24,7 +24,7 @@ · 6 unmodified lines
24 display: inline-block;24 display: inline-block;
25 vertical-align: top;25 vertical-align: top;
26 padding: 0 .375rem;26 padding: 0 .375rem;
27 color: $textColor;27 color: var(--textColor);
28 text-decoration: none;28 text-decoration: none;
29 29
30 &:first-child:before {30 &:first-child:before {
assets/stylesheets/components/_prism.scss+25−32
@@ -1,9 +1,26 @@
1html {
2 --prismValue: #905;
3 --prismText: #5e8e01;
4 --prismOperator: #a67f59;
5 --prismKeyword: #0070a3;
6 --prismFunction: #dd4a68;
7 --prismVariable: #e90;
8}
9html._theme-dark {
10 --prismValue: #eb8160;
11 --prismText: #ddcf88;
12 --prismOperator: #b1c676;
13 --prismKeyword: #91b3ed;
14 --prismFunction: #c79e6b;
15 --prismVariable: #e9c062;
16}
17
1.token.comment,18.token.comment,
2.token.prolog,19.token.prolog,
3.token.doctype,20.token.doctype,
4.token.cdata,21.token.cdata,
5.token.punctuation {22.token.punctuation {
6 color: $textColorLight;23 color: var(--textColorLight);
7}24}
8 25
9.namespace {26.namespace {
@@ -17,11 +34,7 @@ · 7 unmodified lines
17.token.constant,34.token.constant,
18.token.symbol,35.token.symbol,
19.token.deleted {36.token.deleted {
20 @if $style == 'dark' {37 color: var(--prismValue);
21 color: #eb8160;
22 } @else {
23 color: #905;
24 }
25}38}
26 39
27.token.selector,40.token.selector,
@@ -30,11 +43,7 @@ · 2 unmodified lines
30.token.char,43.token.char,
31.token.builtin,44.token.builtin,
32.token.inserted {45.token.inserted {
33 @if $style == 'dark' {46 color: var(--prismText);
34 color: #ddcf88;
35 } @else {
36 color: #5e8e01;
37 }
38}47}
39 48
40.token.operator,49.token.operator,
@@ -42,44 +51,28 @@ · 1 unmodified line
42.token.url,51.token.url,
43.language-css .token.string,52.language-css .token.string,
44.style .token.string {53.style .token.string {
45 @if $style == 'dark' {54 color: var(--prismOperator);
46 color: #b1c676;
47 } @else {
48 color: #a67f59;
49 }
50}55}
51 56
52.token.atrule,57.token.atrule,
53.token.attr-value,58.token.attr-value,
54.token.keyword {59.token.keyword {
55 @if $style == 'dark' {60 color: var(--prismKeyword);
56 color: #91b3ed;
57 } @else {
58 color: #0070a3;
59 }
60}61}
61 62
62.token.function {63.token.function {
63 @if $style == 'dark' {64 color: var(--prismFunction);
64 color: #c79e6b;
65 } @else {
66 color: #dd4a68;
67 }
68}65}
69 66
70.token.regex,67.token.regex,
71.token.important,68.token.important,
72.token.variable {69.token.variable {
73 @if $style == 'dark' {70 color: var(--prismVariable);
74 color: #e9c062;
75 } @else {
76 color: #e90;
77 }
78}71}
79 72
80.token.important,73.token.important,
81.token.bold {74.token.bold {
82 font-weight: $boldFontWeight;75 font-weight: var(--boldFontWeight);
83}76}
84 77
85.token.italic {78.token.italic {
assets/stylesheets/components/_settings.scss+12−12
@@ -7,15 +7,15 @@ · 6 unmodified lines
7 position: absolute;7 position: absolute;
8 top: 0;8 top: 0;
9 bottom: 0;9 bottom: 0;
10 z-index: $headerZ;10 z-index: var(--headerZ);
11 11
12 &._in { display: block; }12 &._in { display: block; }
13 13
14 > ._header { justify-content: space-between; }14 > ._header { justify-content: space-between; }
15 15
16 &._dirty > ._header {16 &._dirty > ._header {
17 background: $noteGreenBackground;17 background: var(--noteGreenBackground);
18 border-color: $noteGreenBorder;18 border-color: var(--noteGreenBorder);
19 }19 }
20}20}
21 21
@@ -37,7 +37,7 @@ · 15 unmodified lines
37 padding-right: .5rem;37 padding-right: .5rem;
38 line-height: inherit;38 line-height: inherit;
39 font-size: inherit;39 font-size: inherit;
40 font-weight: $boldFontWeight;40 font-weight: var(--boldFontWeight);
41 text-align: right;41 text-align: right;
42 @extend %border-box;42 @extend %border-box;
43}43}
@@ -52,7 +52,7 @@ · 8 unmodified lines
52 52
53 > small {53 > small {
54 display: block;54 display: block;
55 color: $textColorLight;55 color: var(--textColorLight);
56 margin-left: 1.75rem;56 margin-left: 1.75rem;
57 }57 }
58 58
@@ -62,7 +62,7 @@ · 3 unmodified lines
62 }62 }
63}63}
64 64
65@media (max-width: $maxWidth) {65@media (max-width: 80rem) {
66 ._setting-max-width { display: none; }66 ._setting-max-width { display: none; }
67}67}
68 68
@@ -80,7 +80,7 @@ · 11 unmodified lines
80 line-height: 1.5rem;80 line-height: 1.5rem;
81 padding: 0 .75rem;81 padding: 0 .75rem;
82 font-size: .875rem;82 font-size: .875rem;
83 font-weight: $boldFontWeight;83 font-weight: var(--boldFontWeight);
84 color: inherit;84 color: inherit;
85 text-align: left;85 text-align: left;
86 cursor: pointer;86 cursor: pointer;
@@ -119,12 +119,12 @@ · 32 unmodified lines
119 position: relative;119 position: relative;
120 vertical-align: top;120 vertical-align: top;
121 padding: 0 .75rem;121 padding: 0 .75rem;
122 line-height: $headerHeight;122 line-height: var(--headerHeight);
123 color: $textColorLight;123 color: var(--textColorLight);
124 124
125 &.active {125 &.active {
126 color: $textColor;126 color: var(--textColor);
127 font-weight: $boldFontWeight;127 font-weight: var(--boldFontWeight);
128 box-shadow: inset 0 -2px $linkColor;128 box-shadow: inset 0 -2px var(--linkColor);
129 }129 }
130}130}
assets/stylesheets/components/_sidebar.scss+37−39
@@ -4,14 +4,14 @@ · 3 unmodified lines
4 4
5._sidebar {5._sidebar {
6 position: absolute;6 position: absolute;
7 z-index: $sidebarZ;7 z-index: var(--sidebarZ);
8 top: 0;8 top: 0;
9 bottom: 0;9 bottom: 0;
10 left: 0;10 left: 0;
11 overflow-x: hidden;11 overflow-x: hidden;
12 overflow-y: scroll;12 overflow-y: scroll;
13 padding-top: $headerHeight;13 padding-top: var(--headerHeight);
14 background: $sidebarBackground;14 background: var(--sidebarBackground);
15 background-clip: content-box;15 background-clip: content-box;
16 -webkit-overflow-scrolling: touch;16 -webkit-overflow-scrolling: touch;
17 -ms-overflow-style: none; // IE 10 doesn't support pointer-events17 -ms-overflow-style: none; // IE 10 doesn't support pointer-events
@@ -22,13 +22,13 @@ · 4 unmodified lines
22 22
23 ._overlay-scrollbars & {23 ._overlay-scrollbars & {
24 padding-top: 0;24 padding-top: 0;
25 top: $headerHeight;25 top: var(--headerHeight);
26 }26 }
27 27
28 body:not(._native-scrollbars) & {28 body:not(._native-scrollbars) & {
29 &::-webkit-scrollbar { width: 10px; }29 &::-webkit-scrollbar { width: 10px; }
30 &::-webkit-scrollbar-track {30 &::-webkit-scrollbar-track {
31 background: $contentBackground;31 background: var(--contentBackground);
32 border: 0;32 border: 0;
33 }33 }
34 &::-webkit-scrollbar-thumb {34 &::-webkit-scrollbar-thumb {
@@ -52,10 +52,10 @@ · 17 unmodified lines
52 52
53._resizer {53._resizer {
54 position: absolute;54 position: absolute;
55 z-index: $sidebarZ + 1;55 z-index: var(--headerZ);
56 top: $headerHeight;56 top: var(--headerHeight);
57 bottom: $headerHeight;57 bottom: var(--headerHeight);
58 left: $sidebarWidth;58 left: var(--sidebarWidth);
59 margin-left: -2px;59 margin-left: -2px;
60 width: 3px;60 width: 3px;
61 cursor: col-resize;61 cursor: col-resize;
@@ -72,11 +72,11 @@ · 10 unmodified lines
72 margin: 0;72 margin: 0;
73 padding: 0;73 padding: 0;
74 list-style: none;74 list-style: none;
75 width: $sidebarWidth;75 width: var(--sidebarWidth);
76 box-shadow: inset -1px 0 $sidebarBorder;76 box-shadow: inset -1px 0 var(--sidebarBorder);
77 @extend %border-box;77 @extend %border-box;
78 78
79 @media #{$mediumScreen} { width: $sidebarMediumWidth; }79 @include mobile { width: var(--sidebarMediumWidth); }
80 80
81 ._sidebar > & { min-height: 100%; }81 ._sidebar > & { min-height: 100%; }
82 82
@@ -89,7 +89,7 @@ · 6 unmodified lines
89 padding: 0 .75rem 0 2.125rem;89 padding: 0 .75rem 0 2.125rem;
90 line-height: 2rem;90 line-height: 2rem;
91 font-size: .75rem;91 font-size: .75rem;
92 color: $textColorLight;92 color: var(--textColorLight);
93 text-transform: uppercase;93 text-transform: uppercase;
94 cursor: default;94 cursor: default;
95}95}
@@ -110,8 +110,8 @@ · 14 unmodified lines
110 line-height: 1.5rem;110 line-height: 1.5rem;
111 font-size: .875rem;111 font-size: .875rem;
112 cursor: default;112 cursor: default;
113 background: $sidebarBackground;113 background: var(--sidebarBackground);
114 box-shadow: inset -1px 0 $sidebarBorder;114 box-shadow: inset -1px 0 var(--sidebarBorder);
115 @extend %truncate-text;115 @extend %truncate-text;
116 116
117 &, &:hover {117 &, &:hover {
@@ -123,16 +123,16 @@ · 5 unmodified lines
123 &.focus:hover,123 &.focus:hover,
124 &.active,124 &.active,
125 &.active:hover {125 &.active:hover {
126 color: $focusText;126 color: var(--focusText);
127 background: $focusBackground;127 background: var(--focusBackground);
128 box-shadow: inset -1px 0 $focusBorder;128 box-shadow: inset -1px 0 var(--focusBorder);
129 }129 }
130 130
131 &.active,131 &.active,
132 &.active:hover {132 &.active:hover {
133 color: $selectionText;133 color: var(--selectionText);
134 background: $selectionBackground;134 background: var(--selectionBackground);
135 box-shadow: inset -1px 0 $selectionBorder;135 box-shadow: inset -1px 0 var(--selectionBorder);
136 }136 }
137 137
138 &:before {138 &:before {
@@ -160,7 +160,7 @@ · 21 unmodified lines
160}160}
161 161
162._list-count {162._list-count {
163 color: $textColorLighter;163 color: var(--textColorLighter);
164 pointer-events: none;164 pointer-events: none;
165 165
166 ._list-disabled:hover > & { display: none; }166 ._list-disabled:hover > & { display: none; }
@@ -168,7 +168,7 @@ · 1 unmodified line
168 168
169._list-enable {169._list-enable {
170 display: none;170 display: none;
171 color: $linkColor;171 color: var(--linkColor);
172 cursor: pointer;172 cursor: pointer;
173 173
174 &:hover { text-decoration: underline; }174 &:hover { text-decoration: underline; }
@@ -188,7 +188,7 @@ · 13 unmodified lines
188._list-disabled {188._list-disabled {
189 @extend %_list-dir;189 @extend %_list-dir;
190 190
191 &, &:hover { color: $textColorLight; }191 &, &:hover { color: var(--textColorLight); }
192 &:before { opacity: .7; }192 &:before { opacity: .7; }
193}193}
194 194
@@ -200,11 +200,9 @@ · 5 unmodified lines
200 width: 1rem;200 width: 1rem;
201 height: 1rem;201 height: 1rem;
202 cursor: pointer;202 cursor: pointer;
203 fill: black;203 fill: var(--absolute);
204 opacity: .4;204 opacity: .4;
205 205
206 @if $style == 'dark' { fill: white; }
207
208 &:hover { opacity: .65; }206 &:hover { opacity: .65; }
209 207
210 ._list-rdir > & {208 ._list-rdir > & {
@@ -239,11 +237,11 @@ · 28 unmodified lines
239//237//
240 238
241._list-pagelink {239._list-pagelink {
242 color: $linkColor;240 color: var(--linkColor);
243 cursor: pointer;241 cursor: pointer;
244 242
245 &:hover {243 &:hover {
246 color: $linkColorHover;244 color: var(--linkColorHover);
247 text-decoration: underline;245 text-decoration: underline;
248 }246 }
249}247}
@@ -275,9 +273,9 @@ · 25 unmodified lines
275 left: .75rem;273 left: .75rem;
276 width: .75rem;274 width: .75rem;
277 height: 1px;275 height: 1px;
278 background: rgba($selectionText, .9);276 background: var(--transparentSelectionText);
279 box-shadow: 0 -3px rgba($selectionText, .9), // top line277 box-shadow: 0 -3px var(--transparentSelectionText), // top line
280 0 3px rgba($selectionText, .9); // bottom line278 0 3px var(--transparentSelectionText); // bottom line
281 }279 }
282}280}
283 281
@@ -289,7 +287,7 @@ · 5 unmodified lines
289 padding: .5rem .75rem;287 padding: .5rem .75rem;
290 line-height: 1.25rem;288 line-height: 1.25rem;
291 font-size: .8125rem;289 font-size: .8125rem;
292 color: $textColorLight;290 color: var(--textColorLight);
293 291
294 & + & { padding-top: 0; }292 & + & { padding-top: 0; }
295}293}
@@ -303,9 +301,9 @@ · 7 unmodified lines
303._list-hover.clone {301._list-hover.clone {
304 position: fixed;302 position: fixed;
305 overflow: visible;303 overflow: visible;
306 z-index: $hoverZ;304 z-index: var(--hoverZ);
307 left: 0;305 left: 0;
308 min-width: $sidebarWidth;306 min-width: var(--sidebarWidth);
309 padding: .25rem .75rem;307 padding: .25rem .75rem;
310 pointer-events: none;308 pointer-events: none;
311 -webkit-font-smoothing: subpixel-antialiased;309 -webkit-font-smoothing: subpixel-antialiased;
@@ -313,7 +311,7 @@ · 1 unmodified line
313 transform: translate3d(0, 0, 0);311 transform: translate3d(0, 0, 0);
314 @extend %border-box;312 @extend %border-box;
315 313
316 @media #{$mediumScreen} { min-width: $sidebarMediumWidth; }314 @include mobile { min-width: var(--sidebarMediumWidth); }
317 315
318 > ._list-text { display: inline; }316 > ._list-text { display: inline; }
319 317
@@ -346,10 +344,10 @@ · 26 unmodified lines
346 padding: .5rem .75rem .25rem .75rem;344 padding: .5rem .75rem .25rem .75rem;
347 line-height: 1.5rem;345 line-height: 1.5rem;
348 font-size: .75rem;346 font-size: .75rem;
349 font-weight: $bolderFontWeight;347 font-weight: var(--bolderFontWeight);
350 color: $textColorLight;348 color: var(--textColorLight);
351 text-transform: uppercase;349 text-transform: uppercase;
352 background: linear-gradient(to bottom, $sidebarBackground, $sidebarBackground 75%, rgba($sidebarBackground, 0));350 background: linear-gradient(to bottom, var(--sidebarBackground), var(--sidebarBackground) 75%, var(--transparentSidebarBackground));
353 cursor: default;351 cursor: default;
354}352}
355 353
assets/stylesheets/global/_base.scss+37−31
@@ -1,9 +1,10 @@
1html {1html {
2 height: 100%;2 height: 100%;
3 font-size: 100%;3 font-size: 100%;
4 background: $documentBackground;4 background: #fff; // fallback to show the error message to browsers that don't support CSS variables.
5 background: var(--documentBackground);
5 6
6 @media #{$mediumScreen} { font-size: 93.75%; }7 @include mobile { font-size: 93.75%; }
7 8
8 @include print { background: none; }9 @include print { background: none; }
9 @import 'global/print';10 @import 'global/print';
@@ -17,10 +18,11 @@ body { · 7 unmodified lines
17 font-weight: normal;18 font-weight: normal;
18 font-family: $baseFont;19 font-family: $baseFont;
19 line-height: 1.7;20 line-height: 1.7;
20 color: $textColor;21 color: $textColor; // fallback to show the error message to browsers that don't support CSS variables.
22 color: var(--textColor);
21 word-wrap: break-word;23 word-wrap: break-word;
22 overflow-wrap: break-word;24 overflow-wrap: break-word;
23 background: $contentBackground;25 background: var(--contentBackground);
24 touch-action: manipulation;26 touch-action: manipulation;
25 -webkit-tap-highlight-color: rgba(black, 0);27 -webkit-tap-highlight-color: rgba(black, 0);
26 -webkit-touch-callout: none;28 -webkit-touch-callout: none;
@@ -29,11 +31,11 @@ body { · 2 unmodified lines
29}31}
30 32
31a {33a {
32 color: $linkColor;34 color: var(--linkColor);
33 text-decoration: $linkTextDecoration;35 text-decoration: var(--linkTextDecoration);
34 36
35 &:hover {37 &:hover {
36 color: $linkColorHover;38 color: var(--linkColorHover);
37 text-decoration: underline;39 text-decoration: underline;
38 }40 }
39}41}
@@ -47,7 +49,7 @@ img { · 7 unmodified lines
47h1, h2, h3, h4, h5, h6 {49h1, h2, h3, h4, h5, h6 {
48 margin: 1.5em 0 1em;50 margin: 1.5em 0 1em;
49 line-height: 1.3;51 line-height: 1.3;
50 font-weight: $bolderFontWeight;52 font-weight: var(--bolderFontWeight);
51}53}
52 54
53h1 { font-size: 1.5em; }55h1 { font-size: 1.5em; }
@@ -59,7 +61,7 @@ h5, h6 { font-size: 1em; } · 5 unmodified lines
59p { margin: 0 0 1em; }61p { margin: 0 0 1em; }
60p:last-child { margin-bottom: 0; }62p:last-child { margin-bottom: 0; }
61 63
62b, strong { font-weight: $boldFontWeight; }64b, strong { font-weight: var(--boldFontWeight); }
63 65
64small { font-size: .9em; }66small { font-size: .9em; }
65 67
@@ -79,7 +81,7 @@ li > ul, li > ol, dd > ul, dd > ol { margin: .5em 0; } · 13 unmodified lines
79li > p { margin-bottom: .25em; }81li > p { margin-bottom: .25em; }
80 82
81dl { margin: 1.5em 0; }83dl { margin: 1.5em 0; }
82dt { font-weight: $boldFontWeight; }84dt { font-weight: var(--boldFontWeight); }
83dd {85dd {
84 margin: .375em;86 margin: .375em;
85 padding-left: 1em;87 padding-left: 1em;
@@ -91,15 +93,15 @@ dfn, var { font-style: normal; } · 5 unmodified lines
91 93
92abbr, acronym, dfn {94abbr, acronym, dfn {
93 cursor: help;95 cursor: help;
94 border-bottom: 1px dotted $textColor;96 border-bottom: 1px dotted var(--textColor);
95}97}
96 98
97pre, code, samp, %pre, %code {99pre, code, samp, %pre, %code {
98 font-family: $monoFont;100 font-family: var(--monoFont);
99 font-weight: normal;101 font-weight: normal;
100 font-style: normal;102 font-style: normal;
101 font-size: .9em;103 font-size: .9em;
102 color: $textColor;104 color: var(--textColor);
103 white-space: pre-wrap;105 white-space: pre-wrap;
104 direction: ltr;106 direction: ltr;
105 -moz-tab-size: 2;107 -moz-tab-size: 2;
@@ -121,14 +123,14 @@ a > code { color: inherit; } · 15 unmodified lines
121table {123table {
122 margin: 1.5em 0;124 margin: 1.5em 0;
123 background: none;125 background: none;
124 border: 1px solid $boxBorder;126 border: 1px solid var(--boxBorder);
125 border-collapse: separate;127 border-collapse: separate;
126 border-spacing: 0;128 border-spacing: 0;
127 border-radius: 3px;129 border-radius: 3px;
128}130}
129 131
130caption {132caption {
131 font-weight: $boldFontWeight;133 font-weight: var(--boldFontWeight);
132 padding: 0 .7em .3em;134 padding: 0 .7em .3em;
133}135}
134 136
@@ -142,15 +144,15 @@ th, td { · 7 unmodified lines
142}144}
143 145
144th {146th {
145 font-weight: $boldFontWeight;147 font-weight: var(--boldFontWeight);
146 border: 0;148 border: 0;
147 border-bottom: 1px solid $boxBorder;149 border-bottom: 1px solid var(--boxBorder);
148 border-radius: 0;150 border-radius: 0;
149 @extend %heading-box;151 @extend %heading-box;
150 152
151 &:empty { background: none; }153 &:empty { background: none; }
152 154
153 + th, + td { border-left: 1px solid $boxBorder; }155 + th, + td { border-left: 1px solid var(--boxBorder); }
154 156
155 tr:first-child > &:first-child { border-top-left-radius: 3px; }157 tr:first-child > &:first-child { border-top-left-radius: 3px; }
156 tr:first-child > &:last-child { border-top-right-radius: 3px; }158 tr:first-child > &:last-child { border-top-right-radius: 3px; }
@@ -161,10 +163,10 @@ th { · 4 unmodified lines
161}163}
162 164
163td {165td {
164 background: $contentBackground;166 background: var(--contentBackground);
165 border-bottom: 1px solid $boxBorderLight;167 border-bottom: 1px solid var(--boxBorderLight);
166 168
167 + td { border-left: 1px solid $boxBorderLight; }169 + td { border-left: 1px solid var(--boxBorderLight); }
168 tr:last-child > & { border-bottom: 0; }170 tr:last-child > & { border-bottom: 0; }
169 171
170 > pre:only-child, > p:only-child, > ul:only-child, > ol:only-child {172 > pre:only-child, > p:only-child, > ul:only-child, > ol:only-child {
@@ -190,7 +192,7 @@ input, button { · 19 unmodified lines
190 margin: 0;192 margin: 0;
191 font-family: inherit;193 font-family: inherit;
192 font-size: 100%;194 font-size: 100%;
193 color: $textColor;195 color: var(--textColor);
194 line-height: normal;196 line-height: normal;
195 @extend %border-box;197 @extend %border-box;
196}198}
@@ -218,6 +220,10 @@ button:focus { · 21 unmodified lines
218 outline: -webkit-focus-ring-color auto 5px;220 outline: -webkit-focus-ring-color auto 5px;
219}221}
220 222
223img, iframe {
224 background: var(--externalsBackground);
225}
226
221input[type="search"]::-webkit-search-cancel-button,227input[type="search"]::-webkit-search-cancel-button,
222input[type="search"]::-webkit-search-decoration {228input[type="search"]::-webkit-search-decoration {
223 -webkit-appearance: none;229 -webkit-appearance: none;
@@ -230,9 +236,9 @@ input[type="search"]::-webkit-search-decoration { · 6 unmodified lines
230 border: 0 !important;236 border: 0 !important;
231}237}
232 238
233::-webkit-input-placeholder { color: $textColorLighter; }239::-webkit-input-placeholder { color: var(--textColorLighter); }
234::-moz-placeholder { color: $textColorLighter; opacity: 1; }240::-moz-placeholder { color: var(--textColorLighter); opacity: 1; }
235:-ms-input-placeholder { color: $textColorLighter; }241:-ms-input-placeholder { color: var(--textColorLighter); }
236 242
237body:not(._native-scrollbars) {243body:not(._native-scrollbars) {
238 *::-webkit-scrollbar { -webkit-appearance: none; }244 *::-webkit-scrollbar { -webkit-appearance: none; }
@@ -243,12 +249,12 @@ body:not(._native-scrollbars) { · 4 unmodified lines
243 *::-webkit-scrollbar-corner { display: none; }249 *::-webkit-scrollbar-corner { display: none; }
244 250
245 *::-webkit-scrollbar-track {251 *::-webkit-scrollbar-track {
246 background: $contentBackground;252 background: var(--contentBackground);
247 border: 1px solid $contentBackground;253 border: 1px solid var(--contentBackground);
248 254
249 &:hover {255 &:hover {
250 background: $sidebarBackground;256 background: var(--sidebarBackground);
251 border-color: $sidebarBorder;257 border-color: var(--sidebarBorder);
252 }258 }
253 259
254 &:vertical { border-width: 0 0 0 1px; }260 &:vertical { border-width: 0 0 0 1px; }
@@ -266,14 +272,14 @@ body:not(._native-scrollbars) { · 11 unmodified lines
266 272
267 *::-webkit-scrollbar-thumb {273 *::-webkit-scrollbar-thumb {
268 min-height: 2rem;274 min-height: 2rem;
269 background: $scrollbarColor;275 background: var(--scrollbarColor);
270 background-clip: padding-box;276 background-clip: padding-box;
271 border: 5px solid rgba(black, 0);277 border: 5px solid rgba(black, 0);
272 border-radius: 10px;278 border-radius: 10px;
273 279
274 &:hover,280 &:hover,
275 &:active {281 &:active {
276 background-color: $scrollbarColorHover;282 background-color: var(--scrollbarColorHover);
277 border-width: 4px;283 border-width: 4px;
278 }284 }
279 }285 }
assets/stylesheets/global/_classes.scss+24−25
@@ -36,15 +36,15 @@ · 35 unmodified lines
36//36//
37 37
38%box {38%box {
39 background: $boxBackground;39 background: var(--boxBackground);
40 border: 1px solid $boxBorder;40 border: 1px solid var(--boxBorder);
41 border-radius: 3px;41 border-radius: 3px;
42}42}
43 43
44%heading-box {44%heading-box {
45 color: $boxHeaderColor;45 color: var(--boxHeaderColor);
46 background: $boxHeaderBackground;46 background: var(--boxHeaderBackground);
47 border: 1px solid $boxBorder;47 border: 1px solid var(--boxBorder);
48 border-radius: 3px;48 border-radius: 3px;
49}49}
50 50
@@ -82,15 +82,15 @@ · 31 unmodified lines
82%note {82%note {
83 margin: 1.5rem 0;83 margin: 1.5rem 0;
84 padding: .5rem .875rem;84 padding: .5rem .875rem;
85 background: $noteBackground;85 background: var(--noteBackground);
86 border: 1px solid $noteBorder;86 border: 1px solid var(--noteBorder);
87 border-radius: 3px;87 border-radius: 3px;
88}88}
89 89
90%label {90%label {
91 margin: 0 1px;91 margin: 0 1px;
92 padding: 1px 4px 2px;92 padding: 1px 4px 2px;
93 background: $labelBackground;93 background: var(--labelBackground);
94 border-radius: 3px;94 border-radius: 3px;
95}95}
96 96
@@ -102,35 +102,35 @@ · 5 unmodified lines
102 padding-right: .5em;102 padding-right: .5em;
103 overflow: hidden;103 overflow: hidden;
104 font-size: inherit;104 font-size: inherit;
105 color: $boxHeaderColor;105 color: var(--boxHeaderColor);
106 border: 1px solid $boxBorder;106 border: 1px solid var(--boxBorder);
107 border-radius: 2px;107 border-radius: 2px;
108 @extend %label;108 @extend %label;
109}109}
110 110
111%label-yellow {111%label-yellow {
112 background: $noteBackground;112 background: var(--noteBackground);
113 border-color: $noteBorder;113 border-color: var(--noteBorder);
114}114}
115 115
116%note-green, %label-green {116%note-green, %label-green {
117 background: $noteGreenBackground;117 background: var(--noteGreenBackground);
118 border-color: $noteGreenBorder;118 border-color: var(--noteGreenBorder);
119}119}
120 120
121%note-blue, %label-blue {121%note-blue, %label-blue {
122 background: $noteBlueBackground;122 background: var(--noteBlueBackground);
123 border-color: $noteBlueBorder;123 border-color: var(--noteBlueBorder);
124}124}
125 125
126%note-orange, %label-orange {126%note-orange, %label-orange {
127 background: $noteOrangeBackground;127 background: var(--noteOrangeBackground);
128 border-color: $noteOrangeBorder;128 border-color: var(--noteOrangeBorder);
129}129}
130 130
131%note-red, %label-red {131%note-red, %label-red {
132 background: $noteRedBackground;132 background: var(--noteRedBackground);
133 border-color: $noteRedBorder;133 border-color: var(--noteRedBorder);
134}134}
135 135
136//136//
@@ -149,12 +149,11 @@ · 12 unmodified lines
149 pointer-events: none;149 pointer-events: none;
150 150
151 // <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M15,15H2V6h2.595c0,0,0.689-0.896,2.17-2H1C0.447,4,0,4.449,0,5v11c0,0.553,0.447,1,1,1h15c0.553,0,1-0.447,1-1v-3.746 l-2,1.645V15z M13.361,8.05v3.551L20,6.4l-6.639-4.999v3.131C5.3,4.532,5.3,12.5,5.3,12.5C7.582,8.752,8.986,8.05,13.361,8.05z"/></svg>151 // <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M15,15H2V6h2.595c0,0,0.689-0.896,2.17-2H1C0.447,4,0,4.449,0,5v11c0,0.553,0.447,1,1,1h15c0.553,0,1-0.447,1-1v-3.746 l-2,1.645V15z M13.361,8.05v3.551L20,6.4l-6.639-4.999v3.131C5.3,4.532,5.3,12.5,5.3,12.5C7.582,8.752,8.986,8.05,13.361,8.05z"/></svg>
152 @if $style == 'dark' {152 background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzMzNzdjMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
153 background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2NiZDBkMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
154 } @else {
155 background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzMzNzdjMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
156 }
157 }153 }
158}154}
155html._theme-dark %external-link:after {
156 background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2NiZDBkMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
157}
159 158
160%internal-link:after { content: none !important; }159%internal-link:after { content: none !important; }
assets/stylesheets/global/_icons.scss+0−175
@@ -1,175 +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
26%darkIconFix {
27 @if $style == 'dark' {
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-mongoose:before { background-position: -7rem -5rem; }
98._icon-phpunit:before { background-position: -8rem -5rem; }
99._icon-nokogiri:before { background-position: -9rem -5rem; @extend %darkIconFix !optional; }
100._icon-rethinkdb:before { background-position: 0 -6rem; }
101._icon-react:before { background-position: -1rem -6rem; }
102._icon-socketio:before { background-position: -2rem -6rem; }
103._icon-modernizr:before { background-position: -3rem -6rem; }
104._icon-bower:before { background-position: -4rem -6rem; }
105._icon-fish:before { background-position: -5rem -6rem; @extend %darkIconFix !optional; }
106._icon-scikit_image:before { background-position: -6rem -6rem; }
107._icon-twig:before { background-position: -7rem -6rem; }
108._icon-pandas:before { background-position: -8rem -6rem; }
109._icon-scikit_learn:before { background-position: -9rem -6rem; }
110._icon-bottle:before { background-position: 0 -7rem; }
111._icon-docker:before { background-position: -1rem -7rem; }
112._icon-cakephp:before { background-position: -2rem -7rem; }
113._icon-lua:before { background-position: -3rem -7rem; @extend %darkIconFix !optional; }
114._icon-clojure:before { background-position: -4rem -7rem; }
115._icon-symfony:before { background-position: -5rem -7rem; }
116._icon-mocha:before { background-position: -6rem -7rem; }
117._icon-meteor:before { background-position: -7rem -7rem; @extend %darkIconFix !optional; }
118._icon-npm:before { background-position: -8rem -7rem; }
119._icon-apache_http_server:before { background-position: -9rem -7rem; }
120._icon-drupal:before { background-position: 0 -8rem; }
121._icon-webpack:before { background-position: -1rem -8rem; }
122._icon-phaser:before { background-position: -2rem -8rem; }
123._icon-vue:before { background-position: -3rem -8rem; }
124._icon-opentsdb:before { background-position: -4rem -8rem; }
125._icon-q:before { background-position: -5rem -8rem; }
126._icon-crystal:before { background-position: -6rem -8rem; @extend %darkIconFix !optional; }
127._icon-julia:before { background-position: -7rem -8rem; @extend %darkIconFix !optional; }
128._icon-redux:before { background-position: -8rem -8rem; @extend %darkIconFix !optional; }
129._icon-bootstrap:before { background-position: -9rem -8rem; }
130._icon-react_native:before { background-position: 0 -9rem; }
131._icon-phalcon:before { background-position: -1rem -9rem; }
132._icon-matplotlib:before { background-position: -2rem -9rem; }
133._icon-cmake:before { background-position: -3rem -9rem; }
134._icon-elixir:before { background-position: -4rem -9rem; @extend %darkIconFix !optional; }
135._icon-vagrant:before { background-position: -5rem -9rem; }
136._icon-dojo:before { background-position: -6rem -9rem; }
137._icon-flow:before { background-position: -7rem -9rem; }
138._icon-relay:before { background-position: -8rem -9rem; }
139._icon-phoenix:before { background-position: -9rem -9rem; }
140
141._icon-tcl_tk:before { background-position: 0 0; @extend %doc-icon-2; }
142._icon-erlang:before { background-position: -1rem 0; @extend %doc-icon-2; }
143._icon-chef:before { background-position: -2rem 0; @extend %doc-icon-2; }
144._icon-ramda:before { background-position: -3rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
145._icon-codeigniter:before { background-position: -4rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
146._icon-influxdata:before { background-position: -5rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
147._icon-tensorflow:before { background-position: -6rem 0; @extend %doc-icon-2; }
148._icon-haxe:before { background-position: -7rem 0; @extend %doc-icon-2; }
149._icon-ansible:before { background-position: -8rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
150._icon-typescript:before { background-position: -9rem 0; @extend %doc-icon-2; }
151._icon-browser_support_tables:before { background-position: 0rem -1rem; @extend %doc-icon-2; }
152._icon-gnu_fortran:before { background-position: -1rem -1rem; @extend %doc-icon-2; }
153._icon-gcc:before { background-position: -2rem -1rem; @extend %doc-icon-2; }
154._icon-perl:before { background-position: -3rem -1rem; @extend %doc-icon-2; }
155._icon-apache_pig:before { background-position: -4rem -1rem; @extend %doc-icon-2; }
156._icon-numpy:before { background-position: -5rem -1rem; @extend %doc-icon-2; }
157._icon-kotlin:before { background-position: -6rem -1rem; @extend %doc-icon-2; }
158._icon-padrino:before { background-position: -7rem -1rem; @extend %doc-icon-2; }
159._icon-angular:before { background-position: -8rem -1rem; @extend %doc-icon-2; }
160._icon-love:before { background-position: -9rem -1rem; @extend %doc-icon-2; }
161._icon-jasmine:before { background-position: 0 -2rem; @extend %doc-icon-2; }
162._icon-pug:before { background-position: -1rem -2rem; @extend %doc-icon-2; }
163._icon-electron:before { background-position: -2rem -2rem; @extend %doc-icon-2; }
164._icon-falcon:before { background-position: -3rem -2rem; @extend %doc-icon-2; }
165._icon-godot:before { background-position: -4rem -2rem; @extend %doc-icon-2; }
166._icon-nim:before { background-position: -5rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; }
167._icon-vulkan:before { background-position: -6rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; }
168._icon-d:before { background-position: -7rem -2rem; @extend %doc-icon-2; }
169._icon-bluebird:before { background-position: -8rem -2rem; @extend %doc-icon-2; }
170._icon-eslint:before { background-position: -9rem -2rem; @extend %doc-icon-2; }
171._icon-homebrew:before { background-position: 0 -3rem; @extend %doc-icon-2; }
172._icon-jekyll:before { background-position: -1rem -3rem; @extend %doc-icon-2; }
173._icon-babel:before { background-position: -2rem -3rem; @extend %doc-icon-2; }
174._icon-leaflet:before { background-position: -3rem -3rem; @extend %doc-icon-2; }
175._icon-terraform:before { background-position: -4rem -3rem; @extend %doc-icon-2; }
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 %>
assets/stylesheets/global/_mixins.scss+6−0
@@ -7,3 +7,9 @@ · 6 unmodified lines
7 @content;7 @content;
8 }8 }
9}9}
10
11@mixin mobile {
12 @media (max-width: 800px) {
13 @content;
14 }
15}
assets/stylesheets/global/_print.scss+2−2
@@ -25,14 +25,14 @@ · 24 unmodified lines
25 25
26 ._attribution-p {26 ._attribution-p {
27 background: none;27 background: none;
28 border: 2px solid $boxBorder;28 border: 2px solid var(--boxBorder);
29 }29 }
30 30
31 ._attribution:last-child:after {31 ._attribution:last-child:after {
32 content: 'Exported from DevDocs \2014 https://devdocs.io';32 content: 'Exported from DevDocs \2014 https://devdocs.io';
33 display: block;33 display: block;
34 margin-top: 1rem;34 margin-top: 1rem;
35 font-weight: $bolderFontWeight;35 font-weight: var(--bolderFontWeight);
36 }36 }
37 37
38 ._attribution {38 ._attribution {
assets/stylesheets/global/_variables-dark.scss+53−69
@@ -1,92 +1,76 @@
1$baseFont: -apple-system, BlinkMacSystemFont, 'San Francisco', 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;1html._theme-dark {
2$monoFont: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;2 --absolute: white;
3$boldFontWeight: 500;
4$bolderFontWeight: 600;
5 3
6$style: 'dark';4 --documentBackground: #222;
5 --contentBackground: #33373a;
7 6
8$maxWidth: 80rem;7 --textColor: #cbd0d0;
9$headerHeight: 3rem;8 --textColorLight: #9da5ad;
10$sidebarWidth: 20rem;9 --textColorLighter: #77787a;
11$sidebarMediumWidth: 16rem;
12 10
13$documentBackground: #222;11 --externalsBackground: #fff;
14$contentBackground: #33373a;
15 12
16$textColor: #cbd0d0;13 --inputFocusBorder: transparent;
17$textColorLight: #9da5ad;
18$textColorLighter: #77787a;
19$textColorRed: #f44336;
20 14
21$inputFocusBorder: false;15 --focusBackground: #3f4042;
16 --focusBorder: #000;
17 --focusText: #f7f2f2;
22 18
23$focusBackground: #3f4042;19 --loadingText: #5d6164;
24$focusBorder: #000;
25$focusText: #f7f2f2;
26 20
27$loadingText: #5d6164;21 --selectionBackground: #007acc;
28$splashText: $loadingText;22 --selectionBorder: #000;
29 23
30$selectionBackground: #007acc;24 --highlightBackground: #64675f;
31$selectionBorder: #000;
32$selectionText: #fff;
33 25
34$highlightBackground: #64675f;26 --linkColor: var(--textColor);
27 --linkColorHover: white;
28 --linkTextDecoration: underline;
35 29
36$linkColor: $textColor;30 --headerBackground: #1c1c1c;
37$linkColorHover: white;31 --headerBorder: #000;
38$linkTextDecoration: underline;32 --searchTagBackground: #{darken(#1c1c1c, 5%)};
33 --searchBorder: #{darken(#000, 2%)};
39 34
40$headerBackground: #1c1c1c;35 --sidebarBackground: #24282a;
41$headerBorder: #000;36 --transparentSidebarBackground: #{rgba(#24282a, 0)};
37 --sidebarBorder: #000;
42 38
43$sidebarBackground: #24282a;39 --scrollbarColor: #6c6c6f;
44$sidebarBorder: #000;40 --scrollbarColorHover: #949697;
45 41
46$scrollbarColor: #6c6c6f;42 --pathBackground: var(--headerBackground);
47$scrollbarColorHover: #949697;43 --pathBorder: var(--headerBorder);
48 44
49$pathBackground: $headerBackground;45 --noticeBackground: var(--sidebarBackground);
50$pathBorder: $headerBorder;46 --noticeBorder: var(--sidebarBorder);
51 47
52$noticeBackground: $sidebarBackground;48 --boxBackground: var(--sidebarBackground);
53$noticeBorder: $sidebarBorder;49 --boxBorder: var(--headerBorder);
50 --boxBorderLight: var(--headerBorder);
51 --boxHeaderColor: #dbe4e4;
52 --boxHeaderBackground: var(--sidebarBackground);
54 53
55$boxBackground: $sidebarBackground;54 --noteBackground: #45474b;
56$boxBorder: $headerBorder;55 --noteBorder: #000;
57$boxBorderLight: $headerBorder;
58$boxHeaderColor: #dbe4e4;
59$boxHeaderBackground: $sidebarBackground;
60 56
61$noteBackground: #45474b;57 --noteGreenBackground: #284a2a;
62$noteBorder: #000;58 --noteGreenBorder: #000;
63 59
64$noteGreenBackground: #284a2a;60 --noteBlueBackground: #2a4151;
65$noteGreenBorder: #000;61 --noteBlueBorder: #000;
66 62
67$noteBlueBackground: #2a4151;63 --noteOrangeBackground: #563322;
68$noteBlueBorder: #000;64 --noteOrangeBorder: #000;
69 65
70$noteOrangeBackground: #563322;66 --noteRedBackground: #603033;
71$noteOrangeBorder: #000;67 --noteRedBorder: #000;
72 68
73$noteRedBackground: #603033;69 --labelBackground: var(--boxBackground);
74$noteRedBorder: #000;
75 70
76$labelBackground: $boxBackground;71 --notifBackground: #{rgba(#555, .95)};
72 --notifBorder: 1px solid #000;
77 73
78$notifBackground: rgba(#555, .95);74 --tipBackground: var(--notifBackground);
79$notifBorder: 1px solid #000;75 --tipBorder: var(--notifBorder);
80$notifColor: #fff;76}
81$notifColorLight: #ccc;
82
83$tipBackground: $notifBackground;
84$tipBorder: $notifBorder;
85
86$mediumScreen: '(max-width: 800px)';
87
88$contentZ: 1;
89$sidebarZ: 2;
90$headerZ: 3;
91$noticeZ: 4;
92$hoverZ: 5;
assets/stylesheets/global/_variables-light.scss+76−0
@@ -0,0 +1,76 @@
1html._theme-default {
2 --absolute: black;
3
4 --documentBackground: #f3f3f3;
5 --contentBackground: #fff;
6
7 --textColor: #333;
8 --textColorLight: #666;
9 --textColorLighter: #888;
10
11 --externalsBackground: #fff;
12
13 --inputFocusBorder: #35b5f4;
14
15 --focusBackground: #e5e5e5;
16 --focusBorder: #d4d4d4;
17 --focusText: #000;
18
19 --loadingText: #ccc;
20
21 --selectionBackground: #398df0;
22 --selectionBorder: #196fc2;
23
24 --highlightBackground: #fffdcd;
25
26 --linkColor: #3377c0;
27 --linkColorHover: #2f6cb6;
28 --linkTextDecoration: none;
29
30 --headerBackground: #eee;
31 --headerBorder: #d7d7d7;
32 --searchTagBackground: #{darken(#eee, 5%)};
33 --searchBorder: #{darken(#d7d7d7, 2%)};
34
35 --sidebarBackground: #f9f9f9;
36 --transparentSidebarBackground: #{rgba(#f9f9f9, 0)};
37 --sidebarBorder: #e1e1e1;
38
39 --scrollbarColor: #ccc;
40 --scrollbarColorHover: #999;
41
42 --pathBackground: var(--sidebarBackground);
43 --pathBorder: var(--sidebarBorder);
44
45 --noticeBackground: #faf9e2;
46 --noticeBorder: #e2e2c1;
47
48 --boxBackground: #fafafa;
49 --boxBorder: #d8d8d8;
50 --boxBorderLight: #e5e5e5;
51 --boxHeaderColor: var(--textColor);
52 --boxHeaderBackground: #f5f5f5;
53
54 --noteBackground: #f8f8dd;
55 --noteBorder: #d3d952;
56
57 --noteGreenBackground: #e7f8e1;
58 --noteGreenBorder: #89da70;
59
60 --noteBlueBackground: #d4f3fd;
61 --noteBlueBorder: #94bbeb;
62
63 --noteOrangeBackground: #fbe6d1;
64 --noteOrangeBorder: #ec8b01;
65
66 --noteRedBackground: #fed5d3;
67 --noteRedBorder: #dc7874;
68
69 --labelBackground: #f4f4f4;
70
71 --notifBackground: #{rgba(#333, .85)};
72 --notifBorder: none;
73
74 --tipBackground: #{rgba(#fffdcd, .95)};
75 --tipBorder: 1px solid #e7dca9;
76}
assets/stylesheets/global/_variables.scss+25−82
@@ -1,92 +1,35 @@
1// Variables needed to style the error message for browsers that don't support CSS variables.
1$baseFont: -apple-system, BlinkMacSystemFont, 'San Francisco', 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;2$baseFont: -apple-system, BlinkMacSystemFont, 'San Francisco', 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;
2$monoFont: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
3$boldFontWeight: 500;
4$bolderFontWeight: 600;
5
6$style: 'light';
7
8$maxWidth: 80rem;
9$headerHeight: 3rem;
10$sidebarWidth: 20rem;
11$sidebarMediumWidth: 16rem;
12
13$documentBackground: #f3f3f3;
14$contentBackground: #fff;
15
16$textColor: #333;3$textColor: #333;
17$textColorLight: #666;
18$textColorLighter: #888;
19$textColorRed: #f44336;
20
21$inputFocusBorder: #35b5f4;
22
23$focusBackground: #e5e5e5;
24$focusBorder: #d4d4d4;
25$focusText: #000;
26
27$loadingText: #ccc;
28$splashText: #ccc;
29
30$selectionBackground: #398df0;
31$selectionBorder: #196fc2;
32$selectionText: #fff;
33
34$highlightBackground: #fffdcd;
35
36$linkColor: #3377c0;
37$linkColorHover: #2f6cb6;
38$linkTextDecoration: none;
39
40$headerBackground: #eee;
41$headerBorder: #d7d7d7;
42
43$sidebarBackground: #f9f9f9;
44$sidebarBorder: #e1e1e1;
45
46$scrollbarColor: #ccc;
47$scrollbarColorHover: #999;
48
49$pathBackground: $sidebarBackground;
50$pathBorder: $sidebarBorder;
51
52$noticeBackground: #faf9e2;
53$noticeBorder: #e2e2c1;
54
55$boxBackground: #fafafa;
56$boxBorder: #d8d8d8;
57$boxBorderLight: #e5e5e5;
58$boxHeaderColor: $textColor;
59$boxHeaderBackground: #f5f5f5;
60
61$noteBackground: #f8f8dd;
62$noteBorder: #d3d952;
63
64$noteGreenBackground: #e7f8e1;
65$noteGreenBorder: #89da70;
66 4
67$noteBlueBackground: #d4f3fd;5html {
68$noteBlueBorder: #94bbeb;6 --baseFont: #{$baseFont};
7 --monoFont: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
8 --boldFontWeight: 500;
9 --bolderFontWeight: 600;
69 10
70$noteOrangeBackground: #fbe6d1;11 --textColorRed: #f44336;
71$noteOrangeBorder: #ec8b01;
72 12
73$noteRedBackground: #fed5d3;13 --splashText: var(--loadingText);
74$noteRedBorder: #dc7874;
75 14
76$labelBackground: #f4f4f4;15 --selectionText: #fff;
16 --transparentSelectionText: rgba(255, 255, 255, 0.9);
77 17
78$notifBackground: rgba(#333, .85);18 --notifColor: #fff;
79$notifBorder: none;19 --notifColorLight: #ccc;
80$notifColor: #fff;
81$notifColorLight: #ccc;
82 20
83$tipBackground: rgba(#fffdcd, .95);21 --maxWidth: 80rem;
84$tipBorder: 1px solid #e7dca9;22 --headerHeight: 3rem;
23 --sidebarWidth: 20rem;
24 --sidebarMediumWidth: 16rem;
85 25
86$mediumScreen: '(max-width: 800px)';26 --focusBackground: #e5e5e5;
27 --focusBorder: #d4d4d4;
28 --focusText: #000;
87 29
88$contentZ: 1;30 --contentZ: 1;
89$sidebarZ: 2;31 --sidebarZ: 2;
90$headerZ: 3;32 --headerZ: 3;
91$noticeZ: 4;33 --noticeZ: 4;
92$hoverZ: 5;34 --hoverZ: 5;
35}
assets/stylesheets/pages/_angularjs.scss+1−1
@@ -8,7 +8,7 @@ · 7 unmodified lines
8 .nav-index-section {8 .nav-index-section {
9 margin: 1.5em 0 1em -2em;9 margin: 1.5em 0 1em -2em;
10 list-style: none;10 list-style: none;
11 font-weight: $boldFontWeight;11 font-weight: var(--boldFontWeight);
12 text-transform: capitalize;12 text-transform: capitalize;
13 }13 }
14 14
assets/stylesheets/pages/_async.scss+2−2
@@ -3,13 +3,13 @@ · 2 unmodified lines
3 3
4 h3 > .type-signature {4 h3 > .type-signature {
5 float: right;5 float: right;
6 color: $textColorLight;6 color: var(--textColorLight);
7 }7 }
8 8
9 h3 > .signature-attributes {9 h3 > .signature-attributes {
10 font-size: .75rem;10 font-size: .75rem;
11 font-weight: normal;11 font-weight: normal;
12 font-style: italic;12 font-style: italic;
13 color: $textColorLighter;13 color: var(--textColorLighter);
14 }14 }
15}15}
assets/stylesheets/pages/_bash.scss+12−0
@@ -0,0 +1,12 @@
1._bash {
2 dl > dt > code,
3 dl > dt > kbd {
4 @extend %block-label, %label-blue;
5 }
6
7 th[align=left] {
8 border-left: 1px solid var(--boxBorder);
9 }
10
11 code { @extend %label; }
12}
assets/stylesheets/pages/_bootstrap.scss+1−1
@@ -4,7 +4,7 @@ · 3 unmodified lines
4 h4 > code, h5 > code, strong > code { @extend %label; }4 h4 > code, h5 > code, strong > code { @extend %label; }
5 5
6 h2 > small {6 h2 > small {
7 color: $textColorLight;7 color: var(--textColorLight);
8 float: right;8 float: right;
9 }9 }
10 10
assets/stylesheets/pages/_c.scss+2−2
@@ -23,7 +23,7 @@ · 22 unmodified lines
23 23
24 .t-spar { // language/switch, language/for, etc.24 .t-spar { // language/switch, language/for, etc.
25 font-style: italic;25 font-style: italic;
26 color: $textColorLight;26 color: var(--textColorLight);
27 }27 }
28 .t-sdsc-nopad dl, .t-sdsc-nopad dd { margin: 0; }28 .t-sdsc-nopad dl, .t-sdsc-nopad dd { margin: 0; }
29 29
@@ -57,7 +57,7 @@ · 27 unmodified lines
57 margin: 1rem 0;57 margin: 1rem 0;
58 padding: .375rem;58 padding: .375rem;
59 font-size: .75rem;59 font-size: .75rem;
60 border: 1px solid $boxBorder;60 border: 1px solid var(--boxBorder);
61 border-radius: 2px;61 border-radius: 2px;
62 }62 }
63 63
assets/stylesheets/pages/_clojure.scss+1−1
@@ -5,6 +5,6 @@ · 4 unmodified lines
5 .type {5 .type {
6 float: right;6 float: right;
7 font-size: .9em;7 font-size: .9em;
8 color: $textColorLight;8 color: var(--textColorLight);
9 }9 }
10}10}
assets/stylesheets/pages/_cypress.scss+21−0
@@ -0,0 +1,21 @@
1._cypress {
2 @extend %simple;
3
4 .note {
5 h1 {
6 margin-left: inherit
7 }
8
9 &.danger {
10 @extend %note-red
11 }
12
13 &.info {
14 @extend %note-blue
15 }
16
17 &.success {
18 @extend %note-green
19 }
20 }
21}
assets/stylesheets/pages/_d.scss+3−3
@@ -2,10 +2,10 @@ · 1 unmodified line
2 h2 { @extend %block-heading; }2 h2 { @extend %block-heading; }
3 h3, .d_decl { @extend %block-label, %label-blue; }3 h3, .d_decl { @extend %block-label, %label-blue; }
4 .d_decl { @extend %code; }4 .d_decl { @extend %code; }
5 .d_decl > small { color: $textColorLight; }5 .d_decl > small { color: var(--textColorLight); }
6 .d_decl > strong { font-weight: $bolderFontWeight; }6 .d_decl > strong { font-weight: var(--bolderFontWeight); }
7 7
8 p > code, li > code, td > code, dd > code { @extend %label; }8 p > code, li > code, td > code, dd > code { @extend %label; }
9 9
10 span.red { color: $textColorRed; }10 span.red { color: var(--textColorRed); }
11}11}
assets/stylesheets/pages/_dart.scss+12−0
@@ -0,0 +1,12 @@
1._dart {
2 @extend %simple;
3
4 dl:not(.dl-horizontal) dt, .multi-line-signature {
5 @extend %block-label;
6
7 .features {
8 float: right;
9 color: var(--textColorLight);
10 }
11 }
12}
assets/stylesheets/pages/_dojo.scss+1−1
@@ -1,5 +1,5 @@
1._dojo {1._dojo {
2 @extend %simple;2 @extend %simple;
3 3
4 .jsdoc-inheritance { color: $textColorLight; }4 .jsdoc-inheritance { color: var(--textColorLight); }
5}5}
assets/stylesheets/pages/_ember.scss+2−2
@@ -5,7 +5,7 @@ · 4 unmodified lines
5 5
6 h3 > .access {6 h3 > .access {
7 float: right;7 float: right;
8 color: $textColorLight;8 color: var(--textColorLight);
9 font-weight: normal;9 font-weight: normal;
10 }10 }
11 11
@@ -15,6 +15,6 @@ · 3 unmodified lines
15 }15 }
16 16
17 p.github-link {17 p.github-link {
18 color: $textColorLight;18 color: var(--textColorLight);
19 }19 }
20}20}
assets/stylesheets/pages/_erlang.scss+1−1
@@ -5,5 +5,5 @@ · 4 unmodified lines
5 code.code { @extend %label; }5 code.code { @extend %label; }
6 .note { @extend %note; }6 .note { @extend %note; }
7 .warning { @extend %note, %note-red; }7 .warning { @extend %note, %note-red; }
8 .note .label, .warning .label { font-weight: $boldFontWeight; }8 .note .label, .warning .label { font-weight: var(--boldFontWeight); }
9}9}
assets/stylesheets/pages/_graphite.scss+7−0
@@ -0,0 +1,7 @@
1._graphite {
2 @extend %simple;
3
4 dl > dt {
5 @extend %block-label, %label-blue;
6 }
7}
assets/stylesheets/pages/_jquery.scss+2−2
@@ -32,7 +32,7 @@ · 31 unmodified lines
32 32
33 .toc-list {33 .toc-list {
34 margin-top: 0;34 margin-top: 0;
35 font-weight: $boldFontWeight;35 font-weight: var(--boldFontWeight);
36 36
37 > li + li { margin-top: 1em; }37 > li + li { margin-top: 1em; }
38 > li > ul { font-weight: normal; }38 > li > ul { font-weight: normal; }
@@ -48,7 +48,7 @@ · 9 unmodified lines
48 .returns,48 .returns,
49 .option-type {49 .option-type {
50 float: right;50 float: right;
51 font-weight: $boldFontWeight;51 font-weight: var(--boldFontWeight);
52 margin-left: 1em;52 margin-left: 1em;
53 }53 }
54 54
assets/stylesheets/pages/_love.scss+2−2
@@ -12,6 +12,6 @@ · 11 unmodified lines
12 .smwtable { width: 100%; }12 .smwtable { width: 100%; }
13 .smwtable td:nth-last-child(2), .smwtable td:last-child { width: 2.5em; }13 .smwtable td:nth-last-child(2), .smwtable td:last-child { width: 2.5em; }
14 14
15 .cell-green { background: $noteGreenBackground; }15 .cell-green { background: var(--noteGreenBackground); }
16 .cell-red { background: $noteRedBackground; }16 .cell-red { background: var(--noteRedBackground); }
17}17}
assets/stylesheets/pages/_mdn.scss+28−3
@@ -7,7 +7,7 @@ · 6 unmodified lines
7 > span {7 > span {
8 display: block;8 display: block;
9 font-size: 1rem;9 font-size: 1rem;
10 font-weight: $boldFontWeight;10 font-weight: var(--boldFontWeight);
11 }11 }
12 12
13 ul, ol {13 ul, ol {
@@ -30,6 +30,7 @@ · 16 unmodified lines
30 .notice,30 .notice,
31 .warning,31 .warning,
32 .overheadIndicator,32 .overheadIndicator,
33 .blockIndicator,
33 .syntaxbox, // CSS, JavaScript34 .syntaxbox, // CSS, JavaScript
34 .twopartsyntaxbox, // CSS35 .twopartsyntaxbox, // CSS
35 .inheritsbox, // JavaScript36 .inheritsbox, // JavaScript
@@ -42,7 +43,7 @@ · 6 unmodified lines
42 > .note {43 > .note {
43 em {44 em {
44 font-style: normal;45 font-style: normal;
45 font-weight: $boldFontWeight;46 font-weight: var(--boldFontWeight);
46 }47 }
47 48
48 > ul { margin: 1em 0; }49 > ul { margin: 1em 0; }
@@ -93,7 +94,7 @@ · 44 unmodified lines
93 dt > strong > code, // HTML element attribute94 dt > strong > code, // HTML element attribute
94 dl > dt > code { // CSS property value, Javascript function argument95 dl > dt > code { // CSS property value, Javascript function argument
95 font-family: inherit;96 font-family: inherit;
96 font-weight: $boldFontWeight;97 font-weight: var(--boldFontWeight);
97 font-size: inherit;98 font-size: inherit;
98 }99 }
99 100
@@ -104,4 +105,28 @@ · 4 unmodified lines
104 .cleared { clear: both; } // CSS/box-shadow105 .cleared { clear: both; } // CSS/box-shadow
105 106
106 code > strong { font-weight: normal; }107 code > strong { font-weight: normal; }
108
109 // Compatibility tablees
110
111 .bc-github-link {
112 float: right;
113 font-size: .75rem;
114 }
115
116 .bc-supports-yes, .bc-supports-yes + dd, .bc-supports-yes + dd + dd { background: var(--noteGreenBackground); }
117 .bc-supports-partial, .bc-supports-partial + dd, .bc-supports-partial + dd + dd { background: var(--noteOrangeBackground); }
118 .bc-supports-no, .bc-supports-no + dd, .bc-supports-no + dd + dd { background: var(--noteRedBackground); }
119
120 .bc-table {
121 min-width: 100%;
122
123 dl {
124 margin: .25rem 0 0;
125 padding: .25rem 0 0;
126 font-size: .75rem;
127 border-top: 1px solid var(--boxBorder);
128 }
129
130 dd { margin: 0; }
131 }
107}132}
assets/stylesheets/pages/_meteor.scss+2−2
@@ -9,10 +9,10 @@ · 8 unmodified lines
9 dt > code { @extend %label; }9 dt > code { @extend %label; }
10 10
11 .api-heading { overflow: hidden; }11 .api-heading { overflow: hidden; }
12 .api-heading > code { font-weight: $boldFontWeight; }12 .api-heading > code { font-weight: var(--boldFontWeight); }
13 .locus, .src-code { float: right; }13 .locus, .src-code { float: right; }
14 .locus, .type, .src-code { margin-left: .5em; }14 .locus, .type, .src-code { margin-left: .5em; }
15 h2 .subtext-api { margin-top: .25rem; }15 h2 .subtext-api { margin-top: .25rem; }
16 .locus, .subtext-api, .subtext-api > code { font-size: .75rem; }16 .locus, .subtext-api, .subtext-api > code { font-size: .75rem; }
17 .locus, .type { color: $textColorLight; }17 .locus, .type { color: var(--textColorLight); }
18}18}
assets/stylesheets/pages/_node.scss+3−0
@@ -20,5 +20,8 @@ · 19 unmodified lines
20 margin: 0 0 1em 1em;20 margin: 0 0 1em 1em;
21 @extend %label;21 @extend %label;
22 }22 }
23
24 .srclink { float: right; }
25 details > table { margin: 0; }
23}26}
24 27
assets/stylesheets/pages/_npm.scss+1−1
@@ -8,7 +8,7 @@ · 7 unmodified lines
8 8
9 .faint.heading {9 .faint.heading {
10 font-size: .9em;10 font-size: .9em;
11 color: $textColorLight;11 color: var(--textColorLight);
12 }12 }
13 13
14 .youtube-video iframe { width: 420px; height: 315px; }14 .youtube-video iframe { width: 420px; height: 315px; }
assets/stylesheets/pages/_phalcon.scss+1−1
@@ -3,6 +3,6 @@ · 2 unmodified lines
3 3
4 h3 > small {4 h3 > small {
5 float: right;5 float: right;
6 color: $textColorLight;6 color: var(--textColorLight);
7 }7 }
8}8}
assets/stylesheets/pages/_php.scss+2−2
@@ -13,7 +13,7 @@ · 12 unmodified lines
13 13
14 .verinfo {14 .verinfo {
15 float: right;15 float: right;
16 font-weight: $boldFontWeight;16 font-weight: var(--boldFontWeight);
17 }17 }
18 18
19 .classsynopsis,19 .classsynopsis,
@@ -21,7 +21,7 @@ · 1 unmodified line
21 .description > .methodsynopsis,21 .description > .methodsynopsis,
22 .description > .fieldsynopsis { @extend %note, %note-blue; }22 .description > .fieldsynopsis { @extend %note, %note-blue; }
23 23
24 .classsynopsisinfo_comment { color: $textColorLight; }24 .classsynopsisinfo_comment { color: var(--textColorLight); }
25 25
26 .classsynopsisinfo_comment,26 .classsynopsisinfo_comment,
27 .classsynopsis > .constructorsynopsis,27 .classsynopsis > .constructorsynopsis,
assets/stylesheets/pages/_postgres.scss+1−1
@@ -16,7 +16,7 @@ · 15 unmodified lines
16 }16 }
17 17
18 p > code { @extend %label; }18 p > code { @extend %label; }
19 p.c2 { font-weight: $boldFontWeight; }19 p.c2 { font-weight: var(--boldFontWeight); }
20 20
21 .navfooter > table { width: 100%; }21 .navfooter > table { width: 100%; }
22 td[align=center] { text-align: center; }22 td[align=center] { text-align: center; }
assets/stylesheets/pages/_qt.scss+9−0
@@ -0,0 +1,9 @@
1._qt {
2 @extend %simple;
3
4 // Function headers
5 h3.fn > code {
6 float: right;
7 color: var(--textColorLight);
8 }
9}
assets/stylesheets/pages/_rdoc.scss+2−13
@@ -6,7 +6,7 @@ · 5 unmodified lines
6 .method-description > h2, h3, h4, h5, h6 { font-size: 1em; }6 .method-description > h2, h3, h4, h5, h6 { font-size: 1em; }
7 7
8 .method-heading {8 .method-heading {
9 font-weight: $boldFontWeight;9 font-weight: var(--boldFontWeight);
10 @extend %block-label, %label-blue;10 @extend %block-label, %label-blue;
11 11
12 + .method-heading { margin-top: -.5em; }12 + .method-heading { margin-top: -.5em; }
@@ -22,7 +22,7 @@ · 9 unmodified lines
22 a.method-click-advice {22 a.method-click-advice {
23 float: right;23 float: right;
24 font-size: .75rem;24 font-size: .75rem;
25 color: $linkColor;25 color: var(--linkColor);
26 cursor: pointer;26 cursor: pointer;
27 @extend %user-select-none;27 @extend %user-select-none;
28 28
@@ -33,19 +33,8 @@ · 4 unmodified lines
33 }33 }
34 }34 }
35 35
36 .method-description { position: relative; }
37
38 .method-source-code {36 .method-source-code {
39 display: none;37 display: none;
40 position: absolute;
41 z-index: 1;
42 top: 0;
43 left: -1em;
44 right: 0;
45 background: rgba($contentBackground, .95);
46 box-shadow: 0 1em 1em 1em $contentBackground;
47
48 > pre { margin: 0; }
49 }38 }
50 39
51 // Rails guides40 // Rails guides
assets/stylesheets/pages/_react_native.scss+1−1
@@ -2,7 +2,7 @@ · 1 unmodified line
2 @extend %simple;2 @extend %simple;
3 3
4 .deprecated { @extend %note, %note-orange; }4 .deprecated { @extend %note, %note-orange; }
5 .deprecatedTitle { font-weight: $boldFontWeight; }5 .deprecatedTitle { font-weight: var(--boldFontWeight); }
6 6
7 span.platform { float: right; }7 span.platform { float: right; }
8 span.propType, span.platform { font-weight: normal; }8 span.propType, span.platform { font-weight: normal; }
assets/stylesheets/pages/_redis.scss+2−2
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 14
15 .args {15 .args {
16 font-size: .75rem;16 font-size: .75rem;
17 color: $textColorLight;17 color: var(--textColorLight);
18 }18 }
19 19
20 // Others20 // Others
@@ -35,7 +35,7 @@ · 14 unmodified lines
35 > .prompt {35 > .prompt {
36 float: left;36 float: left;
37 margin-right: .5em;37 margin-right: .5em;
38 color: $textColorLight;38 color: var(--textColorLight);
39 }39 }
40 40
41 > code {41 > code {
assets/stylesheets/pages/_rfc.scss+1−1
@@ -10,6 +10,6 @@ · 9 unmodified lines
10 10
11 > h1, > h2, > h3, > h4, > h5 {11 > h1, > h2, > h3, > h4, > h5 {
12 margin: 0;12 margin: 0;
13 font-family: $baseFont;13 font-family: var(--baseFont);
14 }14 }
15}15}
assets/stylesheets/pages/_rust.scss+10−1
@@ -3,9 +3,18 @@ · 2 unmodified lines
3 3
4 h4 { @extend %block-label; }4 h4 { @extend %block-label; }
5 .docblock { margin-left: 1em; }5 .docblock { margin-left: 1em; }
6 div.information, div.important-traits {
7 @extend %note;
8
9 > pre { margin: .5rem 0; }
10 }
6 11
7 div.stability { margin-bottom: 1em; }12 div.stability { margin-bottom: 1em; }
8 em.stab, span.stab { @extend %label; }13 em.stab, span.stab { @extend %label; }
9 em.stab.unstable, span.stab.unstable { @extend %label-orange; }14 em.stab.unstable, span.stab.unstable { @extend %label-orange; }
10 .since, .out-of-band { float: right; }15 .out-of-band { float: right; }
16 .since, .srclink {
17 float: right;
18 margin-left: .5rem;
19 }
11}20}
assets/stylesheets/pages/_scala.scss+4−0
@@ -0,0 +1,4 @@
1._scala {
2 @extend %simple;
3 .deprecated { @extend %label-red; }
4}
assets/stylesheets/pages/_simple.scss+1−31
@@ -17,34 +17,4 @@ · 16 unmodified lines
17 blockquote > h4, blockquote > h5 { margin-top: .25rem; }17 blockquote > h4, blockquote > h5 { margin-top: .25rem; }
18}18}
19 19
20._apache_pig,20._simple { @extend %simple; }
21._bower,
22._chai,
23._codeceptjs,
24._docker,
25._electron,
26._fish,
27._flow,
28._gnu,
29._grunt,
30._haxe,
31._immutable,
32._influxdata,
33._jasmine,
34._jest,
35._less,
36._lodash,
37._marionette,
38._markdown,
39._mocha,
40._mongoose,
41._nim,
42._redux,
43._requirejs,
44._simple,
45._typescript,
46._vagrant,
47._vulkan,
48._yarn {
49 @extend %simple;
50}
assets/stylesheets/pages/_sphinx.scss+2−2
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 14
15 .versionmodified, span.title, .topic-title {15 .versionmodified, span.title, .topic-title {
16 display: block;16 display: block;
17 font-weight: $boldFontWeight;17 font-weight: var(--boldFontWeight);
18 }18 }
19 19
20 p > code, li > code, dd > code, .docutils > dt > code { @extend %label; }20 p > code, li > code, dd > code, .docutils > dt > code { @extend %label; }
@@ -26,7 +26,7 @@ · 5 unmodified lines
26 .admonition-title {26 .admonition-title {
27 float: left;27 float: left;
28 margin: 0 .5em 0 0;28 margin: 0 .5em 0 0;
29 font-weight: $boldFontWeight;29 font-weight: var(--boldFontWeight);
30 30
31 &:after { content: ':'; }31 &:after { content: ':'; }
32 }32 }
assets/stylesheets/pages/_sphinx_simple.scss+1−1
@@ -6,7 +6,7 @@ · 5 unmodified lines
6 .admonition.tip { @extend %note-green; }6 .admonition.tip { @extend %note-green; }
7 .admonition-title {7 .admonition-title {
8 margin: 0 0 .25rem;8 margin: 0 0 .25rem;
9 font-weight: $boldFontWeight;9 font-weight: var(--boldFontWeight);
10 }10 }
11 11
12 code { @extend %label; }12 code { @extend %label; }
assets/stylesheets/pages/_support_tables.scss+1−1
@@ -24,7 +24,7 @@ · 23 unmodified lines
24 }24 }
25 25
26 tr.current {26 tr.current {
27 font-weight: $boldFontWeight;27 font-weight: var(--boldFontWeight);
28 font-size: 1rem;28 font-size: 1rem;
29 }29 }
30 30
assets/stylesheets/pages/_wordpress.scss+15−0
@@ -0,0 +1,15 @@
1._wordpress {
2 @extend %simple;
3
4 .breadcrumbs {
5 display: none;
6 }
7
8 .callout-warning {
9 @extend %note, %note-red;
10 }
11
12 .callout-alert {
13 @extend %note, %note-orange;
14 }
15}
assets/stylesheets/pages/_yard.scss+1−1
@@ -1,5 +1,5 @@
1._yard {1._yard {
2 @extend %simple;2 @extend %simple;
3 3
4 .tag_title { font-weight: $boldFontWeight; }4 .tag_title { font-weight: var(--boldFontWeight); }
5}5}
assets/stylesheets/pages/_yii.scss+1−1
@@ -3,6 +3,6 @@ · 2 unmodified lines
3 3
4 .detail-header-tag, .detailHeaderTag {4 .detail-header-tag, .detailHeaderTag {
5 float: right;5 float: right;
6 color: $textColorLight;6 color: var(--textColorLight);
7 }7 }
8}8}
docs/adding-docs.md+24−0
@@ -0,0 +1,24 @@
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
3**Note:** please read the [contributing guidelines](../.github/CONTRIBUTING.md) before submitting a new documentation.
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`)
62. Add the appropriate class attributes and filter options (see the [Scraper Reference](./scraper-reference.md) page).
73. Check that the scraper is listed in `thor docs:list`.
84. Create filters specific to the scraper in the `lib/docs/filters/[my_doc]/` directory and add them to the class's [filter stacks](./scraper-reference.md#filter-stacks). You may create any number of filters but will need at least the following two:
9 * A [`CleanHtml`](./filter-reference.md#cleanhtmlfilter) filter whose task is to clean the HTML markup (e.g. adding `id` attributes to headings) and remove everything superfluous and/or nonessential.
10 * An [`Entries`](./filter-reference.md#entriesfilter) filter whose task is to determine the pages' metadata (the list of entries, each with a name, type and path).
11 The [Filter Reference](./filter-reference.md) page has all the details about filters.
125. Using the `thor docs:page [my_doc] [path]` command, check that the scraper works properly. Files will appear in the `public/docs/[my_doc]/` directory (but not inside the app as the command doesn't touch the index). `path` in this case refers to either the remote path (if using `UrlScraper`) or the local path (if using `FileScraper`).
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.
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). 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).
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.
21
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.)
23
24Finally, try to document your scraper and filters' behavior as much as possible using comments (e.g. why some URLs are ignored, HTML markup removed, metadata that way, etc.). It'll make updating the documentation much easier.
docs/filter-reference.md+224−0
@@ -0,0 +1,224 @@
1**Table of contents:**
2
3* [Overview](#overview)
4* [Instance methods](#instance-methods)
5* [Core filters](#core-filters)
6* [Custom filters](#custom-filters)
7 - [CleanHtmlFilter](#cleanhtmlfilter)
8 - [EntriesFilter](#entriesfilter)
9
10## Overview
11
12Filters use the [HTML::Pipeline](https://github.com/jch/html-pipeline) library. They take an HTML string or [Nokogiri](http://nokogiri.org/) node as input, optionally perform modifications and/or extract information from it, and then outputs the result. Together they form a pipeline where each filter hands its output to the next filter's input. Every documentation page passes through this pipeline before being copied on the local filesystem.
13
14Filters are subclasses of the [`Docs::Filter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/filter.rb) class and require a `call` method. A basic implementation looks like this:
15
16```ruby
17module Docs
18 class CustomFilter < Filter
19 def call
20 doc
21 end
22 end
23end
24```
25
26Filters which manipulate the Nokogiri node object (`doc` and related methods) are _HTML filters_ and must not manipulate the HTML string (`html`). Vice-versa, filters which manipulate the string representation of the document are _text filters_ and must not manipulate the Nokogiri node object. The two types are divided into two stacks within the scrapers. These stacks are then combined into a pipeline that calls the HTML filters before the text filters (more details [here](./scraper-reference.md#filter-stacks)). This is to avoid parsing the document multiple times.
27
28The `call` method must return either `doc` or `html`, depending on the type of filter.
29
30## Instance methods
31
32* `doc` [Nokogiri::XML::Node]
33 The Nokogiri representation of the container element.
34 See [Nokogiri's API docs](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node) for the list of available methods.
35
36* `html` [String]
37 The string representation of the container element.
38
39* `context` [Hash] **(frozen)**
40 The scraper's `options` along with a few additional keys: `:base_url`, `:root_url`, `:root_page` and `:url`.
41
42* `result` [Hash]
43 Used to store the page's metadata and pass back information to the scraper.
44 Possible keys:
45
46 - `:path` — the page's normalized path
47 - `:store_path` — the path where the page will be stored (equal to `:path` with `.html` at the end)
48 - `:internal_urls` — the list of distinct internal URLs found within the page
49 - `:entries` — the [`Entry`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/models/entry.rb) objects to add to the index
50
51* `css`, `at_css`, `xpath`, `at_xpath`
52 Shortcuts for `doc.css`, `doc.xpath`, etc.
53
54* `base_url`, `current_url`, `root_url` [Docs::URL]
55 Shortcuts for `context[:base_url]`, `context[:url]`, and `context[:root_url]` respectively.
56
57* `root_path` [String]
58 Shortcut for `context[:root_path]`.
59
60* `subpath` [String]
61 The sub-path from the base URL of the current URL.
62 _Example: if `base_url` equals `example.com/docs` and `current_url` equals `example.com/docs/file?raw`, the returned value is `/file`._
63
64* `slug` [String]
65 The `subpath` removed of any leading slash or `.html` extension.
66 _Example: if `subpath` equals `/dir/file.html`, the returned value is `dir/file`._
67
68* `root_page?` [Boolean]
69 Returns `true` if the current page is the root page.
70
71* `initial_page?` [Boolean]
72 Returns `true` if the current page is the root page or its subpath is one of the scraper's `initial_paths`.
73
74## Core filters
75
76* [`ContainerFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/container.rb) — changes the root node of the document (remove everything outside)
77* [`CleanHtmlFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_html.rb) — removes HTML comments, `<script>`, `<style>`, etc.
78* [`NormalizeUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_urls.rb) — replaces all URLs with their fully qualified counterpart
79* [`InternalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/internal_urls.rb) — detects internal URLs (the ones to scrape) and replaces them with their unqualified, relative counterpart
80* [`NormalizePathsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_paths.rb) — makes the internal paths consistent (e.g. always end with `.html`)
81* [`CleanLocalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_local_urls.rb) — removes links, iframes and images pointing to localhost (`FileScraper` only)
82* [`InnerHtmlFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/inner_html.rb) — converts the document to a string
83* [`CleanTextFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_text.rb) — removes empty nodes
84* [`AttributionFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/attribution.rb) — appends the license info and link to the original document
85* [`TitleFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/title.rb) — prepends the document with a title (disabled by default)
86* [`EntriesFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/entries.rb) — abstract filter for extracting the page's metadata
87
88## Custom filters
89
90Scrapers can have any number of custom filters but require at least the two described below.
91
92**Note:** filters are located in the [`lib/docs/filters`](https://github.com/Thibaut/devdocs/tree/master/lib/docs/filters/) directory. The class's name must be the [CamelCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename.
93
94### `CleanHtmlFilter`
95
96The `CleanHtml` filter is tasked with cleaning the HTML markup where necessary and removing anything superfluous or nonessential. Only the core documentation should remain at the end.
97
98Nokogiri's many jQuery-like methods make it easy to search and modify elements — see the [API docs](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node).
99
100Here's an example implementation that covers the most common use-cases:
101
102```ruby
103module Docs
104 class MyScraper
105 class CleanHtmlFilter < Filter
106 def call
107 css('hr').remove
108 css('#changelog').remove if root_page?
109
110 # Set id attributes on <h3> instead of an empty <a>
111 css('h3').each do |node|
112 node['id'] = node.at_css('a')['id']
113 end
114
115 # Make proper table headers
116 css('td.header').each do |node|
117 node.name = 'th'
118 end
119
120 # Remove code highlighting
121 css('pre').each do |node|
122 node.content = node.content
123 end
124
125 doc
126 end
127 end
128 end
129end
130```
131
132**Notes:**
133
134* Empty elements will be automatically removed by the core `CleanTextFilter` later in the pipeline's execution.
135* Although the goal is to end up with a clean version of the page, try to keep the number of modifications to a minimum, so as to make the code easier to maintain. Custom CSS is the preferred way of normalizing the pages (except for hiding stuff which should always be done by removing the markup).
136* Try to document your filter's behavior as much as possible, particularly modifications that apply only to a subset of pages. It'll make updating the documentation easier.
137
138### `EntriesFilter`
139
140The `Entries` filter is responsible for extracting the page's metadata, represented by a set of _entries_, each with a name, type and path.
141
142The following two models are used under the hood to represent the metadata:
143
144* [`Entry(name, type, path)`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/models/entry.rb)
145* [`Type(name, slug, count)`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/models/type.rb)
146
147Each scraper must implement its own `EntriesFilter` by subclassing the [`Docs::EntriesFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/entries.rb) class. The base class already implements the `call` method and includes four methods which the subclasses can override:
148
149* `get_name` [String]
150 The name of the default entry (aka. the page's name).
151 It is usually guessed from the `slug` (documented above) or by searching the HTML markup.
152 **Default:** modified version of `slug` (underscores are replaced with spaces and forward slashes with dots)
153
154* `get_type` [String]
155 The type of the default entry (aka. the page's type).
156 Entries without a type can be searched for but won't be listed in the app's sidebar (unless no other entries have a type).
157 **Default:** `nil`
158
159* `include_default_entry?` [Boolean]
160 Whether to include the default entry.
161 Used when a page consists of multiple entries (returned by `additional_entries`) but doesn't have a name/type of its own, or to remove a page from the index (if it has no additional entries), in which case it won't be copied on the local filesystem and any link to it in the other pages will be broken (as explained on the [Scraper Reference](./scraper-reference.md) page, this is used to keep the `:skip` / `:skip_patterns` options to a maintainable size, or if the page includes links that can't reached from anywhere else).
162 **Default:** `true`
163
164* `additional_entries` [Array]
165 The list of additional entries.
166 Each entry is represented by an Array of three attributes: its name, fragment identifier, and type. The fragment identifier refers to the `id` attribute of the HTML element (usually a heading) that the entry relates to. It is combined with the page's path to become the entry's path. If absent or `nil`, the page's path is used. If the type is absent or `nil`, the default `type` is used.
167 Example: `[ ['One'], ['Two', 'id'], ['Three', nil, 'type'] ]` adds three additional entries, the first one named "One" with the default path and type, the second one named "Two" with the URL fragment "#id" and the default type, and the third one named "Three" with the default path and the type "type".
168 The list is usually constructed by running through the markup. Exceptions can also be hard-coded for specific pages.
169 **Default:** `[]`
170
171The following accessors are also available, but must not be overridden:
172
173* `name` [String]
174 Memoized version of `get_name` (`nil` for the root page).
175
176* `type` [String]
177 Memoized version of `get_type` (`nil` for the root page).
178
179**Notes:**
180
181* Leading and trailing whitespace is automatically removed from names and types.
182* Names must be unique across the documentation and as short as possible (ideally less than 30 characters). Whenever possible, methods should be differentiated from properties by appending `()`, and instance methods should be differentiated from class methods using the `Class#method` or `object.method` conventions.
183* You can call `name` from `get_type` or `type` from `get_name` but doing both will cause a stack overflow (i.e. you can infer the name from the type or the type from the name, but you can't do both at the same time). Don't call `get_name` or `get_type` directly as their value isn't memoized.
184* The root page has no name and no type (both are `nil`). `get_name` and `get_type` won't get called with the page (but `additional_entries` will).
185* `Docs::EntriesFilter` is an _HTML filter_. It must be added to the scraper's `html_filters` stack.
186* Try to document the code as much as possible, particularly the special cases. It'll make updating the documentation easier.
187
188**Example:**
189
190```ruby
191module Docs
192 class MyScraper
193 class EntriesFilter < Docs::EntriesFilter
194 def get_name
195 node = at_css('h1')
196 result = node.content.strip
197 result << ' event' if type == 'Events'
198 result << '()' if node['class'].try(:include?, 'function')
199 result
200 end
201
202 def get_type
203 object, method = *slug.split('/')
204 method ? object : 'Miscellaneous'
205 end
206
207 def additional_entries
208 return [] if root_page?
209
210 css('h2').map do |node|
211 [node.content, node['id']]
212 end
213 end
214
215 def include_default_entry?
216 !at_css('.obsolete')
217 end
218 end
219 end
220end
221```
222
223
224return [[Home]]
docs/maintainers.md+102−0
@@ -0,0 +1,102 @@
1# Maintainer's Guide
2
3This document is intended for [DevDocs maintainers](#list-of-maintainers).
4
5## Merging pull requests
6
7- PRs should be approved by at least one maintainer before being merged.
8
9- PRs that add or update documentations should always be merged locally, and the app deployed, before the merge is pushed to GitHub.
10
11 This workflow is required because there is a dependency between the local and production environments. The `thor docs:download` command downloads documentations from production files uploaded by the `thor docs:upload` command. If a PR adding a new documentation is merged and pushed to GitHub before the files have been uploaded to production, the `thor docs:download` will fail for the new documentation and the docker container will not build properly until the new documentation is deployed to production.
12
13## Updating docs
14
15The process for updating docs is as follow:
16
17- Make version/release changes in the scraper file.
18- If needed, update the copyright notice of the documentation in the scraper file (`options[:attribution]`) and the about page (`about_tmpl.coffee`). The copyright notice must be the same as the one on the original documentation.
19- Run `thor docs:generate`.
20- Make sure the documentation still works well. The `thor docs:generate` command outputs a summary of the changes, which helps identifying issues (e.g. deleted files) and new pages to check out in the app. Verify locally that everything works, especially the files that were created (if any), and that the categorization of entries is still good. Often, updates will require code changes to tweak some new markup in the source website or categorize new entries.
21- Commit the changes (protip: use the `thor docs:commit` command documented below).
22- Optional: do more updates.
23- Run `thor docs:upload` (documented below).
24- Push to GitHub to [deploy the app](#deploying-devdocs) and verify that everything works in production.
25- Run `thor docs:clean` (documented below).
26
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`.
28
29## Setup requirements
30
31In order to deploy DevDocs, you must:
32
33- be given access to Heroku, [configure the Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) on your computer, and familiarize yourself with Heroku's UI and CLI, as well as that of New Relic (accessible through [the Heroku dashboard](https://dashboard.heroku.com/apps/devdocs)).
34
35- be given access to DevDocs's [Sentry instance](https://sentry.io/devdocs/devdocs-js/) (for JS error tracking) and familiarize yourself with its UI.
36
37- be provided with DevDocs's S3 credentials, and install (`brew install awscli` on macOS) and [configure](https://docs.aws.amazon.com/cli/latest/reference/configure/) the AWS CLI on your computer. The configuration must add a named profile called "devdocs":
38 ```
39 aws configure --profile devdocs
40 ```
41
42- be provided with DevDocs's MaxCDN push zone credentials, and add them to your `.bash_profile` as such:
43 ```
44 export DEVDOCS_DL_USERNAME="username"
45 export DEVDOCS_DL_PASSWORD="password"
46 ```
47
48## Thor commands
49
50In addition to the [publicly-documented commands](https://github.com/freeCodeCamp/devdocs#available-commands), the following commands are aimed at DevDocs maintainers:
51
52- `thor docs:package`
53
54 Generates packages for one or more documentations. Those packages are intended to be uploaded to DevDocs's MaxCDN push zone by maintainers via the `thor docs:upload` command, and downloaded by users via the `thor docs:download` command.
55
56 Versions can be specified as such: `thor docs:package rails@5.2 node@10\ LTS`.
57
58 Packages can also be automatically generated during the scraping process by passing the `--package` option to `thor docs:generate`.
59
60- `thor docs:upload`
61
62 This command does two operations:
63
64 1. sync the files for the specified documentations with S3 (used by the Heroku app);
65 2. upload the documentations' packages to DevDocs's MaxCDN push zone (used by the `thor docs:download` command).
66
67 For the command to work, you must have the AWS CLI and MaxCDN credentials configured as indicated above.
68
69 **Important:** the app should always be deployed immediately after this command has finished running. Do not run this command unless you are able and ready to deploy DevDocs.
70
71 To upload all documentations that are packaged on your computer, run `thor docs:upload --packaged`.
72 To test your configuration and the effect of this command without uploading anything, pass the `--dryrun` option.
73
74- `thor docs:commit`
75
76 Shortcut command to create a Git commit for a given documentation once it has been updated. Scraper and `assets/` file changes will be committed. The commit message will include the most recent version that the documentation was updated to. If some files were missed by the commit, use `git commit --amend` to add them to the commit. The command may be run before `thor docs:upload` is run, but the commit should not be pushed to GitHub before the files have been uploaded and the app deployed.
77
78- `thor docs:clean`
79
80 Shortcut command to delete all package files (once uploaded via `thor docs:upload`, they are not needed anymore).
81
82## Deploying DevDocs
83
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.
85
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.
87- If you're deploying frontend changes, monitor [Sentry](https://sentry.io/devdocs/devdocs-js/) for new JS errors 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.
89
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.
91
92If this is your first deploy, make sure another maintainer is around to assist.
93
94## List of maintainers
95
96- [Jed Fox](https://github.com/j-f1)
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)
101
102Interested in helping maintain DevDocs? Come talk to us on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs) :)
docs/scraper-reference.md+227−0
@@ -0,0 +1,227 @@
1**Table of contents:**
2
3* [Overview](#overview)
4* [Configuration](#configuration)
5 - [Attributes](#attributes)
6 - [Filter stacks](#filter-stacks)
7 - [Filter options](#filter-options)
8
9## Overview
10
11Starting from a root URL, scrapers recursively follow links that match a set of rules, passing each valid response through a chain of filters before writing the file on the local filesystem. They also create an index of the pages' metadata (determined by one filter), which is dumped into a JSON file at the end.
12
13Scrapers rely on the following libraries:
14
15* [Typhoeus](https://github.com/typhoeus/typhoeus) for making HTTP requests
16* [HTML::Pipeline](https://github.com/jch/html-pipeline) for applying filters
17* [Nokogiri](http://nokogiri.org/) for parsing HTML
18
19There are currently two kinds of scrapers: [`UrlScraper`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/scrapers/url_scraper.rb) which downloads files via HTTP and [`FileScraper`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/scrapers/file_scraper.rb) which reads them from the local filesystem. They function almost identically (both use URLs), except that `FileScraper` substitutes the base URL with a local path before reading a file. `FileScraper` uses the placeholder `localhost` base URL by default and includes a filter to remove any URL pointing to it at the end.
20
21To be processed, a response must meet the following requirements:
22
23* 200 status code
24* HTML content type
25* effective URL (after redirection) contained in the base URL (explained below)
26
27(`FileScraper` only checks if the file exists and is not empty.)
28
29Each URL is requested only once (case-insensitive).
30
31## Configuration
32
33Configuration is done via class attributes and divided into three main categories:
34
35* [Attributes](#attributes) — essential information such as name, version, URL, etc.
36* [Filter stacks](#filter-stacks) — the list of filters that will be applied to each page.
37* [Filter options](#filter-options) — the options passed to said filters.
38
39**Note:** scrapers are located in the [`lib/docs/scrapers`](https://github.com/Thibaut/devdocs/tree/master/lib/docs/scrapers/) directory. The class's name must be the [CamelCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename.
40
41### Attributes
42
43* `name` [String]
44 Must be unique.
45 Defaults to the class's name.
46
47* `slug` [String]
48 Must be unique, lowercase, and not include dashes (underscores are ok).
49 Defaults to `name` lowercased.
50
51* `type` [String] **(required, inherited)**
52 Defines the CSS class name (`_[type]`) and custom JavaScript class (`app.views.[Type]Page`) that will be added/loaded on each page. Documentations sharing a similar structure (e.g. generated with the same tool or originating from the same website) should use the same `type` to avoid duplicating the CSS and JS.
53 Must include lowercase letters only.
54
55* `release` [String] **(required)**
56 The version of the software at the time the scraper was last run. This is only informational and doesn't affect the scraper's behavior.
57
58* `base_url` [String] **(required in `UrlScraper`)**
59 The documents' location. Only URLs _inside_ the `base_url` will be scraped. "inside" more or less means "starting with" except that `/docs` is outside `/doc` (but `/doc/` is inside).
60 Defaults to `localhost` in `FileScraper`. _(Note: any iframe, image, or skipped link pointing to localhost will be removed by the `CleanLocalUrls` filter; the value should be overridden if the documents are available online.)_
61 Unless `root_path` is set, the root/initial URL is equal to `base_url`.
62
63* `root_path` [String] **(inherited)**
64 The path from the `base_url` of the root URL.
65
66* `initial_paths` [Array] **(inherited)**
67 A list of paths (from the `base_url`) to add to the initial queue. Useful for scraping isolated documents.
68 Defaults to `[]`. _(Note: the `root_path` is added to the array at runtime.)_
69
70* `dir` [String] **(required, `FileScraper` only)**
71 The absolute path where the files are located on the local filesystem.
72 _Note: `FileScraper` works exactly like `UrlScraper` (manipulating the same kind of URLs) except that it substitutes `base_url` with `dir` in order to read files instead of making HTTP requests._
73
74* `params` [Hash] **(inherited, `UrlScraper` only)**
75 Query string parameters to append to every URL. (e.g. `{ format: 'raw' }` → `?format=raw`)
76 Defaults to `{}`.
77
78* `abstract` [Boolean]
79 Make the scraper abstract / not runnable. Used for sharing behavior with other scraper classes (e.g. all MDN scrapers inherit from the abstract [`Mdn`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/scrapers/mdn/mdn.rb) class).
80 Defaults to `false`.
81
82### Filter stacks
83
84Each scraper has two [filter](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/filter.rb) [stacks](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/filter_stack.rb): `html_filters` and `text_filters`. They are combined into a pipeline (using the [HTML::Pipeline](https://github.com/jch/html-pipeline) library) which causes each filter to hand its output to the next filter's input.
85
86HTML filters are executed first and manipulate a parsed version of the document (a [Nokogiri](http://nokogiri.org/Nokogiri/XML/Node.html) node object), whereas text filters manipulate the document as a string. This separation avoids parsing the document multiple times.
87
88Filter stacks are like sorted sets. They can modified using the following methods:
89
90```ruby
91push(*names) # append one or more filters at the end
92insert_before(index, *names) # insert one filter before another (index can be a name)
93insert_after(index, *names) # insert one filter after another (index can be a name)
94replace(index, name) # replace one filter with another (index can be a name)
95```
96
97"names" are `require` paths relative to `Docs` (e.g. `jquery/clean_html` → `Docs::Jquery::CleanHtml`).
98
99Default `html_filters`:
100
101* [`ContainerFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/container.rb) — changes the root node of the document (remove everything outside)
102* [`CleanHtmlFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_html.rb) — removes HTML comments, `<script>`, `<style>`, etc.
103* [`NormalizeUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_urls.rb) — replaces all URLs with their fully qualified counterpart
104* [`InternalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/internal_urls.rb) — detects internal URLs (the ones to scrape) and replaces them with their unqualified, relative counterpart
105* [`NormalizePathsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_paths.rb) — makes the internal paths consistent (e.g. always end with `.html`)
106* [`CleanLocalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_local_urls.rb) — removes links, iframes and images pointing to localhost (`FileScraper` only)
107
108Default `text_filters`:
109
110* [`InnerHtmlFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/inner_html.rb) — converts the document to a string
111* [`CleanTextFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_text.rb) — removes empty nodes
112* [`AttributionFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/attribution.rb) — appends the license info and link to the original document
113
114Additionally:
115
116* [`TitleFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/title.rb) is a core HTML filter, disabled by default, which prepends the document with a title (`<h1>`).
117* [`EntriesFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/entries.rb) is an abstract HTML filter that each scraper must implement and responsible for extracting the page's metadata.
118
119### Filter options
120
121The filter options are stored in the `options` Hash. The Hash is inheritable (a recursive copy) and empty by default.
122
123More information about how filters work is available on the [Filter Reference](./filter-reference.md) page.
124
125* [`ContainerFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/container.rb)
126
127 - `:container` [String or Proc]
128 A CSS selector of the container element. Everything outside of it will be removed and become unavailable to the other filters. If more than one element match the selector, the first one inside the DOM is used. If no elements match the selector, an error is raised.
129 If the value is a Proc, it is called for each page with the filter instance as argument, and should return a selector or `nil`.
130 The default container is the `<body>` element.
131 _Note: links outside of the container element will not be followed by the scraper. To remove links that should be followed, use a [`CleanHtml`](./filter-reference.md#cleanhtmlfilter) filter later in the stack._
132
133* [`NormalizeUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_urls.rb)
134 The following options are used to modify URLs in the pages. They are useful to remove duplicates (when the same page is accessible from multiple URLs) and fix websites that have a bunch of redirections in place (when URLs that should be scraped, aren't, because they are behind a redirection which is outside of the `base_url` — see the MDN scrapers for examples of this).
135
136 - `:replace_urls` [Hash]
137 Replaces all instances of a URL with another.
138 Format: `{ 'original_url' => 'new_url' }`
139 - `:replace_paths` [Hash]
140 Replaces all instances of a sub-path (path from the `base_url`) with another.
141 Format: `{ 'original_path' => 'new_path' }`
142 - `:fix_urls` [Proc]
143 Called with each URL. If the returned value is `nil`, the URL isn't modified. Otherwise the returned value is used as replacement.
144
145 _Note: before these rules are applied, all URLs are converted to their fully qualified counterpart (http://...)._
146
147* [`InternalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/internal_urls.rb)
148
149 Internal URLs are the ones _inside_ the scraper's `base_url` ("inside" more or less means "starting with", except that `/docs` is outside `/doc`). They will be scraped unless excluded by one of the following rules. All internal URLs are converted to relative URLs inside the pages.
150
151 - `:skip_links` [Boolean or Proc]
152 If `false`, does not convert or follow any internal URL (creating a single-page documentation).
153 If the value is a Proc, it is called for each page with the filter instance as argument.
154 - `:follow_links` [Proc]
155 Called for page with the filter instance as argument. If the returned value is `false`, does not add internal URLs to the queue.
156 - `:trailing_slash` [Boolean]
157 If `true`, adds a trailing slash to all internal URLs. If `false`, removes it.
158 This is another option used to remove duplicate pages.
159 - `:skip` [Array]
160 Ignores internal URLs whose sub-paths (path from the `base_url`) are in the Array (case-insensitive).
161 - `:skip_patterns` [Array]
162 Ignores internal URLs whose sub-paths match any Regexp in the Array.
163 - `:only` [Array]
164 Ignores internal URLs whose sub-paths aren't in the Array (case-insensitive) and don't match any Regexp in `:only_patterns`.
165 - `:only_patterns` [Array]
166 Ignores internal URLs whose sub-paths don't match any Regexp in the Array and aren't in `:only`.
167
168 If the scraper has a `root_path`, the empty and `/` paths are automatically skipped.
169 If `:only` or `:only_patterns` is set, the root path is automatically added to `:only`.
170
171 _Note: pages can be excluded from the index based on their content using the [`Entries`](./filter-reference.md#entriesfilter) filter. However, their URLs will still be converted to relative in the other pages and trying to open them will return a 404 error. Although not ideal, this is often better than having to maintain a long list of `:skip` URLs._
172
173* [`AttributionFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/attribution.rb)
174
175 - `:attribution` [String] **(required)**
176 An HTML string with the copyright and license information. See the other scrapers for examples.
177
178* [`TitleFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/title.rb)
179
180 - `:title` [String or Boolean or Proc]
181 Unless the value is `false`, adds a title to every page.
182 If the value is `nil`, the title is the name of the page as determined by the [`Entries`](./filter-reference.md#entriesfilter) filter. Otherwise the title is the String or the value returned by the Proc (called for each page, with the filter instance as argument). If the Proc returns `nil` or `false`, no title is added.
183 - `:root_title` [String or Boolean]
184 Overrides the `:title` option for the root page only.
185
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+45−41
@@ -12,6 +12,8 @@ class App < Sinatra::Application · 11 unmodified lines
12 Rack::Mime::MIME_TYPES['.webapp'] = 'application/x-web-app-manifest+json'12 Rack::Mime::MIME_TYPES['.webapp'] = 'application/x-web-app-manifest+json'
13 13
14 configure do14 configure do
15 use Rack::SslEnforcer, only_environments: ['production', 'test'], hsts: true, force_secure_cookies: false
16
15 set :sentry_dsn, ENV['SENTRY_DSN']17 set :sentry_dsn, ENV['SENTRY_DSN']
16 set :protection, except: [:frame_options, :xss_header]18 set :protection, except: [:frame_options, :xss_header]
17 19
@@ -35,6 +37,9 @@ class App < Sinatra::Application · 17 unmodified lines
35 37
36 set :csp, false38 set :csp, false
37 39
40 require 'docs'
41 Docs.generate_manifest
42
38 Dir[docs_path, root.join(assets_prefix, '*/')].each do |path|43 Dir[docs_path, root.join(assets_prefix, '*/')].each do |path|
39 sprockets.append_path(path)44 sprockets.append_path(path)
40 end45 end
@@ -48,6 +53,11 @@ class App < Sinatra::Application · 7 unmodified lines
48 end53 end
49 54
50 configure :test, :development do55 configure :test, :development do
56 require 'thor'
57 load 'tasks/sprites.thor'
58
59 SpritesCLI.new.invoke(:generate)
60
51 require 'active_support/per_thread_registry'61 require 'active_support/per_thread_registry'
52 require 'active_support/cache'62 require 'active_support/cache'
53 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)
@@ -67,7 +77,7 @@ class App < Sinatra::Application · 13 unmodified lines
67 set :static, false77 set :static, false
68 set :cdn_origin, 'https://cdn.devdocs.io'78 set :cdn_origin, 'https://cdn.devdocs.io'
69 set :docs_origin, '//docs.devdocs.io'79 set :docs_origin, '//docs.devdocs.io'
70 set :csp, "default-src 'self' *; script-src 'self' 'nonce-devdocs' http://cdn.devdocs.io https://cdn.devdocs.io https://www.google-analytics.com https://secure.gaug.es http://*.jquery.com https://*.jquery.com; font-src 'none'; style-src 'self' 'unsafe-inline' *; img-src 'self' * data:;"80 set :csp, "default-src 'self' *; script-src 'self' 'nonce-devdocs' https://cdn.devdocs.io https://www.google-analytics.com https://secure.gaug.es https://*.jquery.com; font-src 'none'; style-src 'self' 'unsafe-inline' *; img-src 'self' * data:;"
71 81
72 use Rack::ConditionalGet82 use Rack::ConditionalGet
73 use Rack::ETag83 use Rack::ETag
@@ -190,51 +200,45 @@ class App < Sinatra::Application · 116 unmodified lines
190 request.query_string.empty? ? nil : "?#{request.query_string}"200 request.query_string.empty? ? nil : "?#{request.query_string}"
191 end201 end
192 202
193 def manifest_asset_urls203 def service_worker_asset_urls
194 @@manifest_asset_urls ||= [204 @@service_worker_asset_urls ||= [
195 javascript_path('application', asset_host: false),205 javascript_path('application', asset_host: false),
196 stylesheet_path('application'),206 stylesheet_path('application'),
197 stylesheet_path('application-dark'),207 image_path('sprites/docs.png'),
198 image_path('docs-1.png'),208 image_path('sprites/docs@2x.png'),
199 image_path('docs-1@2x.png'),209 asset_path('docs.js'),
200 image_path('docs-2.png'),210 App.production? ? nil : javascript_path('debug'),
201 image_path('docs-2@2x.png'),211 ].compact
202 asset_path('docs.js')
203 ]
204 end
205
206 def main_stylesheet_path
207 stylesheet_paths[dark_theme? ? :dark : :default]
208 end
209
210 def alternate_stylesheet_path
211 stylesheet_paths[dark_theme? ? :default : :dark]
212 end212 end
213 213
214 def stylesheet_paths214 # Returns a cache name for the service worker to use which changes if any of the assets changes
215 @@stylesheet_paths ||= {215 # When a manifest exist, this name is only created once based on the asset manifest because it never changes without a server restart
216 default: stylesheet_path('application'),216 # 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 dark: stylesheet_path('application-dark')217 def service_worker_cache_name
218 }218 if File.exist?(App.assets_manifest_path)
219 end219 if defined?(@@service_worker_cache_name)
220 220 return @@service_worker_cache_name
221 def app_size221 end
222 @app_size ||= memoized_cookies['size'].nil? ? '20rem' : "#{memoized_cookies['size']}px"
223 end
224 222
225 def app_layout223 digest = Sprockets::Manifest
226 memoized_cookies['layout']224 .new(nil, App.assets_manifest_path)
227 end225 .files
226 .values
227 .map {|file| file["digest"]}
228 .join
228 229
229 def app_theme230 return @@service_worker_cache_name ||= Digest::MD5.hexdigest(digest)
230 @app_theme ||= memoized_cookies['dark'].nil? ? 'default' : 'dark'231 else
231 end232 paths = App.sprockets
233 .each_file
234 .to_a
235 .reject {|file| file.start_with?(App.docs_path)}
232 236
233 def dark_theme?237 return App.sprockets.pack_hexdigest(App.sprockets.files_digest(paths))
234 app_theme == 'dark'238 end
235 end239 end
236 240
237 def redirect_via_js(path) # courtesy of HTML5 App Cache241 def redirect_via_js(path)
238 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: '/'
239 redirect '/', 302243 redirect '/', 302
240 end244 end
@@ -257,15 +261,15 @@ class App < Sinatra::Application · 16 unmodified lines
257 end261 end
258 end262 end
259 263
260 get '/manifest.appcache' do264 get '/service-worker.js' do
261 content_type 'text/cache-manifest'265 content_type 'application/javascript'
262 expires 0, :'no-cache'266 expires 0, :'no-cache'
263 erb :manifest267 erb :'service-worker.js'
264 end268 end
265 269
266 get '/' do270 get '/' do
267 return redirect "/#q=#{params[:q]}" if params[:q]271 return redirect "/#q=#{params[:q]}" if params[:q]
268 return redirect '/' unless request.query_string.empty? # courtesy of HTML5 App Cache272 return redirect '/' unless request.query_string.empty?
269 response.headers['Content-Security-Policy'] = settings.csp if settings.csp273 response.headers['Content-Security-Policy'] = settings.csp if settings.csp
270 erb :index274 erb :index
271 end275 end
lib/docs.rb+18−1
@@ -1,5 +1,5 @@
1require 'bundler/setup'1require 'bundler/setup'
2Bundler.require :docs2Bundler.require :default, :docs
3 3
4require 'active_support'4require 'active_support'
5require 'active_support/core_ext'5require 'active_support/core_ext'
@@ -29,6 +29,7 @@ module Docs · 23 unmodified lines
29 self.rescue_errors = false29 self.rescue_errors = false
30 30
31 class DocNotFound < NameError; end31 class DocNotFound < NameError; end
32 class SetupError < StandardError; end
32 33
33 def self.all34 def self.all
34 Dir["#{root_path}/docs/scrapers/**/*.rb"].35 Dir["#{root_path}/docs/scrapers/**/*.rb"].
@@ -74,6 +75,22 @@ module Docs · 39 unmodified lines
74 end75 end
75 end76 end
76 77
78 def self.find_by_slug(slug, version = nil)
79 doc = all.find { |klass| klass.slug == slug }
80
81 unless doc
82 raise DocNotFound.new(%(could not find doc with "#{slug}"), slug)
83 end
84
85 if version.present?
86 version = doc.versions.find { |klass| klass.version == version || klass.version_slug == version }
87 raise DocNotFound.new(%(could not find version "#{version}" for doc "#{doc.name}"), doc.name) unless version
88 doc = version
89 end
90
91 doc
92 end
93
77 def self.generate_page(name, version, page_id)94 def self.generate_page(name, version, page_id)
78 find(name, version).store_page(store, page_id)95 find(name, version).store_page(store, page_id)
79 end96 end
lib/docs/core/doc.rb+105−1
@@ -95,6 +95,9 @@ module Docs · 94 unmodified lines
95 false95 false
96 end96 end
97 end97 end
98 rescue Docs::SetupError => error
99 puts "ERROR: #{error.message}"
100 false
98 end101 end
99 102
100 def store_pages(store)103 def store_pages(store)
@@ -118,6 +121,9 @@ module Docs · 17 unmodified lines
118 false121 false
119 end122 end
120 end123 end
124 rescue Docs::SetupError => error
125 puts "ERROR: #{error.message}"
126 false
121 end127 end
122 128
123 private129 private
@@ -146,7 +152,6 @@ module Docs · 22 unmodified lines
146 end152 end
147 end153 end
148 154
149
150 def initialize155 def initialize
151 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
152 end157 end
@@ -158,5 +163,104 @@ module Docs · 5 unmodified lines
158 def build_pages(&block)163 def build_pages(&block)
159 raise NotImplementedError164 raise NotImplementedError
160 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
161 end265 end
162end266end
lib/docs/core/requester.rb+8−4
@@ -1,5 +1,7 @@
1module Docs1module Docs
2 class Requester < Typhoeus::Hydra2 class Requester < Typhoeus::Hydra
3 include Instrumentable
4
3 attr_reader :request_options5 attr_reader :request_options
4 6
5 def self.run(urls, options = {}, &block)7 def self.run(urls, options = {}, &block)
@@ -20,7 +22,7 @@ module Docs · 14 unmodified lines
20 def initialize(options = {})22 def initialize(options = {})
21 @request_options = options.extract!(:request_options)[:request_options].try(:dup) || {}23 @request_options = options.extract!(:request_options)[:request_options].try(:dup) || {}
22 options[:max_concurrency] ||= 2024 options[:max_concurrency] ||= 20
23 options[:pipelining] = false25 options[:pipelining] = 0
24 super26 super
25 end27 end
26 28
@@ -52,9 +54,11 @@ module Docs · 25 unmodified lines
52 end54 end
53 55
54 def handle_response(response)56 def handle_response(response)
55 on_response.each do |callback|57 instrument 'handle_response.requester', url: response.url do
56 result = callback.call(response)58 on_response.each do |callback|
57 result.each { |url| request(url) } if result.is_a? Array59 result = callback.call(response)
60 result.each { |url| request(url) } if result.is_a?(Array)
61 end
58 end62 end
59 end63 end
60 end64 end
lib/docs/core/scrapers/file_scraper.rb+17−6
@@ -1,14 +1,13 @@
1module Docs1module Docs
2 class FileScraper < Scraper2 class FileScraper < Scraper
3 SOURCE_DIRECTORY = File.expand_path '../../../../../docs', __FILE__
4
3 Response = Struct.new :body, :url5 Response = Struct.new :body, :url
4 6
5 class << self7 class << self
6 attr_accessor :dir
7
8 def inherited(subclass)8 def inherited(subclass)
9 super9 super
10 subclass.base_url = base_url10 subclass.base_url = base_url
11 subclass.dir = dir
12 end11 end
13 end12 end
14 13
@@ -16,13 +15,25 @@ module Docs · 1 unmodified line
16 15
17 html_filters.push 'clean_local_urls'16 html_filters.push 'clean_local_urls'
18 17
18 def source_directory
19 @source_directory ||= File.join(SOURCE_DIRECTORY, self.class.path)
20 end
21
19 private22 private
20 23
24 def assert_source_directory_exists
25 unless Dir.exists?(source_directory)
26 raise SetupError, "The #{self.class.name} scraper requires the original documentation files to be stored in the \"#{source_directory}\" directory."
27 end
28 end
29
21 def request_one(url)30 def request_one(url)
22 Response.new read_file(file_path_for(url)), URL.parse(url)31 assert_source_directory_exists
32 Response.new read_file(File.join(source_directory, url_to_path(url))), URL.parse(url)
23 end33 end
24 34
25 def request_all(urls)35 def request_all(urls)
36 assert_source_directory_exists
26 queue = [urls].flatten37 queue = [urls].flatten
27 until queue.empty?38 until queue.empty?
28 result = yield request_one(queue.shift)39 result = yield request_one(queue.shift)
@@ -34,8 +45,8 @@ module Docs · 5 unmodified lines
34 response.body.present?45 response.body.present?
35 end46 end
36 47
37 def file_path_for(url)48 def url_to_path(url)
38 File.join self.class.dir, url.remove(base_url.to_s)49 url.remove(base_url.to_s)
39 end50 end
40 51
41 def read_file(path)52 def read_file(path)
lib/docs/core/url.rb+1−1
@@ -72,7 +72,7 @@ module Docs · 71 unmodified lines
72 72
73 if base == dest73 if base == dest
74 ''74 ''
75 elsif dest.start_with? File.join(base, '')75 elsif dest.start_with?(::File.join(base, ''))
76 url.path[(path.length)..-1]76 url.path[(path.length)..-1]
77 end77 end
78 end78 end
lib/docs/filters/bash/clean_html.rb+74−0
@@ -0,0 +1,74 @@
1module Docs
2 class Bash
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove the navigation header and footer and the lines underneath and above it
6 at_css('.header + hr').remove
7 line_above = at_xpath('//div[@class="header"]/preceding::hr[1]')
8 line_above.remove unless line_above.nil?
9 css('.header').remove
10
11 # Remove chapter and section numbers from title
12 title_node = at_css('h1, h2, h3, h4, h5, h6')
13 title_node.content = title_node.content.gsub(/(\d+\.?)+/, '').strip
14 title_node.name = 'h1'
15
16 # Remove the "D. " from names like "D. Concept Index" and "D. Function Index"
17 title_node.content = title_node.content[3..-1] if title_node.content.start_with?("D. ")
18
19 # Remove columns containing a single space from tables
20 # In the original reference they are used to add width between two columns
21 xpath('//td[text()=" " and not(descendant::*)]').remove
22
23 # Add id's to additional entry nodes
24 css('dl > dt > code').each do |node|
25 # Only take the direct text (i.e. "<div>Hello <span>World</span></div>" becomes "Hello")
26 node['id'] = node.xpath('text()').to_s.strip
27 end
28
29 # Fix hashes of index entries so they link to the correct hash on the linked page
30 css('table[class^=index-] td[valign=top] > a').each do |node|
31 path = node['href'].split('#')[0]
32 hash = node.content
33
34 # Fix the index entries linking to the Special Parameters page
35 # There are multiple index entries that should link to the same paragraph on that page
36 # Example: the documentation for "$!" is equal to the documentation for "!"
37 if path.downcase.include?('special-parameters')
38 if hash.size > 1 && hash[0] == '$'
39 hash = hash[1..-1]
40 end
41 end
42
43 node['href'] = path + '#' + hash
44 end
45
46 # Fix index table letter hashes (the "Jump to" hashes)
47 css('table[class^=index-] th > a').each do |node|
48 node['id'] = node['name']
49 end
50
51 # Remove the rows with a horizontal line in them from the index tables
52 css('td[colspan="4"]').remove
53
54 # Remove additional text from menu entry and index entry cells
55 css('td[valign=top]').each do |node|
56 link = node.at_css('a')
57 node.children = link unless link.nil?
58 end
59
60 css('tt', 'code', 'table').remove_attr('class')
61
62 css('tt').each do |node|
63 node.name = 'code'
64 end
65
66 css('pre').each do |node|
67 node.content = node.content
68 end
69
70 doc
71 end
72 end
73 end
74end
lib/docs/filters/bash/entries.rb+56−0
@@ -0,0 +1,56 @@
1module Docs
2 class Bash
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 name = at_css('hr + a + *').content.gsub(/(\d+\.?)+/, '')
6
7 # Remove the "D. " from names like "D. Concept Index" and "D. Function Index"
8 name = name[3..-1] if name.start_with?('D. ')
9
10 name
11 end
12
13 def get_type
14 return 'Manual: Appendices' if name.start_with?('Appendix')
15 return 'Manual: Indexes' if at_css('a[rel=up]').content.include?("Index")
16 "Manual"
17 end
18
19 def additional_entries
20 entry_type = {
21 "Function Index" => "Functions",
22 "Index of Shell Builtin Commands" => "Builtin Commands",
23 "Index of Shell Reserved Words" => "Reserved Words",
24 "Parameter and Variable Index" => "Parameters and Variables"
25 }[name]
26
27 # Only extract additional entries from certain index pages
28 return [] if entry_type.nil?
29
30 entries = []
31
32 css('table[class^=index-] td[valign=top] > a').each_slice(2) do |entry_node, section_node|
33 entry_name = entry_node.content
34
35 page = section_node['href'].split('#')[0]
36 hash = entry_name
37
38 # The Special Parameters page has multiple additional entries which should link to the same paragraph
39 # Example: the documentation for "$!" is equal to the documentation for "!"
40 if page == 'special-parameters'
41 if hash.size > 1 && hash[0] == '$'
42 hash = hash[1..-1]
43 end
44 end
45
46 # Construct path to the page which the index links to
47 entry_path = '/html_node/' + page + '#' + hash
48
49 entries << [entry_name, entry_path, entry_type]
50 end
51
52 entries
53 end
54 end
55 end
56end
lib/docs/filters/cmake/entries.rb+1−0
@@ -20,6 +20,7 @@ module Docs · 19 unmodified lines
20 'prop_sf' => 'Properties: Source Files',20 'prop_sf' => 'Properties: Source Files',
21 'prop_test' => 'Properties: Tests',21 'prop_test' => 'Properties: Tests',
22 'prop_tgt' => 'Properties: Targets',22 'prop_tgt' => 'Properties: Targets',
23 'envvar' => 'Environment Variables',
23 'variable' => 'Variables' }24 'variable' => 'Variables' }
24 25
25 def get_name26 def get_name
lib/docs/filters/codeceptjs/entries.rb+2−2
@@ -20,8 +20,8 @@ module Docs · 19 unmodified lines
20 def additional_entries20 def additional_entries
21 return [] unless subpath.start_with?('helpers') && subpath != 'helpers/'21 return [] unless subpath.start_with?('helpers') && subpath != 'helpers/'
22 22
23 css('h2').each_with_object [] do |node, entries|23 css('h2, h3').each_with_object [] do |node, entries|
24 next if node['id'] == 'access-from-helpers'24 next if node['id'] == 'access-from-helpers' || node.content !~ /\s*[a-z_]/
25 entries << ["#{node.content} (#{name})", node['id']]25 entries << ["#{node.content} (#{name})", node['id']]
26 end26 end
27 end27 end
lib/docs/filters/composer/clean_html.rb+35−0
@@ -0,0 +1,35 @@
1module Docs
2 class Composer
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove unneeded elements
6 css('#searchbar, .toc, .fork-and-edit, .anchor').remove
7
8 # Fix the home page titles
9 if subpath == ''
10 css('h1').each do |node|
11 node.name = 'h2'
12 end
13
14 # Add a main title before the first subtitle
15 at_css('h2').before('<h1>Composer</h1>')
16 end
17
18 # Code blocks
19 css('pre').each do |node|
20 code = node.at_css('code[class]')
21
22 unless code.nil?
23 node['data-language'] = 'javascript' if code['class'].include?('javascript')
24 node['data-language'] = 'php' if code['class'].include?('php')
25 end
26
27 node.content = node.content.strip
28 node.remove_attribute('class')
29 end
30
31 doc
32 end
33 end
34 end
35end
lib/docs/filters/composer/entries.rb+36−0
@@ -0,0 +1,36 @@
1module Docs
2 class Composer
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 title = at_css('h1').content
6 title = "#{Integer(subpath[1]) + 1}. #{title}" if type == 'Book'
7 title
8 end
9
10 def get_type
11 return 'Articles' if subpath.start_with?('articles/')
12 'Book'
13 end
14
15 def additional_entries
16 entries = []
17
18 if subpath == '04-schema.md' # JSON Schema
19 css('h3').each do |node|
20 name = node.content.strip
21 name.remove!(' (root-only)')
22 entries << [name, node['id'], 'JSON Schema']
23 end
24 end
25
26 if subpath == '06-config.md' # Composer config
27 css('h2').each do |node|
28 entries << [node.content.strip, node['id'], 'Configuration Options']
29 end
30 end
31
32 entries
33 end
34 end
35 end
36end
lib/docs/filters/cordova/clean_html.rb+1−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Cordova2 class Cordova
3 class CleanHtmlFilter < Filter3 class CleanHtmlFilter < Filter
4 def call4 def call
5 @doc = at_css('.page-content > div')5 @doc = at_css('#page-toc-source') || at_css('.page-content > div')
6 6
7 at_css('h1').content = 'Apache Cordova' if root_page?7 at_css('h1').content = 'Apache Cordova' if root_page?
8 8
lib/docs/filters/cpp/entries.rb+2−0
@@ -31,6 +31,8 @@ module Docs · 30 unmodified lines
31 'Keywords'31 'Keywords'
32 elsif subpath.start_with?('experimental')32 elsif subpath.start_with?('experimental')
33 'Experimental libraries'33 'Experimental libraries'
34 elsif subpath.start_with?('language/')
35 'Language'
34 elsif type = at_css('.t-navbar > div:nth-child(4) > :first-child').try(:content)36 elsif type = at_css('.t-navbar > div:nth-child(4) > :first-child').try(:content)
35 type.strip!37 type.strip!
36 type.remove! ' library'38 type.remove! ' library'
lib/docs/filters/css/entries.rb+17−10
@@ -6,13 +6,16 @@ module Docs · 5 unmodified lines
6 'CSS_Background_and_Borders' => 'Backgrounds & Borders',6 'CSS_Background_and_Borders' => 'Backgrounds & Borders',
7 'CSS_Columns' => 'Multi-column Layout',7 'CSS_Columns' => 'Multi-column Layout',
8 'CSS_Flexible_Box_Layout' => 'Flexible Box Layout',8 'CSS_Flexible_Box_Layout' => 'Flexible Box Layout',
9 'CSS_Fonts' => 'Fonts',
9 'CSS_Grid_Layout' => 'Grid Layout',10 'CSS_Grid_Layout' => 'Grid Layout',
10 'CSS_Images' => 'Images',11 'CSS_Images' => 'Images',
11 'CSS_Lists_and_Counters' => 'Lists',12 'CSS_Lists_and_Counters' => 'Lists',
12 'CSS_Transforms' => 'Transforms',13 'CSS_Transforms' => 'Transforms',
13 'Media_Queries' => 'Media Queries',14 'Media_Queries' => 'Media Queries',
15 'filter-function' => 'Filter Effects',
14 'transform-function' => 'Transforms',16 'transform-function' => 'Transforms',
15 '@media' => 'Media Queries',17 '@media' => 'Media Queries',
18 'overscroll' => 'Overscroll',
16 'text-size-adjust' => 'Miscellaneous',19 'text-size-adjust' => 'Miscellaneous',
17 'resolved_value' => 'Miscellaneous',20 'resolved_value' => 'Miscellaneous',
18 'touch-action' => 'Miscellaneous',21 'touch-action' => 'Miscellaneous',
@@ -42,7 +45,7 @@ module Docs · 23 unmodified lines
42 end45 end
43 46
44 def get_type47 def get_type
45 if slug.include?('-webkit') || slug.include?('-moz')48 if slug.include?('-webkit') || slug.include?('-moz') || slug.include?('-ms')
46 'Extensions'49 'Extensions'
47 elsif type = TYPE_BY_PATH[slug.split('/').first]50 elsif type = TYPE_BY_PATH[slug.split('/').first]
48 type51 type
@@ -66,19 +69,22 @@ module Docs · 17 unmodified lines
66 'Pseudo-Elements'69 'Pseudo-Elements'
67 elsif name.start_with?(':')70 elsif name.start_with?(':')
68 'Selectors'71 'Selectors'
72 elsif name.start_with?('display-')
73 'Display'
69 else74 else
70 'Miscellaneous'75 'Miscellaneous'
71 end76 end
72 end77 end
73 78
74 STATUSES = {79 STATUSES = {
75 'spec-Living' => 0,80 'spec-Living' => 0,
76 'spec-REC' => 1,81 'spec-REC' => 1,
77 'spec-CR' => 2,82 'spec-CR' => 2,
78 'spec-PR' => 3,83 'spec-PR' => 3,
79 'spec-LC' => 4,84 'spec-LC' => 4,
80 'spec-WD' => 5,85 'spec-WD' => 5,
81 'spec-ED' => 686 'spec-ED' => 6,
87 'spec-Obsolete' => 7
82 }88 }
83 89
84 PRIORITY_STATUSES = %w(spec-REC spec-CR)90 PRIORITY_STATUSES = %w(spec-REC spec-CR)
@@ -86,6 +92,7 @@ module Docs · 1 unmodified line
86 92
87 def get_spec93 def get_spec
88 return unless table = at_css('#Specifications + table') || css('.standard-table').last94 return unless table = at_css('#Specifications + table') || css('.standard-table').last
95
89 specs = table.css('tbody tr').to_a96 specs = table.css('tbody tr').to_a
90 # [link, span]97 # [link, span]
91 specs.map! { |node| [node.at_css('> td:nth-child(1) > a'), node.at_css('> td:nth-child(2) > span')] }98 specs.map! { |node| [node.at_css('> td:nth-child(1) > a'), node.at_css('> td:nth-child(2) > span')] }
@@ -110,8 +117,8 @@ module Docs · 18 unmodified lines
110 'shape' => [117 'shape' => [
111 %w(rect() Syntax) ],118 %w(rect() Syntax) ],
112 'timing-function' => [119 'timing-function' => [
113 %w(cubic-bezier() The_cubic-bezier()_class_of_timing-functions),120 %w(cubic-bezier()),
114 %w(steps() The_steps()_class_of_timing-functions),121 %w(steps()),
115 %w(linear linear),122 %w(linear linear),
116 %w(ease ease),123 %w(ease ease),
117 %w(ease-in ease-in),124 %w(ease-in ease-in),
lib/docs/filters/cypress/clean_html.rb+26−0
@@ -0,0 +1,26 @@
1# frozen_string_literal: true
2
3module Docs
4 class Cypress
5 class CleanHtmlFilter < Filter
6 def call
7 article_div = at_css('#article > div')
8 @doc = article_div unless article_div.nil?
9
10 header = at_css('h1.article-title')
11 doc.prepend_child(header) unless header.nil?
12
13 css('.article-edit-link').remove
14 css('.article-footer').remove
15 css('.article-footer-updated').remove
16
17 css('pre').each do |node|
18 node.content = node.content
19 node['data-language'] = 'javascript'
20 end
21
22 doc
23 end
24 end
25 end
26end
lib/docs/filters/cypress/entries.rb+34−0
@@ -0,0 +1,34 @@
1# frozen_string_literal: true
2
3module Docs
4 class Cypress
5 class EntriesFilter < Docs::EntriesFilter
6 SECTIONS = %w[
7 commands
8 core-concepts
9 cypress-api
10 events
11 getting-started
12 guides
13 overview
14 plugins
15 references
16 utilities
17 ].freeze
18
19 def get_name
20 at_css('h1.article-title').content.strip
21 end
22
23 def get_type
24 path = context[:url].path
25
26 SECTIONS.each do |section|
27 if path.match?("/#{section}/")
28 return section.split('-').map(&:capitalize).join(' ')
29 end
30 end
31 end
32 end
33 end
34end
lib/docs/filters/dart/clean_html.rb+66−0
@@ -0,0 +1,66 @@
1module Docs
2 class Dart
3 class CleanHtmlFilter < Filter
4 def call
5 # Move the title into the main content node in the v1 docs
6 title = at_css('h1.title')
7 if title
8 name = title.children.last.content.strip
9 kind = title.at_css('.kind').content
10 at_css('.main-content').prepend_child("<h1>#{name} #{kind}</h1>")
11 end
12
13 # Add a title to the homepage of the v2 docs
14 if subpath == 'index.html' && at_css('.main-content > h1').nil?
15 at_css('.main-content').prepend_child('<h1>Dart SDK</h1>')
16 end
17
18 # Add the library to the main content (it is not always visible in the menu entry)
19 breadcrumbs = at_css('.breadcrumbs').css('li:not(.self-crumb) > a')
20 if breadcrumbs.length > 1
21 library = breadcrumbs[1].content
22
23 # Generate the link to the homepage of the library
24 with_hypens = library.gsub(/:/, '-')
25 location = "#{'../' * subpath.count('/')}#{with_hypens}/#{with_hypens}-library"
26 link = "<a href=\"#{location}\" class=\"_links-link\">#{library}</span>"
27
28 # Add the link to the main title, just like how the "Homepage" and "Source code" links appear
29 at_css('.main-content').prepend_child("<p class=\"_links\">#{link}</p>")
30 end
31
32 # Extract the actual content
33 # We can't use options[:container] here because the entries filter uses the breadcrumbs node
34 @doc = at_css('.main-content')
35
36 # Move the features (i.e. "read-only, inherited") into the blue header
37 css('.features').each do |node|
38 header = node.xpath('parent::dd/preceding::dt').last
39 header.add_child node unless header.nil?
40 end
41
42 css('section').each do |node|
43 if node['id'] && node.first_element_child
44 node.first_element_child['id'] ||= node['id']
45 end
46
47 node.before(node.children).remove
48 end
49
50 css('span').each do |node|
51 node.before(node.children).remove
52 end
53
54 # Make code blocks detectable by Prism
55 css('pre').each do |node|
56 node['data-language'] = 'dart'
57 node.content = node.content
58 end
59
60 css('.properties', '.property', '.callables', '.callable').remove_attr('class')
61
62 doc
63 end
64 end
65 end
66end
lib/docs/filters/dart/entries.rb+42−0
@@ -0,0 +1,42 @@
1module Docs
2 class Dart
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 title = at_css('h1.title')
6 if title # v1
7 name = title.element_children.last.content.strip
8 kind = title.at_css('.kind').content
9 else # v2
10 title = at_css('.main-content > h1')
11 name = title.content[/(.*)( )/, 1].split(' top-level')[0]
12 kind = title.content[/(.*)( )(.+)/, 3]
13 end
14
15 breadcrumbs = at_css('.breadcrumbs').css('li:not(.self-crumb) > a')
16 first_part = ''
17
18 if breadcrumbs.length == 2 && !kind.include?('class')
19 first_part = breadcrumbs[1].content
20 elsif breadcrumbs.length == 3
21 first_part = breadcrumbs[2].content
22 end
23
24 separator = ''
25
26 unless first_part.empty?
27 if kind.include?('class')
28 separator = ':'
29 else
30 separator = '.'
31 end
32 end
33
34 "#{first_part}#{separator}#{name}"
35 end
36
37 def get_type
38 at_css('.breadcrumbs > li:nth-child(2)').content.split(' ')[0]
39 end
40 end
41 end
42end
lib/docs/filters/dom/clean_html.rb+1−1
@@ -30,7 +30,7 @@ module Docs · 29 unmodified lines
30 end30 end
31 31
32 # Remove <div> wrapping .overheadIndicator32 # Remove <div> wrapping .overheadIndicator
33 css('div > .overheadIndicator:first-child:last-child').each do |node|33 css('div > .overheadIndicator:first-child:last-child', 'div > .blockIndicator:first-child:last-child').each do |node|
34 node.parent.replace(node)34 node.parent.replace(node)
35 end35 end
36 36
lib/docs/filters/dom/entries.rb+15−2
@@ -6,6 +6,7 @@ module Docs · 5 unmodified lines
6 'EXT_' => 'WebGL',6 'EXT_' => 'WebGL',
7 'OES_' => 'WebGL',7 'OES_' => 'WebGL',
8 'WEBGL_' => 'WebGL',8 'WEBGL_' => 'WebGL',
9 'Sensor API' => 'Sensors',
9 'Ambient Light' => 'Ambient Light',10 'Ambient Light' => 'Ambient Light',
10 'Audio' => 'Audio',11 'Audio' => 'Audio',
11 'Battery Status' => 'Battery Status',12 'Battery Status' => 'Battery Status',
@@ -22,10 +23,13 @@ module Docs · 10 unmodified lines
22 'Encrypted Media Extensions' => 'Encrypted Media',23 'Encrypted Media Extensions' => 'Encrypted Media',
23 'Fetch' => 'Fetch',24 'Fetch' => 'Fetch',
24 'File API' => 'File',25 'File API' => 'File',
26 'Fullscreen' => 'Fullscreen',
25 'Geolocation' => 'Geolocation',27 'Geolocation' => 'Geolocation',
26 'Geometry' => 'Geometry',28 'Geometry' => 'Geometry',
27 'High Resolution Time' => 'Performance',29 'High Resolution Time' => 'Performance',
28 'Intersection' => 'Intersection Observer',30 'Intersection' => 'Intersection Observer',
31 'Keyboard' => 'Keyboard',
32 'Media Capabilities' => 'Media',
29 'Media Capture' => 'Media',33 'Media Capture' => 'Media',
30 'Media Session' => 'Media',34 'Media Session' => 'Media',
31 'Media Source' => 'Media',35 'Media Source' => 'Media',
@@ -34,6 +38,7 @@ module Docs · 2 unmodified lines
34 'MIDI' => 'Audio',38 'MIDI' => 'Audio',
35 'Navigation Timing' => 'Performance',39 'Navigation Timing' => 'Performance',
36 'Network Information' => 'Network Information',40 'Network Information' => 'Network Information',
41 'Orientation Sensor' => 'Sensors',
37 'Payment' => 'Payments',42 'Payment' => 'Payments',
38 'Performance Timeline' => 'Performance',43 'Performance Timeline' => 'Performance',
39 'Pointer Events' => 'Pointer Events',44 'Pointer Events' => 'Pointer Events',
@@ -52,11 +57,13 @@ module Docs · 12 unmodified lines
52 'Web App Manifest' => 'Web App Manifest',57 'Web App Manifest' => 'Web App Manifest',
53 'Budget' => 'Budget',58 'Budget' => 'Budget',
54 'Web Authentication' => 'Authentication',59 'Web Authentication' => 'Authentication',
60 'Web Locks' => 'Locks',
55 'Web Workers' => 'Web Workers',61 'Web Workers' => 'Web Workers',
56 'WebGL' => 'WebGL',62 'WebGL' => 'WebGL',
57 'WebRTC' => 'WebRTC',63 'WebRTC' => 'WebRTC',
58 'WebUSB' => 'WebUSB',64 'WebUSB' => 'WebUSB',
59 'WebVR' => 'WebVR' }65 'WebVR' => 'WebVR',
66 'WebVTT' => 'WebVTT' }
60 67
61 TYPE_BY_NAME_STARTS_WITH = {68 TYPE_BY_NAME_STARTS_WITH = {
62 'AbortController' => 'Fetch',69 'AbortController' => 'Fetch',
@@ -88,10 +95,12 @@ module Docs · 25 unmodified lines
88 'Fetch' => 'Fetch',95 'Fetch' => 'Fetch',
89 'File' => 'File',96 'File' => 'File',
90 'GlobalEventHandlers' => 'GlobalEventHandlers',97 'GlobalEventHandlers' => 'GlobalEventHandlers',
98 'HMDVR' => 'WebVR',
91 'history' => 'History',99 'history' => 'History',
92 'HTML Drag' => 'Drag & Drop',100 'HTML Drag' => 'Drag & Drop',
93 'HTML' => 'Elements',101 'HTML' => 'Elements',
94 'IDB' => 'IndexedDB',102 'IDB' => 'IndexedDB',
103 'Keyboard' => 'Keyboard',
95 'location' => 'Location',104 'location' => 'Location',
96 'navigator' => 'Navigator',105 'navigator' => 'Navigator',
97 'MediaKeySession' => 'Encrypted Media',106 'MediaKeySession' => 'Encrypted Media',
@@ -99,6 +108,7 @@ module Docs · 1 unmodified line
99 'MediaSession' => 'Media Session',108 'MediaSession' => 'Media Session',
100 'MediaTrack' => 'Media Streams',109 'MediaTrack' => 'Media Streams',
101 'Message' => 'Channel Messaging',110 'Message' => 'Channel Messaging',
111 'Mutation' => 'DOM',
102 'NamedNode' => 'Nodes',112 'NamedNode' => 'Nodes',
103 'Node' => 'Nodes',113 'Node' => 'Nodes',
104 'Notification' => 'Notification',114 'Notification' => 'Notification',
@@ -116,9 +126,11 @@ module Docs · 11 unmodified lines
116 'Shadow' => 'Shadow DOM',126 'Shadow' => 'Shadow DOM',
117 'StaticRange' => 'Range',127 'StaticRange' => 'Range',
118 'Streams' => 'Media Streams',128 'Streams' => 'Media Streams',
129 'StyleProperty' => 'CSS',
119 'StyleSheet' => 'CSS',130 'StyleSheet' => 'CSS',
120 'Stylesheet' => 'CSS',131 'Stylesheet' => 'CSS',
121 'SVG' => 'SVG',132 'SVG' => 'SVG',
133 'TextTrack' => 'WebVTT',
122 'TimeRanges' => 'Media',134 'TimeRanges' => 'Media',
123 'timing' => 'Performance',135 'timing' => 'Performance',
124 'Timing' => 'Performance',136 'Timing' => 'Performance',
@@ -157,6 +169,7 @@ module Docs · 32 unmodified lines
157 'timing' => 'Performance',169 'timing' => 'Performance',
158 'Timing' => 'Performance',170 'Timing' => 'Performance',
159 'udio' => 'Audio',171 'udio' => 'Audio',
172 'VRDevice' => 'WebVR',
160 'WebGL' => 'WebGL',173 'WebGL' => 'WebGL',
161 'WEBGL' => 'WebGL',174 'WEBGL' => 'WebGL',
162 'WebRTC' => 'WebRTC',175 'WebRTC' => 'WebRTC',
@@ -236,7 +249,7 @@ module Docs · 73 unmodified lines
236 249
237 def include_default_entry?250 def include_default_entry?
238 return true if type == 'Console'251 return true if type == 'Console'
239 return true unless node = doc.at_css('.overheadIndicator')252 return true unless node = doc.at_css('.overheadIndicator, .blockIndicator')
240 node = node.parent while node.parent != doc253 node = node.parent while node.parent != doc
241 return true if node.previous_element.try(:name).in?(%w(h2 h3))254 return true if node.previous_element.try(:name).in?(%w(h2 h3))
242 content = node.content255 content = node.content
lib/docs/filters/elixir/clean_html.rb+6−0
@@ -57,8 +57,14 @@ module Docs · 56 unmodified lines
57 node.parent.after(node)57 node.parent.after(node)
58 end58 end
59 59
60 css('.signature').each do |node|
61 non_text_children = node.xpath('node()[not(self::text())]')
62 non_text_children.to_a.reverse.each { |child| node.parent.add_next_sibling(child) }
63 end
64
60 css('pre').each do |node|65 css('pre').each do |node|
61 node['data-language'] = 'elixir'66 node['data-language'] = 'elixir'
67 node.content = node.content
62 end68 end
63 end69 end
64 end70 end
lib/docs/filters/express/clean_html.rb+5−0
@@ -42,6 +42,11 @@ module Docs · 41 unmodified lines
42 node.parent.content = node.parent.content42 node.parent.content = node.parent.content
43 end43 end
44 44
45 # Fix links to the method reference
46 css('a').each do |node|
47 node['href'] = node['href'].sub('4x/api', 'index')
48 end
49
45 doc50 doc
46 end51 end
47 end52 end
lib/docs/filters/go/clean_html.rb+1−0
@@ -17,6 +17,7 @@ module Docs · 16 unmodified lines
17 css('#plusone', '#nav', '.pkgGopher', '#footer', '.collapsed', '.permalink', '#pkg-callgraph').remove17 css('#plusone', '#nav', '.pkgGopher', '#footer', '.collapsed', '.permalink', '#pkg-callgraph').remove
18 18
19 css('span[style]', '.toggleVisible', '.expanded', 'div.toggle').each do |node|19 css('span[style]', '.toggleVisible', '.expanded', 'div.toggle').each do |node|
20 node.first_element_child['id'] = node['id'] if node['id']
20 node.before(node.children).remove21 node.before(node.children).remove
21 end22 end
22 23
lib/docs/filters/graphite/clean_html.rb+27−0
@@ -0,0 +1,27 @@
1module Docs
2 class Graphite
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove the paragraph icon after all headers
6 css('.headerlink').remove
7
8 css('dl.function > dt').each do |node|
9 node.content = node.content
10 end
11
12 css('.section').each do |node|
13 node.before(node.children).remove
14 end
15
16 css('div[class*="highlight-"]').each do |node|
17 node.content = node.content.strip
18 node.name = 'pre'
19 node['data-language'] = node['class'][/highlight\-(\w+)/, 1]
20 node.remove_attribute('class')
21 end
22
23 doc
24 end
25 end
26 end
27end
lib/docs/filters/graphite/entries.rb+46−0
@@ -0,0 +1,46 @@
1module Docs
2 class Graphite
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 at_css('h1').children[0].to_s
6 end
7
8 def get_type
9 get_name
10 end
11
12 def additional_entries
13 entries = []
14
15 # Sections
16 css('.section > .section').each do |node|
17 title = node.at_css('h2, h3')
18
19 next if title.nil?
20
21 # Move the id attribute to the title
22 # If this is excluded, the complete section will be highlighted in yellow when someone navigates to it
23 title['id'] = node['id']
24 node.remove_attribute('id')
25
26 parent_title_selector = "parent::div[@class='section']/preceding::#{title.name == 'h2' ? 'h1' : 'h2'}"
27
28 entries << [
29 title.children[0].to_s,
30 title['id'],
31 title.xpath(parent_title_selector).last.children[0].to_s
32 ]
33 end
34
35 # Functions
36 css('dl.function > dt').each do |node|
37 name = node.at_css('.descname').content
38 name << '()'
39 entries << [name, node['id'], 'Functions']
40 end
41
42 entries
43 end
44 end
45 end
46end
lib/docs/filters/handlebars/clean_html.rb+44−0
@@ -0,0 +1,44 @@
1module Docs
2 class Handlebars
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove the t-shirt shop advertisement
6 css('#callout').remove
7
8 # The title filter is used to add titles to pages without one, remove original headers
9 css('h1').remove
10
11 # Remove the link to the issue tracker
12 css('.issue-tracker').remove
13
14 css('pre').each do |node|
15 # Remove nested nodes inside pre tags
16 node.content = node.content
17
18 # Add syntax highlighting
19 node['data-language'] = 'html'
20 end
21
22 # Transform 'Learn More' links to headers in the "Getting Started" part of the homepage
23 # If this step is skipped, that section looks cluttered with 4 sub-sections without any dividers
24 css('#getting-started + .contents a.more-info').each do |node|
25 clone = node.clone
26
27 # Move it to the top of the sub-section
28 node.parent.prepend_child(clone)
29
30 # Turn it into a header
31 clone.name = 'h3'
32
33 # Remove the "Learn More: " part
34 clone.content = clone.content[12..-1]
35 end
36
37 # Remove class attributes from div elements to reduce file size
38 css('div').remove_attr('class')
39
40 doc
41 end
42 end
43 end
44end
lib/docs/filters/handlebars/entries.rb+19−0
@@ -0,0 +1,19 @@
1module Docs
2 class Handlebars
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 subpath[0..-6].titleize
6 end
7
8 def get_type
9 name
10 end
11
12 def additional_entries
13 css('h2, h3').to_a.map do |node|
14 [node.content.strip, node['id'], root_page? ? 'Manual' : nil]
15 end
16 end
17 end
18 end
19end
lib/docs/filters/haskell/clean_html.rb+4−0
@@ -65,6 +65,10 @@ module Docs · 64 unmodified lines
65 end65 end
66 end66 end
67 67
68 css('pre code').each do |node|
69 node.before(node.children).remove
70 end
71
68 doc72 doc
69 end73 end
70 end74 end
lib/docs/filters/haskell/entries.rb+2−2
@@ -52,7 +52,7 @@ module Docs · 51 unmodified lines
52 return [] if subpath.start_with?('users_guide')52 return [] if subpath.start_with?('users_guide')
53 return [] if IGNORE_ENTRIES_PATHS.include?(subpath.split('/').last)53 return [] if IGNORE_ENTRIES_PATHS.include?(subpath.split('/').last)
54 54
55 css('#synopsis > ul > li').each_with_object [] do |node, entries|55 css('#synopsis > details > ul > li').each_with_object [] do |node, entries|
56 link = node.at_css('a')56 link = node.at_css('a')
57 name = node.content.strip57 name = node.content.strip
58 name.remove! %r{\A(?:module|data|newtype|class|type family m|type)\s+}58 name.remove! %r{\A(?:module|data|newtype|class|type family m|type)\s+}
@@ -75,7 +75,7 @@ module Docs · 16 unmodified lines
75 end75 end
76 76
77 def include_default_entry?77 def include_default_entry?
78 subpath.start_with?('users_guide') || at_css('#synopsis > ul > li')78 subpath.start_with?('users_guide') || at_css('#synopsis > details > ul > li')
79 end79 end
80 end80 end
81 end81 end
lib/docs/filters/homebrew/clean_html.rb+5−0
@@ -4,6 +4,11 @@ module Docs · 3 unmodified lines
4 def call4 def call
5 css('hr')5 css('hr')
6 6
7 if at_css('h1').nil?
8 title = current_url.normalized_path[1..-1].gsub(/-/, ' ')
9 doc.children.before("<h1>#{title}</h1>")
10 end
11
7 css('div.highlighter-rouge').each do |node|12 css('div.highlighter-rouge').each do |node|
8 lang = node['class'][/language-(\w+)/, 1]13 lang = node['class'][/language-(\w+)/, 1]
9 node['data-language'] = lang if lang14 node['data-language'] = lang if lang
lib/docs/filters/homebrew/entries.rb+3−1
@@ -2,7 +2,8 @@ module Docs · 1 unmodified line
2 class Homebrew2 class Homebrew
3 class EntriesFilter < Docs::EntriesFilter3 class EntriesFilter < Docs::EntriesFilter
4 def get_name4 def get_name
5 name = at_css('h1').content.strip5 header = at_css('h1')
6 name = header.nil? ? current_url.normalized_path[1..-1].gsub(/-/, ' ') : header.content.strip
6 name.remove! %r{\(.*}7 name.remove! %r{\(.*}
7 name8 name
8 end9 end
@@ -16,6 +17,7 @@ module Docs · 7 unmodified lines
16 Python-for-Formula-Authors17 Python-for-Formula-Authors
17 Migrating-A-Formula-To-A-Tap18 Migrating-A-Formula-To-A-Tap
18 Rename-A-Formula19 Rename-A-Formula
20 Building-Against-Non-Homebrew-Dependencies
19 How-to-Create-and-Maintain-a-Tap21 How-to-Create-and-Maintain-a-Tap
20 Brew-Test-Bot22 Brew-Test-Bot
21 Prose-Style-Guidelines)23 Prose-Style-Guidelines)
lib/docs/filters/html/entries.rb+1−1
@@ -28,7 +28,7 @@ module Docs · 27 unmodified lines
28 28
29 def include_default_entry?29 def include_default_entry?
30 return false if %w(Element/Heading_Elements).include?(slug)30 return false if %w(Element/Heading_Elements).include?(slug)
31 (node = doc.at_css '.overheadIndicator').nil? || node.content.exclude?('not on a standards track')31 (node = doc.at_css '.overheadIndicator, .blockIndicator').nil? || node.content.exclude?('not on a standards track')
32 end32 end
33 33
34 def additional_entries34 def additional_entries
lib/docs/filters/javascript/clean_html.rb+2−2
@@ -11,12 +11,12 @@ module Docs · 10 unmodified lines
11 11
12 def other12 def other
13 # Remove "style" attribute13 # Remove "style" attribute
14 css('.inheritsbox', '.overheadIndicator').each do |node|14 css('.inheritsbox', '.overheadIndicator', '.blockIndicator').each do |node|
15 node.remove_attribute 'style'15 node.remove_attribute 'style'
16 end16 end
17 17
18 # Remove <div> wrapping .overheadIndicator18 # Remove <div> wrapping .overheadIndicator
19 css('div > .overheadIndicator:first-child:last-child').each do |node|19 css('div > .overheadIndicator:first-child:last-child', 'div > .blockIndicator:first-child:last-child').each do |node|
20 node.parent.replace(node)20 node.parent.replace(node)
21 end21 end
22 end22 end
lib/docs/filters/javascript/entries.rb+1−1
@@ -88,7 +88,7 @@ module Docs · 87 unmodified lines
88 end88 end
89 89
90 def include_default_entry?90 def include_default_entry?
91 node = doc.at_css '.overheadIndicator, .warning'91 node = doc.at_css '.blockIndicator, .warning'
92 92
93 # Can't use :first-child because #doc is a DocumentFragment93 # Can't use :first-child because #doc is a DocumentFragment
94 return true unless node && node.parent == doc && !node.previous_element94 return true unless node && node.parent == doc && !node.previous_element
lib/docs/filters/kotlin/clean_html.rb+7−0
@@ -46,6 +46,13 @@ module Docs · 45 unmodified lines
46 parent.content = parent.content46 parent.content = parent.content
47 parent['data-language'] = 'kotlin'47 parent['data-language'] = 'kotlin'
48 end48 end
49
50 css('.tags').each do |wrapper|
51 platforms = wrapper.css('.platform:not(.tag-value-Common)').to_a
52 platforms = platforms.map { |node| "#{node.content} (#{node['data-tag-version']})" }
53 platforms = "<b>Platform and version requirements:</b> #{platforms.join ", "}"
54 wrapper.replace(platforms)
55 end
49 end56 end
50 end57 end
51 end58 end
lib/docs/filters/kotlin/entries.rb+3−1
@@ -5,7 +5,9 @@ module Docs · 4 unmodified lines
5 if subpath.start_with?('api')5 if subpath.start_with?('api')
6 breadcrumbs[1..-1].join('.')6 breadcrumbs[1..-1].join('.')
7 else7 else
8 (at_css('h1') || at_css('h2')).content8 node = (at_css('h1') || at_css('h2'))
9 return node.content unless node.nil?
10 subpath[/\/([a-z0-9_-]+)\./][1..-2].titleize.sub('Faq', 'FAQ')
9 end11 end
10 end12 end
11 13
lib/docs/filters/mdn/clean_html.rb+90−0
@@ -14,6 +14,8 @@ module Docs · 13 unmodified lines
14 '#Quick_Links',14 '#Quick_Links',
15 'hr']15 'hr']
16 16
17 BROWSER_UNNECESSARY_CLASS_REGEX = /\s*bc-browser[\w_-]+/
18
17 def call19 def call
18 css(*REMOVE_NODES).remove20 css(*REMOVE_NODES).remove
19 21
@@ -61,6 +63,94 @@ module Docs · 41 unmodified lines
61 node.previous_element << node63 node.previous_element << node
62 end64 end
63 65
66 # New compatibility tables
67
68 css('.bc-data #Legend + dl', '.bc-data #Legend', '.bc-data #Legend_2 + dl', '.bc-data #Legend_2', '.bc-browser-name').remove
69
70 css('abbr.only-icon[title="Full support"]',
71 'abbr.only-icon[title="Partial support"]',
72 'abbr.only-icon[title="No support"]',
73 'abbr.only-icon[title="See implementation notes"]').remove
74
75 css('.bc-data .ic-altname', '.bc-data .ic-deprecated', '.bc-data .ic-non-standard', '.bc-data .ic-experimental').each do |node|
76 node.parent.remove
77 end
78
79 css('abbr.only-icon').each do |node|
80 node.replace(node.content)
81 end
82
83 css('.bc-table .bc-platforms td', '.bc-table .bc-browsers td').each do |node|
84 node.name = 'th'
85 end
86
87 css('.bc-data').each do |node|
88 link = node.at_css('.bc-github-link')
89 prev = node.previous_element
90 prev = prev.previous_element until prev.name == 'h2'
91 prev.add_child(link)
92
93 node.before(node.children).remove
94 end
95
96 css('.bc-table').each do |node|
97 desktop_table = node
98
99 mobile_table = node.dup
100 desktop_table.after(mobile_table)
101
102 if desktop_table.at_css('.bc-platform-server')
103 server_table = node.dup
104 mobile_table.after(server_table)
105 end
106
107 desktop_columns = desktop_table.at_css('th.bc-platform-desktop')['colspan'].to_i
108 mobile_columns = desktop_table.at_css('th.bc-platform-mobile')['colspan'].to_i
109
110 desktop_table.css('.bc-platform-mobile').remove
111 desktop_table.css('.bc-platform-server').remove
112 desktop_table.css('.bc-browsers th').to_a[(desktop_columns + 1)..-1].each(&:remove)
113 desktop_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|
114 line.css('td').to_a[(desktop_columns)..-1].each(&:remove)
115 end
116
117 mobile_table.css('.bc-platform-desktop').remove
118 mobile_table.css('.bc-platform-server').remove
119 mobile_table.css('.bc-browsers th').to_a[1..(desktop_columns)].each(&:remove)
120 mobile_table.css('.bc-browsers th').to_a[(mobile_columns + 1)..-1].each(&:remove)
121 mobile_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|
122 line.css('td').to_a[0..(desktop_columns - 1)].each(&:remove)
123 line.css('td').to_a[(mobile_columns)..-1].each(&:remove)
124 end
125
126 if server_table
127 server_table.css('.bc-platform-desktop').remove
128 server_table.css('.bc-platform-mobile').remove
129 server_table.css('.bc-browsers th').to_a[1..(desktop_columns + mobile_columns)].each(&:remove)
130 server_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|
131 line.css('td').to_a[0..(desktop_columns + mobile_columns - 1)].each(&:remove)
132 end
133 end
134 end
135
136 # Reduce page size to make the offline bundle smaller.
137 css('.bc-supports-unknown').remove_attr('class')
138 css('td[class*="bc-platform"], th[class*="bc-platform"]').remove_attr('class')
139 css('td[class*="bc-browser"], th[class*="bc-browser"]').each do |node|
140 class_name = node['class']
141 class_name.remove!(BROWSER_UNNECESSARY_CLASS_REGEX)
142
143 if class_name.present?
144 node['class'] = class_name
145 else
146 node.remove_attribute('class')
147 end
148 end
149
150 css('abbr[title*="Compatibility unknown"]').each do |node|
151 node.before(node.children).remove
152 end
153
64 doc154 doc
65 end155 end
66 end156 end
lib/docs/filters/node/clean_html.rb+1−1
@@ -16,7 +16,7 @@ module Docs · 15 unmodified lines
16 16
17 css('pre').each do |node|17 css('pre').each do |node|
18 if lang = node.at_css('code')['class']18 if lang = node.at_css('code')['class']
19 node['data-language'] = lang.remove('lang-')19 node['data-language'] = lang.remove(%r{lang(uage)?-})
20 end20 end
21 21
22 node.content = node.content22 node.content = node.content
lib/docs/filters/node/entries.rb+2−0
@@ -26,6 +26,7 @@ module Docs · 25 unmodified lines
26 26
27 def get_type27 def get_type
28 type = at_css('h1').content.strip28 type = at_css('h1').content.strip
29 type.remove! %r{\[.*\]}
29 REPLACE_TYPES[type] || "#{type.first.upcase}#{type[1..-1]}"30 REPLACE_TYPES[type] || "#{type.first.upcase}#{type[1..-1]}"
30 end31 end
31 32
@@ -40,6 +41,7 @@ module Docs · 8 unmodified lines
40 41
41 klass = nil if node.name == 'h2'42 klass = nil if node.name == 'h2'
42 name = node.content.strip43 name = node.content.strip
44 name.remove! %r{\s*\[src\]}
43 45
44 # Skip constructors46 # Skip constructors
45 if name.start_with? 'new '47 if name.start_with? 'new '
lib/docs/filters/pandas/entries.rb+5−3
@@ -2,8 +2,10 @@ module Docs · 1 unmodified line
2 class Pandas2 class Pandas
3 class EntriesFilter < Docs::EntriesFilter3 class EntriesFilter < Docs::EntriesFilter
4 def get_name4 def get_name
5 if subpath.start_with?('generated')5 if subpath.start_with?('generated') || (subpath.include?('reference') && !subpath.include?('reference/index'))
6 name = at_css('dt').content.strip6 name_node = at_css('dt')
7 name_node = at_css('h1') if name_node.nil?
8 name = name_node.content.strip
7 name.sub! %r{\(.*}, '()'9 name.sub! %r{\(.*}, '()'
8 name.remove! %r{\s=.*}10 name.remove! %r{\s=.*}
9 name.remove! %r{\A(class(method)?) (pandas\.)?}11 name.remove! %r{\A(class(method)?) (pandas\.)?}
@@ -16,7 +18,7 @@ module Docs · 6 unmodified lines
16 end18 end
17 19
18 def get_type20 def get_type
19 if subpath.start_with?('generated')21 if subpath.start_with?('generated') || (subpath.include?('reference') && !subpath.include?('reference/index'))
20 css('.toctree-l2.current > a').last.content.remove(/\s\(.+?\)/)22 css('.toctree-l2.current > a').last.content.remove(/\s\(.+?\)/)
21 else23 else
22 'Manual'24 'Manual'
lib/docs/filters/puppeteer/clean_html.rb+24−0
@@ -0,0 +1,24 @@
1module Docs
2 class Puppeteer
3 class CleanHtmlFilter < Filter
4 def call
5 at_css('h1').content = 'Puppeteer Documentation'
6
7 # None of the elements to remove have classes, so the order of the remove calls is trivial
8
9 # Remove links to previous versions of the reference
10 at_css('h1 + ul').remove
11
12 # Remove table of contents
13 at_css('h1 + h5').remove
14 at_css('h1 + ul').remove
15
16 # Make headers bigger by transforming them into a bigger variant
17 css('h3').each { |node| node.name = 'h2' }
18 css('h4').each { |node| node.name = 'h3' }
19
20 doc
21 end
22 end
23 end
24end
lib/docs/filters/puppeteer/entries.rb+41−0
@@ -0,0 +1,41 @@
1module Docs
2 class Puppeteer
3 class EntriesFilter < Docs::EntriesFilter
4 # The entire reference is one big page, so get_name and get_type are not necessary
5
6 def additional_entries
7 entries = []
8
9 css('h3').each do |node|
10 name = node.content.split(': ').last
11
12 # Find all sub-items (all h4's between the current h3 and the next)
13 current = node.next
14 while !current.nil? && current.name != 'h3'
15 if current.name == 'h4'
16 current_name = current.content
17
18 # Prepend events with the class name
19 if current_name.start_with?('event: ')
20 current_name = "#{name} event: #{current_name[/'(.*)'/, 1]}"
21 end
22
23 # Remove arguments from functions
24 if current_name.include?('(')
25 current_name = current_name.split('(')[0] + '()'
26 end
27
28 entries << [current_name, current['id'], name]
29 end
30
31 current = current.next
32 end
33
34 entries << [name, node['id'], name]
35 end
36
37 entries
38 end
39 end
40 end
41end
lib/docs/filters/pygame/clean_html.rb+102−0
@@ -0,0 +1,102 @@
1module Docs
2 class Pygame
3 class CleanHtmlFilter < Filter
4 def call
5 @doc = at_css '.body'
6
7 if root_page?
8 # remove unneeded stuff
9 at_css('.modindex-jumpbox').remove
10 css('[role="navigation"],.pcap, .cap, .footer').remove
11 # table -> list
12 list = at_css('table')
13 list.replace(list.children)
14 list.name = 'ul'
15 css('tr').each do |row|
16 row.name = 'li'
17 row.remove_attribute('class')
18 end
19 at_css('h1').content = 'Pygame'
20 return doc
21 end
22
23 # remove unwanted stuff
24 # .headerlink => ¶ after links
25 # .toc => table of content
26 # .tooltip-content => tooltips after links to functions
27 css('table.toc.docutils, .headerlink, .tooltip-content').remove
28
29 # Remove wrapper .section
30 section = at_css('.section')
31 definition = at_css('.definition')
32 definition['id'] = section['id']
33 section.replace(section.children)
34
35 # Format code for it be highlighted
36 css('.highlight-default.notranslate').each do |node|
37 pre = node.at_css('pre')
38 node.replace(pre)
39 # gets rid of the already existing syntax highlighting
40 pre.content = pre.content
41 pre['class'] = 'language-python'
42 pre['data-language'] = "python"
43 end
44
45 # change descriptions of functions/attributes to blockquote
46 css('.line-block').each do |node|
47 node.name = 'blockquote'
48 end
49
50 # change functions
51 css('.definition').each do |d|
52
53 # the header is the function/attribute name. It might look something like
54 # this:
55 # pygame.image.load()
56 # It'll end up being something like this:
57 # pygame.image.load(filename) -> Surface
58 # pygame.image.load(fileobj, namehint="") -> Surface
59
60 header = d.at_css('dt.title')
61 if d['class'].include?('class') or d['class'].include?('module')
62 header.name = 'h1'
63 @section = header.content.strip
64 else
65 header.name = 'h3'
66 end
67 # save the original header
68 initial_header = header.content.strip
69 # save the real name for the entries
70 header['data-name'] = initial_header
71 # empty the header
72 if header.name == 'h3'
73 header.inner_html = ''
74 end
75 # to replace it with the signatures
76 next_el = header.next_element
77 signatures = next_el.css('.signature')
78 signatures.each do |sig|
79 sig.name = 'code'
80 if header.name == 'h3'
81 sig.parent = header
82 # the signature don't contain pygame.module. I think it's better
83 # to display them, as it avoids confusion with methods (have a
84 # look at the pygame.Rect page)
85 if initial_header.start_with?(@section)
86 sig.content = @section + '.' + sig.text
87 end
88 # separate the signatures on different lines.
89 header.add_child "<br>"
90 end
91 end
92 end
93
94 css('> dl', '> dl > dd', 'h1 code').each do |node|
95 node.before(node.children).remove
96 end
97
98 doc
99 end
100 end
101 end
102end
lib/docs/filters/pygame/entries.rb+29−0
@@ -0,0 +1,29 @@
1module Docs
2 class Pygame
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 at_css('h1').content.remove('pygame.')
6 end
7
8 def get_type
9 at_css('h1').content
10 end
11
12 def additional_entries
13 return [] if root_page?
14
15 css('h1, h2, h3').each_with_object [] do |node, entries|
16 name = node['id'] || node['data-name']
17
18 if node.parent['class'].include?('function') or node.parent['class'].include?('method')
19 name << '()'
20 end
21
22 name.remove!('pygame.')
23
24 entries << [name, node['id']] unless name == self.name
25 end
26 end
27 end
28 end
29end
lib/docs/filters/python/clean_html.rb+5−3
@@ -15,15 +15,17 @@ module Docs · 14 unmodified lines
15 15
16 def other16 def other
17 css('h1').each do |node|17 css('h1').each do |node|
18 node.content = node.content.sub!(/\A[\d\.]+/) do |str|18 node.content = node.content.sub(/\A[\d\.]+/) do |str|
19 rgx = /\A#{str}/19 rgx = /\A#{str}/
20 @levelRegexp = @levelRegexp ? Regexp.union(@levelRegexp, rgx) : rgx20 @levelRegexp = @levelRegexp ? Regexp.union(@levelRegexp, rgx) : rgx
21 ''21 ''
22 end22 end
23 end23 end
24 24
25 css('h2', 'h3', 'h4').each do |node|25 unless @levelRegexp.nil?
26 node.inner_html = node.inner_html.remove @levelRegexp26 css('h2', 'h3', 'h4').each do |node|
27 node.inner_html = node.inner_html.remove @levelRegexp
28 end
27 end29 end
28 end30 end
29 end31 end
lib/docs/filters/python/entries_v2.rb+1−7
@@ -57,16 +57,10 @@ module Docs · 56 unmodified lines
57 clean_id_attributes57 clean_id_attributes
58 entries = []58 entries = []
59 59
60 css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]').each do |node|60 css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]', '.data > dt[id]').each do |node|
61 entries << [node['id'], node['id']]61 entries << [node['id'], node['id']]
62 end62 end
63 63
64 css('.data > dt[id]').each do |node|
65 if node['id'].split('.').last.upcase! # skip constants
66 entries << [node['id'], node['id']]
67 end
68 end
69
70 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|64 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|
71 entries << [node['id'] + '()', node['id']]65 entries << [node['id'] + '()', node['id']]
72 end66 end
lib/docs/filters/python/entries_v3.rb+15−17
@@ -2,16 +2,18 @@ module Docs · 1 unmodified line
2 class Python2 class Python
3 class EntriesV3Filter < Docs::EntriesFilter3 class EntriesV3Filter < Docs::EntriesFilter
4 REPLACE_TYPES = {4 REPLACE_TYPES = {
5 'Cryptographic' => 'Cryptography',5 'contextvars — Context Variables' => 'Context Variables',
6 'Custom Interpreters' => 'Interpreters',6 'Cryptographic' => 'Cryptography',
7 'Data Compression & Archiving' => 'Data Compression',7 'Custom Interpreters' => 'Interpreters',
8 'Generic Operating System' => 'Operating System',8 'Data Compression & Archiving' => 'Data Compression',
9 'Graphical User Interfaces with Tk' => 'Tk',9 'email — An email & MIME handling package' => 'Email',
10 'Internet Data Handling' => 'Internet Data',10 'Generic Operating System' => 'Operating System',
11 'Internet Protocols & Support' => 'Internet',11 'Graphical User Interfaces with Tk' => 'Tk',
12 'Interprocess Communication & Networking' => 'Networking',12 'Internet Data Handling' => 'Internet Data',
13 'Program Frameworks' => 'Frameworks',13 'Internet Protocols & Support' => 'Internet',
14 'Structured Markup Processing Tools' => 'Structured Markup' }14 'Interprocess Communication & Networking' => 'Networking',
15 'Program Frameworks' => 'Frameworks',
16 'Structured Markup Processing Tools' => 'Structured Markup' }
15 17
16 def get_name18 def get_name
17 name = at_css('h1').content19 name = at_css('h1').content
@@ -25,6 +27,7 @@ module Docs · 7 unmodified lines
25 27
26 def get_type28 def get_type
27 return 'Logging' if slug.start_with? 'library/logging'29 return 'Logging' if slug.start_with? 'library/logging'
30 return 'Asynchronous I/O' if slug.start_with? 'library/asyncio'
28 31
29 type = at_css('.related a[accesskey="U"]').content32 type = at_css('.related a[accesskey="U"]').content
30 33
@@ -45,6 +48,7 @@ module Docs · 14 unmodified lines
45 end48 end
46 49
47 def include_default_entry?50 def include_default_entry?
51 return true if slug == 'library/asyncio'
48 !at_css('.body > .section:only-child > .toctree-wrapper:last-child') && !type.in?(%w(Superseded))52 !at_css('.body > .section:only-child > .toctree-wrapper:last-child') && !type.in?(%w(Superseded))
49 end53 end
50 54
@@ -53,16 +57,10 @@ module Docs · 2 unmodified lines
53 clean_id_attributes57 clean_id_attributes
54 entries = []58 entries = []
55 59
56 css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]').each do |node|60 css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]', '.data > dt[id]').each do |node|
57 entries << [node['id'], node['id']]61 entries << [node['id'], node['id']]
58 end62 end
59 63
60 css('.data > dt[id]').each do |node|
61 if node['id'].split('.').last.upcase! # skip constants
62 entries << [node['id'], node['id']]
63 end
64 end
65
66 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|64 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|
67 entries << [node['id'] + '()', node['id']]65 entries << [node['id'] + '()', node['id']]
68 end66 end
lib/docs/filters/qt/clean_html.rb+32−0
@@ -0,0 +1,32 @@
1module Docs
2 class Qt
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove unneeded elements
6 css('.copy-notice, .navigationbar, .headerNavi, .footerNavi, .sidebar, .toc, #ec_toggle', '.landingicons img', 'br').remove
7
8 # QML property/method header
9 css('.qmlproto').each do |node|
10 id = node.at_css('tr')['id']
11 id = node.at_css('a')['name'] if id.blank?
12 node.inner_html = node.at_css('td').inner_html
13 node.name = 'h3'
14 node['id'] = id
15 end
16
17 css('.main-rounded', '.content', '.line', '.context', '.descr', '.types', '.func', '.table', 'div:not([class])', '.landing', '.col-1', '.heading', '.qmlitem', '.qmldoc', 'div.pre').each do |node|
18 node.before(node.children).remove
19 end
20
21 css('pre').each do |node|
22 node.content = node.content
23 node['data-language'] = 'cpp' if node['class'].include?('cpp')
24 node['data-language'] = 'qml' if node['class'].include?('qml')
25 node.remove_attribute('class')
26 end
27
28 doc
29 end
30 end
31 end
32end
lib/docs/filters/qt/entries.rb+142−0
@@ -0,0 +1,142 @@
1# frozen_string_literal: true
2
3module Docs
4 class Qt
5 class EntriesFilter < Docs::EntriesFilter
6 def get_name
7 header = at_css('h1.title + .small-subtitle a') || at_css('h1.title') || at_css('.context h2')
8 name = header.content
9 name.sub! %r{ Class$}, ' (class)'
10 name.sub! %r{ QML Type$}, ' (QML type)'
11 name.sub! %r{ QML Basic Type$}, ' (QML basic type)'
12
13 # Add '(class)' to the class pages where the subtitle name is used (e.g. qset-const-iterator.html)
14 if at_css('h1.title').content.strip.end_with?(' Class') && !name.include?('(class)')
15 name = "#{name} (class) "
16 end
17
18 name
19 end
20
21 def get_type
22 breadcrumb = css('#main_title_bar + ul li')
23 category = if breadcrumb.length < 3
24 then 'Qt'.dup
25 else breadcrumb.at(1).content
26 end
27
28 if category == 'Qt'
29 return 'Qt Platforms' if name.include?(' for ') || name == 'Qt Platform Abstraction'
30 return 'Qt Quick' if name == 'Qt Quick Test' || name == 'Qt Quick Test Reference Documentation'
31
32 alwaysInQt = ['Qt Configure Options', 'Qt Image Formats']
33 category = name if name.start_with?('Qt ') && !alwaysInQt.include?(name)
34 end
35
36 qtPlatformsTypes = ['Qt Platform Headers', 'Qt Android Extras', 'Qt Mac Extras', 'Qt Windows Extras', 'Qt X11 Extras']
37 return 'Qt Platforms' if qtPlatformsTypes.include?(category)
38
39 category.remove!(' Manual')
40 category
41 end
42
43 def include_default_entry?
44 name != 'All Classes' && name != 'All QML Types'
45 end
46
47 def additional_entries
48 entries = []
49 titles = []
50
51 className = at_css('h1.title').content.strip.remove(' Class')
52 displayedClassName = className
53 alternativeClassName = at_css('h1.title + .small-subtitle a')
54 displayedClassName = alternativeClassName.content if alternativeClassName
55
56 # Functions signatures
57 css('h3.fn').each do |node|
58 header = node.clone
59
60 # Skip typenames
61 next if header.content.strip.start_with?('typename ')
62
63 # Remove leading <a name="">
64 header.children.css('a[name]').remove
65
66 # Remove leading <code> tag (virtual/static/… attributes)
67 code = header.children.first
68 code.remove if code.name == 'code'
69
70 # Remove leading ‘const’
71 header.children.first.remove if header.content.strip.start_with?('const ')
72
73 # Remove return type
74 returnType = header.children.first
75 returnType.remove if returnType['class'] == 'type'
76
77 title = header.content.strip
78
79 # Remove leading '&'/'*'
80 title[0] = '' if title[0] == '&' || title[0] == '*'
81
82 # Ignore operator overloads
83 next if title.start_with?('operator')
84
85 # Remove function parameters
86 title.sub! %r{\(.*\)}, '()'
87
88 # Remove template generics
89 title.remove!(%r{^<.*> })
90
91 # Remove ‘const’ at the end
92 title.remove!(%r{ const$})
93
94 # Enum/typedef formatting
95 title.sub! %r{(enum|typedef) (.*)}, '\2 (\1)'
96
97 # Remove property type
98 title = "#{displayedClassName}::#{title}" if title.sub!(%r{ : .*$}, '')
99
100 # Replace the class name by the alternative class name if available
101 title = title.sub(className, displayedClassName) if alternativeClassName
102
103 unless titles.include?(title) # Remove duplicates (function overloading)
104 entries << [title, header['id']]
105 titles.push title
106 end
107 end
108
109 # QML properties/functions
110 qmlTypeName = at_css('h1.title').content.remove(' QML Type', '')
111 css('.qmlproto').each do |node|
112 title = node.content.strip
113 id = node.at_css('tr')['id']
114 id = node.at_css('a')['name'] if id.blank?
115
116 # Remove options
117 title.remove!(%r{^\[.*\] })
118
119 # Remove function parameters
120 title.sub! %r{\(.*\)}, '()'
121
122 # Remove property type
123 title.remove!(%r{ : .*$})
124
125 # Remove return type
126 title.remove!(%r{.* })
127
128 # Remove return type
129 title.remove!(%r{.* })
130
131 title = "#{qmlTypeName}.#{title.strip}"
132 unless titles.include?(title) # Remove duplicates (function overloading)
133 entries << [title, id]
134 titles.push(title)
135 end
136 end
137
138 entries
139 end
140 end
141 end
142end
lib/docs/filters/ramda/clean_html.rb+1−1
@@ -11,7 +11,7 @@ module Docs · 10 unmodified lines
11 css('.card').each do |card|11 css('.card').each do |card|
12 title = card.at_css('h2')12 title = card.at_css('h2')
13 added_in = card.at_css('small')13 added_in = card.at_css('small')
14 added_in.parent = title14 added_in.parent = title if added_in.present?
15 end15 end
16 16
17 css('.params').each do |node|17 css('.params').each do |node|
lib/docs/filters/rdoc/clean_html.rb+4−2
@@ -37,8 +37,10 @@ module Docs · 36 unmodified lines
37 end37 end
38 38
39 # Add class to differentiate Ruby code from C code39 # Add class to differentiate Ruby code from C code
40 css('.method-source-code > pre').each do |node|40 css('.method-source-code').each do |node|
41 node['class'] = node.at_css('.ruby-keyword') ? 'ruby' : 'c'41 node.parent.prepend_child(node)
42 pre = node.at_css('pre')
43 pre['class'] = pre.at_css('.ruby-keyword') ? 'ruby' : 'c'
42 end44 end
43 45
44 # Remove code highlighting46 # Remove code highlighting
lib/docs/filters/react/entries.rb+6−2
@@ -6,9 +6,11 @@ module Docs · 5 unmodified lines
6 end6 end
7 7
8 def get_type8 def get_type
9 link = at_css("nav a[href='#{result[:path].split('/').last}']")9 link = css("nav a[href='#{result[:path].split('/').last}']").last
10 return 'Miscellaneous' unless link10 return 'Miscellaneous' unless link
11 link.ancestors('ul').last.previous_element.content11 type = link.ancestors('ul').last.previous_element.content
12 type.remove! %r{\s*\(.*\)}
13 type
12 end14 end
13 15
14 def additional_entries16 def additional_entries
@@ -25,6 +27,8 @@ module Docs · 10 unmodified lines
25 'Reference: Component'27 'Reference: Component'
26 elsif slug == 'react-api'28 elsif slug == 'react-api'
27 'Reference: React'29 'Reference: React'
30 elsif slug == 'hooks-reference'
31 'Hooks'
28 else32 else
29 'Reference'33 'Reference'
30 end34 end
lib/docs/filters/redis/entries.rb+2−1
@@ -12,7 +12,7 @@ module Docs · 11 unmodified lines
12 when 'HDEL' then 'Hashes'12 when 'HDEL' then 'Hashes'
13 when 'BLPOP' then 'Lists'13 when 'BLPOP' then 'Lists'
14 when 'SADD' then 'Sets'14 when 'SADD' then 'Sets'
15 when 'ZADD' then 'Sorted Sets'15 when 'BZPOPMAX' then 'Sorted Sets'
16 when 'PSUBSCRIBE' then 'Pub/Sub'16 when 'PSUBSCRIBE' then 'Pub/Sub'
17 when 'DISCARD' then 'Transactions'17 when 'DISCARD' then 'Transactions'
18 when 'EVAL' then 'Scripting'18 when 'EVAL' then 'Scripting'
@@ -21,6 +21,7 @@ module Docs · 2 unmodified lines
21 when 'PFADD' then 'HyperLogLog'21 when 'PFADD' then 'HyperLogLog'
22 when 'CLUSTER ADDSLOTS' then 'Cluster'22 when 'CLUSTER ADDSLOTS' then 'Cluster'
23 when 'GEOADD' then 'Geo'23 when 'GEOADD' then 'Geo'
24 when 'XACK' then 'Stream'
24 else 'Miscellaneous'25 else 'Miscellaneous'
25 end26 end
26 end27 end
lib/docs/filters/rust/clean_html.rb+39−6
@@ -1,11 +1,11 @@
1# frozen_string_literal: true
2
1module Docs3module Docs
2 class Rust4 class Rust
3 class CleanHtmlFilter < Filter5 class CleanHtmlFilter < Filter
4 def call6 def call
5 puts subpath if at_css('#versioninfo')
6
7 if slug.start_with?('book') || slug.start_with?('reference')7 if slug.start_with?('book') || slug.start_with?('reference')
8 @doc = at_css('#content')8 @doc = at_css('#content main')
9 elsif slug == 'error-index'9 elsif slug == 'error-index'
10 css('.error-undescribed').remove10 css('.error-undescribed').remove
11 11
@@ -29,12 +29,16 @@ module Docs · 17 unmodified lines
29 29
30 css('.rusttest', '.test-arrow', 'hr').remove30 css('.rusttest', '.test-arrow', 'hr').remove
31 31
32 css('.docblock.attributes').each do |node|
33 node.remove if node.content.include?('#[must_use]')
34 end
35
32 css('a.header').each do |node|36 css('a.header').each do |node|
33 node.first_element_child['id'] = node['name']37 node.first_element_child['id'] = node['name'] || node['id']
34 node.before(node.children).remove38 node.before(node.children).remove
35 end39 end
36 40
37 css('.docblock > h1').each { |node| node.name = 'h4' }41 css('.docblock > h1:not(.section-header)').each { |node| node.name = 'h4' }
38 css('h2.section-header').each { |node| node.name = 'h3' }42 css('h2.section-header').each { |node| node.name = 'h3' }
39 css('h1.section-header').each { |node| node.name = 'h2' }43 css('h1.section-header').each { |node| node.name = 'h2' }
40 44
@@ -44,7 +48,7 @@ module Docs · 3 unmodified lines
44 end48 end
45 end49 end
46 50
47 css('> .impl-items', '> .docblock', 'pre > pre').each do |node|51 css('> .impl-items', '> .docblock', 'pre > pre', '.tooltiptext', '.tooltip').each do |node|
48 node.before(node.children).remove52 node.before(node.children).remove
49 end53 end
50 54
@@ -62,6 +66,35 @@ module Docs · 11 unmodified lines
62 node['data-language'] = 'rust' if node['class'] && node['class'].include?('rust')66 node['data-language'] = 'rust' if node['class'] && node['class'].include?('rust')
63 end67 end
64 68
69 doc.first_element_child.name = 'h1' if doc.first_element_child.name = 'h2'
70 at_css('h1').content = 'Rust Documentation' if root_page?
71
72 css('.table-display').each do |node|
73 node.css('td').each do |td|
74 node.before(td.children)
75 end
76 node.remove
77 end
78
79 css('.important-traits').to_a.each_with_index do |node, index|
80 content = node.at_css('.content.hidden .content')
81 node.at_css('.content.hidden').replace(content) if content
82 node.parent.after(node) if node.parent.name.in?(%(h2 h3 h4))
83 end
84
85 css('code.content').each do |node|
86 node.name = 'pre'
87 node.css('.fmt-newline').each do |line|
88 line.inner_html = line.inner_html + "\n"
89 end
90 node.inner_html = node.inner_html.gsub('<br>', "\n")
91 node.content = node.content
92 end
93
94 css('.since + .srclink').each do |node|
95 node.previous_element.before(node)
96 end
97
65 doc98 doc
66 end99 end
67 end100 end
lib/docs/filters/rust/entries.rb+7−5
@@ -42,11 +42,13 @@ module Docs · 41 unmodified lines
42 entries << [node.content, node['id']] unless node.content.include?('Note:')42 entries << [node.content, node['id']] unless node.content.include?('Note:')
43 end43 end
44 else44 else
45 css('#methods + * + div > .method', '#required-methods + div > .method', '#provided-methods + div > .method').map do |node|45 css('.method')
46 name = node.at_css('.fnname').content46 .each_with_object({}) { |node, entries|
47 name.prepend "#{self.name}::"47 name = node.at_css('.fnname').try(:content)
48 [name, node['id']]48 next unless name
49 end49 name.prepend "#{self.name}::"
50 entries[name] ||= [name, node['id']]
51 }.values
50 end52 end
51 end53 end
52 end54 end
lib/docs/filters/salt_stack/clean_html.rb+27−0
@@ -0,0 +1,27 @@
1module Docs
2 class SaltStack
3 class CleanHtmlFilter < Filter
4 def call
5 if root_page?
6 doc.inner_html = '<h1>SaltStack</h1>'
7 return doc
8 end
9
10 css('.headerlink').remove
11
12 css('div[class^="highlight-"]').each do |node|
13 node.name = 'pre'
14 node['data-language'] = node['class'].scan(/highlight-([a-z]+)/i)[0][0]
15 node.content = node.content.strip
16 end
17
18 css('.function > dt').each do |node|
19 node.name = 'h3'
20 node.content = node.content
21 end
22
23 doc
24 end
25 end
26 end
27end
lib/docs/filters/salt_stack/entries.rb+38−0
@@ -0,0 +1,38 @@
1module Docs
2 class SaltStack
3 class EntriesFilter < Docs::EntriesFilter
4 SALT_REF_RGX = /salt\.([^\.]+)\.([^\s]+)/
5
6 def get_name
7 header = at_css('h1').content
8
9 ref_match = SALT_REF_RGX.match(header)
10 if ref_match
11 ns, mod = ref_match.captures
12 "#{ns}.#{mod}"
13 else
14 header
15 end
16 end
17
18 def get_type
19 slug.split('/', 3)[1]
20 end
21
22 def include_default_entry?
23 slug.split('/').last.start_with? 'salt'
24 end
25
26 def additional_entries
27 entries = []
28
29 css('.function > h3').each do |node|
30 name = node.content.remove('salt.').split('(')[0] + '()'
31 entries << [name, node['id']]
32 end
33
34 entries
35 end
36 end
37 end
38end
lib/docs/filters/scala/clean_html.rb+109−0
@@ -0,0 +1,109 @@
1module Docs
2 class Scala
3 class CleanHtmlFilter < Filter
4 def call
5 @doc = at_css('#content')
6
7 always
8 add_title
9
10 doc
11 end
12
13 def always
14 # Remove deprecated sections
15 css('.members').each do |members|
16 header = members.at_css('h3')
17 members.remove if header.text.downcase.include? 'deprecate'
18 end
19
20 css('#mbrsel, #footer').remove
21
22 css('.diagram-container').remove
23 css('.toggleContainer > .toggle').each do |node|
24 title = node.at_css('span')
25 next if title.nil?
26
27 content = node.at_css('.hiddenContent')
28 next if content.nil?
29
30 title.name = 'dt'
31
32 content.remove_attribute('class')
33 content.remove_attribute('style')
34 content.name = 'dd'
35
36 attributes = at_css('.attributes')
37 unless attributes.nil?
38 title.parent = attributes
39 content.parent = attributes
40 end
41 end
42
43 signature = at_css('#signature')
44 signature.replace "<h2 id=\"signature\">#{signature.inner_html}</h2>"
45
46 css('div.members > h3').each do |node|
47 node.name = 'h2'
48 end
49
50 css('div.members > ol').each do |list|
51 list.css('li').each do |li|
52 h3 = doc.document.create_element 'h3'
53 h3['id'] = li['name'].rpartition('#').last unless li['name'].nil?
54
55 li.prepend_child h3
56 li.css('.shortcomment').remove
57
58 modifier = li.at_css('.modifier_kind')
59 modifier.parent = h3 unless modifier.nil?
60
61 kind = li.at_css('.modifier_kind .kind')
62 kind.content = kind.content + ' ' unless kind.nil?
63
64 symbol = li.at_css('.symbol')
65 symbol.parent = h3 unless symbol.nil?
66
67 li.swap li.children
68 end
69
70 list.swap list.children
71 end
72
73 css('.fullcomment pre, .fullcommenttop pre').each do |pre|
74 pre['data-language'] = 'scala'
75 pre.content = pre.content
76 end
77
78 # Sections of the documentation which do not seem useful
79 %w(#inheritedMembers #groupedMembers .permalink .hiddenContent .material-icons).each do |selector|
80 css(selector).remove
81 end
82
83 # Things that are not shown on the site, like deprecated members
84 css('li[visbl=prt]').remove
85 end
86
87 def add_title
88 css('.permalink').remove
89
90 definition = at_css('#definition')
91 return if definition.nil?
92
93 type_full_name = {a: 'Annotation', c: 'Class', t: 'Trait', o: 'Object', p: 'Package'}
94 type = type_full_name[definition.at_css('.big-circle').text.to_sym]
95 name = CGI.escapeHTML definition.at_css('h1').text
96
97 package = definition.at_css('#owner').text rescue ''
98 package = package + '.' unless name.empty? || package.empty?
99
100 other = definition.at_css('.morelinks').dup
101 other_content = other ? "<h3>#{other.to_html}</h3>" : ''
102
103 title_content = root_page? ? 'Package root' : "#{type} #{package}#{name}".strip
104 title = "<h1>#{title_content}</h1>"
105 definition.replace title + other_content
106 end
107 end
108 end
109end
lib/docs/filters/scala/entries.rb+103−0
@@ -0,0 +1,103 @@
1module Docs
2 class Scala
3 class EntriesFilter < Docs::EntriesFilter
4 REPLACEMENTS = {
5 '$eq' => '=',
6 '$colon' => ':',
7 '$less' => '<',
8 }
9
10 def get_name
11 if is_package?
12 symbol = at_css('#definition h1')
13 symbol ? symbol.text.gsub(/\W+/, '') : "package"
14 else
15 name = slug.split('/').last
16
17 # Some objects have inner objects, show ParentObject$.ChildObject$ instead of ParentObject$$ChildObject$
18 name = name.gsub('$$', '$.')
19
20 REPLACEMENTS.each do |key, value|
21 name = name.gsub(key, value)
22 end
23
24 # If a dollar sign is used as separator between two characters, replace it with a dot
25 name.gsub(/([^$.])\$([^$.])/, '\1.\2')
26 end
27 end
28
29 def get_type
30 # if this entry is for a package, we group the package under the parent package
31 if is_package?
32 parent_package
33 # otherwise, group it under the regular package name
34 else
35 package_name
36 end
37 end
38
39 def include_default_entry?
40 true
41 end
42
43 def additional_entries
44 entries = []
45
46 full_name = "#{type}.#{name}".remove('$')
47 css(".members li[name^=\"#{full_name}\"]").each do |node|
48 # Ignore packages
49 kind = node.at_css('.modifier_kind > .kind')
50 next if !kind.nil? && kind.content == 'package'
51
52 # Ignore deprecated members
53 next unless node.at_css('.symbol > .name.deprecated').nil?
54
55 id = node['name'].rpartition('#').last
56 member_name = node.at_css('.name')
57
58 # Ignore members only existing of hashtags, we can't link to that
59 next if member_name.nil? || member_name.content.strip.remove('#').blank?
60
61 member = "#{name}.#{member_name.content}()"
62 entries << [member, id]
63 end
64
65 entries
66 end
67
68 private
69
70 # For the package name, we use the slug rather than parsing the package
71 # name from the HTML because companion object classes may be broken out into
72 # their own entries (by the source documentation). When that happens,
73 # we want to group these classes (like `scala.reflect.api.Annotations.Annotation`)
74 # under the package name, and not the fully-qualfied name which would
75 # include the companion object.
76 def package_name
77 name = package_drop_last(slug_parts)
78 name.empty? ? '_root_' : name
79 end
80
81 def parent_package
82 parent = package_drop_last(package_name.split('.'))
83 parent.empty? ? '_root_' : parent
84 end
85
86 def package_drop_last(parts)
87 parts[0...-1].join('.')
88 end
89
90 def slug_parts
91 slug.split('/')
92 end
93
94 def owner
95 at_css('#owner')
96 end
97
98 def is_package?
99 slug.ends_with?('index') || slug.ends_with?('package')
100 end
101 end
102 end
103end
lib/docs/filters/sqlite/entries.rb+2−1
@@ -34,7 +34,8 @@ module Docs · 33 unmodified lines
34 'rtree' => 'R*Tree Module',34 'rtree' => 'R*Tree Module',
35 'rbu' => 'RBU Extension',35 'rbu' => 'RBU Extension',
36 'limits' => 'Limits',36 'limits' => 'Limits',
37 'howtocorrupt' => 'How To Corrupt'37 'howtocorrupt' => 'How To Corrupt',
38 'geopoly' => 'Geopoly'
38 }39 }
39 40
40 def get_type41 def get_type
lib/docs/filters/typescript/entries.rb+1−0
@@ -9,6 +9,7 @@ module Docs · 8 unmodified lines
9 9
10 def get_type10 def get_type
11 return 'Declaration Files' if subpath.include?('declaration-files')11 return 'Declaration Files' if subpath.include?('declaration-files')
12 return 'Project Configuration' if slug == 'handbook/configuring-watch'
12 type = at_css('#main-nav a.active').ancestors('.panel').first.at_css('> a').content13 type = at_css('#main-nav a.active').ancestors('.panel').first.at_css('> a').content
13 type = name if type == 'Handbook'14 type = name if type == 'Handbook'
14 type15 type
lib/docs/filters/vue_router/clean_html.rb+21−0
@@ -0,0 +1,21 @@
1module Docs
2 class VueRouter
3 class CleanHtmlFilter < Filter
4 def call
5 @doc = at_css('.content')
6
7 # Remove unneeded elements
8 css('.bit-sponsor, .header-anchor').remove
9
10 css('.custom-block').each do |node|
11 node.name = 'blockquote'
12
13 title = node.at_css('.custom-block-title')
14 title.name = 'strong' unless title.nil?
15 end
16
17 doc
18 end
19 end
20 end
21end
lib/docs/filters/vue_router/entries.rb+71−0
@@ -0,0 +1,71 @@
1module Docs
2 class VueRouter
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 name = at_css('h1').content
6 name.remove! '# '
7 name
8 end
9
10 def get_type
11 return 'Other Guides' if subpath.start_with?('guide/advanced')
12 return 'Basic Guides' if subpath.start_with?('guide') || subpath.start_with?('installation')
13 'API Reference'
14 end
15
16 def include_default_entry?
17 name != 'API Reference'
18 end
19
20 def additional_entries
21 return [] unless subpath.start_with?('api')
22
23 entries = [
24 ['<router-link>', 'router-link', 'API Reference'],
25 ['<router-view>', 'router-view', 'API Reference'],
26 ['$route', 'the-route-object', 'API Reference'],
27 ['Component Injections', 'component-injections', 'API Reference']
28 ]
29
30 css('h3').each do |node|
31 entry_name = node.content.strip
32
33 # Get the previous h2 title
34 title = node
35 title = title.previous_element until title.name == 'h2'
36 title = title.content.strip
37 title.remove! '# '
38
39 entry_name.remove! '# '
40
41 case title
42 when 'Router Construction Options'
43 entry_name = "RouterOptions.#{entry_name}"
44 when '<router-view> Props'
45 entry_name = "<router-view> `#{entry_name}` prop"
46 when '<router-link> Props'
47 entry_name = "<router-link> `#{entry_name}` prop"
48 when 'Router Instance Methods'
49 entry_name = "#{entry_name}()"
50 end
51
52 entry_name = entry_name.split(' API ')[0] if entry_name.start_with?('v-slot')
53
54 unless title == "Component Injections" || node['id'] == 'route-object-properties'
55 entries << [entry_name, node['id'], 'API Reference']
56 end
57 end
58
59 css('#route-object-properties + ul > li > p:first-child > strong').each do |node|
60 entry_name = node.content.strip
61 id = "route-object-#{entry_name.remove('$route.')}"
62
63 node['id'] = id
64 entries << [entry_name, node['id'], 'API Reference']
65 end
66
67 entries
68 end
69 end
70 end
71end
lib/docs/filters/wordpress/clean_html.rb+39−0
@@ -0,0 +1,39 @@
1module Docs
2 class Wordpress
3 class CleanHtmlFilter < Filter
4 def call
5 if root_page?
6 doc.inner_html = '<h1>WordPress</h1>'
7 return doc
8 end
9
10 article = at_css('article[id^="post-"]')
11 @doc = at_css('article[id^="post-"]') unless article.nil?
12
13 css('hr', '.screen-reader-text', '.table-of-contents',
14 '.anchor', '.toc-jump', '.source-code-links', '.user-notes',
15 '.show-more', '.hide-more').remove
16
17 br = /<br\s?\/?>/i
18
19 header = at_css('h1')
20 header.content = header.content.strip
21 doc.prepend_child header
22
23 # Add PHP code highlighting
24 css('pre').each do |node|
25 node['data-language'] = 'php'
26 end
27
28 css('.source-code-container').each do |node|
29 node.name = 'pre'
30 node.inner_html = node.inner_html.gsub(br, "\n")
31 node.content = node.content.strip
32 node['data-language'] = 'php'
33 end
34
35 doc
36 end
37 end
38 end
39end
lib/docs/filters/wordpress/entries.rb+19−0
@@ -0,0 +1,19 @@
1module Docs
2 class Wordpress
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 at_css('.breadcrumbs .trail-end').content
6 end
7
8 def get_type
9 if subpath.starts_with?('classes')
10 'Classes'
11 elsif subpath.starts_with?('hooks')
12 'Hooks'
13 elsif subpath.starts_with?('functions')
14 'Functions'
15 end
16 end
17 end
18 end
19end
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+16−3
@@ -31,10 +31,14 @@ module Docs · 30 unmodified lines
31 end31 end
32 32
33 options[:only_patterns] = [%r{\Aapi}, %r{\Aguide}]33 options[:only_patterns] = [%r{\Aapi}, %r{\Aguide}]
34 options[:skip] = %w(api/ng.html)34 options[:skip] = %w(api/ng.html guide/tutorial/step_14.html guide/api.html guide/tutorial/.html)
35 options[:skip_patterns] = [
36 /error\/\$compile/,
37 /misc/,
38 ]
35 39
36 options[:attribution] = <<-HTML40 options[:attribution] = <<-HTML
37 &copy; 2010&ndash;2017 Google, Inc.<br>41 &copy; 2010&ndash;2018 Google, Inc.<br>
38 Licensed under the Creative Commons Attribution License 4.0.42 Licensed under the Creative Commons Attribution License 4.0.
39 HTML43 HTML
40 44
@@ -45,8 +49,13 @@ module Docs · 4 unmodified lines
45 capybara.execute_script("return document.querySelector('.side-navigation').innerHTML")49 capybara.execute_script("return document.querySelector('.side-navigation').innerHTML")
46 end50 end
47 51
52 version '1.7' do
53 self.release = '1.7.8'
54 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
55 end
56
48 version '1.6' do57 version '1.6' do
49 self.release = '1.6.6'58 self.release = '1.6.9'
50 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"59 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
51 end60 end
52 61
@@ -69,5 +78,9 @@ module Docs · 16 unmodified lines
69 self.release = '1.2.32'78 self.release = '1.2.32'
70 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"79 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
71 end80 end
81
82 def get_latest_version(opts)
83 get_npm_version('angular', opts)
84 end
72 end85 end
73end86end
lib/docs/scrapers/ansible.rb+35−34
@@ -11,52 +11,47 @@ module Docs · 10 unmodified lines
11 11
12 options[:attribution] = <<-HTML12 options[:attribution] = <<-HTML
13 &copy; 2012&ndash;2018 Michael DeHaan<br>13 &copy; 2012&ndash;2018 Michael DeHaan<br>
14 &copy; 2018 Red Hat, Inc.<br>14 &copy; 2018&ndash;2019 Red Hat, Inc.<br>
15 Licensed under the GNU General Public License version 3.15 Licensed under the GNU General Public License version 3.
16 HTML16 HTML
17 17
18 version '2.6' do18 options[:skip] = %w(
19 self.release = '2.6.1'19 installation_guide/index.html
20 self.base_url = 'https://docs.ansible.com/ansible/2.6/'20 reference_appendices/glossary.html
21 reference_appendices/faq.html
22 reference_appendices/tower.html
23 user_guide/quickstart.html
24 modules/modules_by_category.html
25 modules/list_of_all_modules.html)
21 26
22 options[:skip] = %w(27 options[:skip_patterns] = [
23 installation_guide/index.html28 /\Acommunity.*/i,
24 reference_appendices/glossary.html29 /\Adev_guide.*/i,
25 reference_appendices/faq.html30 /\Aroadmap.*/i,
26 reference_appendices/tower.html31 ]
27 user_guide/quickstart.html
28 modules/modules_by_category.html
29 modules/list_of_all_modules.html)
30 32
31 options[:skip_patterns] = [33 version '2.8' do
32 /\Acommunity.*/i,34 self.release = '2.8.3'
33 /\Adev_guide.*/i,35 self.base_url = 'https://docs.ansible.com/ansible/2.7/'
34 /\Aroadmap.*/i,
35 ]
36 end36 end
37 37
38 version '2.5' do38 version '2.7' do
39 self.release = '2.5.3'39 self.release = '2.7.12'
40 self.base_url = 'https://docs.ansible.com/ansible/2.5/'40 self.base_url = 'https://docs.ansible.com/ansible/2.7/'
41 end
41 42
42 options[:skip] = %w(43 version '2.6' do
43 installation_guide/index.html44 self.release = '2.6.18'
44 reference_appendices/glossary.html45 self.base_url = 'https://docs.ansible.com/ansible/2.6/'
45 reference_appendices/faq.html46 end
46 reference_appendices/tower.html
47 user_guide/quickstart.html
48 modules/modules_by_category.html
49 modules/list_of_all_modules.html)
50 47
51 options[:skip_patterns] = [48 version '2.5' do
52 /\Acommunity.*/i,49 self.release = '2.5.15'
53 /\Adev_guide.*/i,50 self.base_url = 'https://docs.ansible.com/ansible/2.5/'
54 /\Aroadmap.*/i,
55 ]
56 end51 end
57 52
58 version '2.4' do53 version '2.4' do
59 self.release = '2.4.3'54 self.release = '2.4.6'
60 self.base_url = 'https://docs.ansible.com/ansible/2.4/'55 self.base_url = 'https://docs.ansible.com/ansible/2.4/'
61 56
62 options[:skip] = %w(57 options[:skip] = %w(
@@ -66,6 +61,12 @@ module Docs · 3 unmodified lines
66 tower.html61 tower.html
67 quickstart.html62 quickstart.html
68 list_of_all_modules.html)63 list_of_all_modules.html)
64 options[:skip_patterns] = []
65 end
66
67 def get_latest_version(opts)
68 doc = fetch_doc('https://docs.ansible.com/ansible/latest/index.html', opts)
69 doc.at_css('.DocSiteProduct-CurrentVersion').content.strip
69 end70 end
70 end71 end
71end72end
lib/docs/scrapers/apache.rb+6−1
@@ -3,7 +3,7 @@ module Docs · 2 unmodified lines
3 self.name = 'Apache HTTP Server'3 self.name = 'Apache HTTP Server'
4 self.slug = 'apache_http_server'4 self.slug = 'apache_http_server'
5 self.type = 'apache'5 self.type = 'apache'
6 self.release = '2.4.33'6 self.release = '2.4.37'
7 self.base_url = 'https://httpd.apache.org/docs/2.4/en/'7 self.base_url = 'https://httpd.apache.org/docs/2.4/en/'
8 self.links = {8 self.links = {
9 home: 'https://httpd.apache.org/'9 home: 'https://httpd.apache.org/'
@@ -33,5 +33,10 @@ module Docs · 23 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+6−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class ApachePig < UrlScraper2 class ApachePig < UrlScraper
3 self.name = 'Apache Pig'3 self.name = 'Apache Pig'
4 self.slug = 'apache_pig'4 self.slug = 'apache_pig'
5 self.type = 'apache_pig'5 self.type = 'simple'
6 self.links = {6 self.links = {
7 home: 'https://pig.apache.org/'7 home: 'https://pig.apache.org/'
8 }8 }
@@ -43,5 +43,10 @@ module Docs · 34 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+7−2
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Async < UrlScraper2 class Async < UrlScraper
3 self.type = 'async'3 self.type = 'async'
4 self.release = '2.6.0'4 self.release = '2.6.1'
5 self.base_url = 'https://caolan.github.io/async/'5 self.base_url = 'https://caolan.github.io/async/'
6 self.root_path = 'docs.html'6 self.root_path = 'docs.html'
7 self.links = {7 self.links = {
@@ -14,8 +14,13 @@ module Docs · 6 unmodified lines
14 options[:skip_links] = true14 options[:skip_links] = true
15 15
16 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
17 &copy; 2010&ndash;2017 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+26−0
@@ -0,0 +1,26 @@
1module Docs
2 class Bash < UrlScraper
3 self.type = 'bash'
4 self.release = '5.0'
5 self.base_url = 'https://www.gnu.org/software/bash/manual'
6 self.root_path = '/html_node/index.html'
7 self.links = {
8 home: 'https://www.gnu.org/software/bash/',
9 code: 'http://git.savannah.gnu.org/cgit/bash.git'
10 }
11
12 html_filters.push 'bash/entries', 'bash/clean_html'
13
14 options[:only_patterns] = [/\/html_node\//]
15
16 options[:attribution] = <<-HTML
17 Copyright &copy; 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.<br>
18 Licensed under the GNU Free Documentation License.
19 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
25 end
26end
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+10−4
@@ -8,16 +8,17 @@ module Docs · 7 unmodified lines
8 8
9 options[:trailing_slash] = true9 options[:trailing_slash] = true
10 10
11 # https://github.com/twbs/bootstrap/blob/master/LICENSE
11 options[:attribution] = <<-HTML12 options[:attribution] = <<-HTML
12 &copy; 2011&ndash;2018 Twitter, Inc.<br>13 &copy; 2011&ndash;2019 Twitter, Inc.<br>
13 &copy; 2011&ndash;2018 The Bootstrap Authors<br>14 &copy; 2011&ndash;2019 The Bootstrap Authors<br>
14 Code licensed under the MIT License.<br>15 Code licensed under the MIT License.<br>
15 Documentation licensed under the Creative Commons Attribution License v3.0.16 Documentation licensed under the Creative Commons Attribution License v3.0.
16 HTML17 HTML
17 18
18 version '4' do19 version '4' do
19 self.release = '4.1.1'20 self.release = '4.3.1'
20 self.base_url = 'https://getbootstrap.com/docs/4.1/'21 self.base_url = 'https://getbootstrap.com/docs/4.3/'
21 self.root_path = 'getting-started/introduction/'22 self.root_path = 'getting-started/introduction/'
22 23
23 html_filters.push 'bootstrap/entries_v4', 'bootstrap/clean_html_v4'24 html_filters.push 'bootstrap/entries_v4', 'bootstrap/clean_html_v4'
@@ -34,5 +35,10 @@ module Docs · 10 unmodified lines
34 35
35 options[:only] = %w(getting-started/ css/ components/ javascript/)36 options[:only] = %w(getting-started/ css/ components/ javascript/)
36 end37 end
38
39 def get_latest_version(opts)
40 doc = fetch_doc('https://getbootstrap.com/', opts)
41 doc.at_css('#bd-versions').content.strip[1..-1]
42 end
37 end43 end
38end44end
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+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Bower < UrlScraper2 class Bower < UrlScraper
3 self.name = 'Bower'3 self.name = 'Bower'
4 self.type = 'bower'4 self.type = 'simple'
5 self.release = '1.8.4'5 self.release = '1.8.4'
6 self.base_url = 'https://bower.io/docs/'6 self.base_url = 'https://bower.io/docs/'
7 self.root_path = 'api'7 self.root_path = 'api'
@@ -19,5 +19,9 @@ module Docs · 11 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−1
@@ -1,7 +1,6 @@
1module Docs1module Docs
2 class C < FileScraper2 class C < FileScraper
3 self.type = 'c'3 self.type = 'c'
4 self.dir = '/Users/Thibaut/DevDocs/Docs/c'
5 self.base_url = 'http://en.cppreference.com/w/c/'4 self.base_url = 'http://en.cppreference.com/w/c/'
6 self.root_path = 'header.html'5 self.root_path = 'header.html'
7 6
@@ -27,6 +26,13 @@ module Docs · 19 unmodified lines
27 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.26 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
28 HTML27 HTML
29 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
30 private36 private
31 37
32 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+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Chai < UrlScraper2 class Chai < UrlScraper
3 self.name = 'Chai'3 self.name = 'Chai'
4 self.type = 'chai'4 self.type = 'simple'
5 self.release = '4.1.0'5 self.release = '4.1.0'
6 self.base_url = 'http://chaijs.com'6 self.base_url = 'http://chaijs.com'
7 self.root_path = '/api/'7 self.root_path = '/api/'
@@ -23,5 +23,9 @@ module Docs · 15 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+14−4
@@ -13,19 +13,29 @@ module Docs · 12 unmodified lines
13 Licensed under the Eclipse Public License 1.0.13 Licensed under the Eclipse Public License 1.0.
14 HTML14 HTML
15 15
16 version '1.9' do16 version '1.10' do
17 self.release = '1.9'17 self.release = '1.10 (stable)'
18 self.base_url = 'https://clojure.github.io/clojure/'18 self.base_url = 'https://clojure.github.io/clojure/'
19 end19 end
20 20
21 version '1.9' do
22 self.release = '1.9 (legacy)'
23 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.9.0/'
24 end
25
21 version '1.8' do26 version '1.8' do
22 self.release = '1.8'27 self.release = '1.8 (legacy)'
23 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.8.0/'28 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.8.0/'
24 end29 end
25 30
26 version '1.7' do31 version '1.7' do
27 self.release = '1.7'32 self.release = '1.7 (legacy)'
28 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'33 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'
29 end34 end
35
36 def get_latest_version(opts)
37 doc = fetch_doc('http://clojure.github.io/clojure/index.html', opts)
38 doc.at_css('#header-version').content[1..-1]
39 end
30 end40 end
31end41end
lib/docs/scrapers/cmake.rb+40−9
@@ -13,41 +13,72 @@ module Docs · 12 unmodified lines
13 options[:title] = false13 options[:title] = false
14 options[:root_title] = 'CMake Reference Documentation'14 options[:root_title] = 'CMake Reference Documentation'
15 options[:skip] = %w(release/index.html genindex.html search.html)15 options[:skip] = %w(release/index.html genindex.html search.html)
16 options[:skip_patterns] = [/\Agenerator/, /\Ainclude/, /\Arelease/]16 options[:skip_patterns] = [/\Agenerator/, /\Acpack_gen/, /\Ainclude/, /\Arelease/]
17 17
18 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
19 &copy; 2000&ndash;2017 Kitware, Inc. and Contributors<br>19 &copy; 2000&ndash;2019 Kitware, Inc. and Contributors<br>
20 Licensed under the BSD 3-clause License.20 Licensed under the BSD 3-clause License.
21 HTML21 HTML
22 22
23 version '3.15' do
24 self.release = '3.15.2'
25 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
26 end
27
28 version '3.14' do
29 self.release = '3.14.6'
30 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
31 end
32
33 version '3.13' do
34 self.release = '3.13.5'
35 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
36 end
37
38 version '3.12' do
39 self.release = '3.12.4'
40 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
41 end
42
43 version '3.11' do
44 self.release = '3.11.4'
45 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
46 end
47
23 version '3.10' do48 version '3.10' do
24 self.release = '3.10.1'49 self.release = '3.10.3'
25 self.base_url = 'https://cmake.org/cmake/help/v3.10/'50 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
26 end51 end
27 52
28 version '3.9' do53 version '3.9' do
29 self.release = '3.9.6'54 self.release = '3.9.6'
30 self.base_url = 'https://cmake.org/cmake/help/v3.9/'55 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
31 end56 end
32 57
33 version '3.8' do58 version '3.8' do
34 self.release = '3.8.2'59 self.release = '3.8.2'
35 self.base_url = 'https://cmake.org/cmake/help/v3.8/'60 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
36 end61 end
37 62
38 version '3.7' do63 version '3.7' do
39 self.release = '3.7.2'64 self.release = '3.7.2'
40 self.base_url = 'https://cmake.org/cmake/help/v3.7/'65 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
41 end66 end
42 67
43 version '3.6' do68 version '3.6' do
44 self.release = '3.6.3'69 self.release = '3.6.3'
45 self.base_url = 'https://cmake.org/cmake/help/v3.6/'70 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
46 end71 end
47 72
48 version '3.5' do73 version '3.5' do
49 self.release = '3.5.2'74 self.release = '3.5.2'
50 self.base_url = 'https://cmake.org/cmake/help/v3.5/'75 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
76 end
77
78 def get_latest_version(opts)
79 doc = fetch_doc('https://cmake.org/documentation/', opts)
80 link = doc.at_css('.entry-content ul > li > strong > a > big')
81 link.content.scan(/([0-9.]+)/)[0][0]
51 end82 end
52 end83 end
53end84end
lib/docs/scrapers/codeception.rb+6−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Codeception < UrlScraper2 class Codeception < UrlScraper
3 self.name = 'Codeception'3 self.name = 'Codeception'
4 self.type = 'codeception'4 self.type = 'codeception'
5 self.release = '2.4.0'5 self.release = '2.5.1'
6 self.base_url = 'https://codeception.com/docs/'6 self.base_url = 'https://codeception.com/docs/'
7 self.root_path = 'index.html'7 self.root_path = 'index.html'
8 self.links = {8 self.links = {
@@ -18,5 +18,10 @@ module Docs · 9 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+6−2
@@ -1,9 +1,9 @@
1module Docs1module Docs
2 class Codeceptjs < UrlScraper2 class Codeceptjs < UrlScraper
3 self.name = 'CodeceptJS'3 self.name = 'CodeceptJS'
4 self.type = 'codeceptjs'4 self.type = 'simple'
5 self.root_path = 'index.html'5 self.root_path = 'index.html'
6 self.release = '1.3.1'6 self.release = '1.4.4'
7 self.base_url = 'https://codecept.io/'7 self.base_url = 'https://codecept.io/'
8 self.links = {8 self.links = {
9 home: 'https://codecept.io/',9 home: 'https://codecept.io/',
@@ -21,5 +21,9 @@ module Docs · 11 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/composer.rb+28−0
@@ -0,0 +1,28 @@
1module Docs
2 class Composer < UrlScraper
3 self.type = 'simple'
4 self.release = '1.9.0'
5 self.base_url = 'https://getcomposer.org/doc/'
6 self.links = {
7 home: 'https://getcomposer.org',
8 code: 'https://github.com/composer/composer'
9 }
10
11 html_filters.push 'composer/clean_html', 'composer/entries'
12
13 options[:container] = '#main'
14
15 options[:skip_patterns] = [
16 /^faqs/
17 ]
18
19 options[:attribution] = <<-HTML
20 &copy; Nils Adermann, Jordi Boggiano<br>
21 Licensed under the MIT License.
22 HTML
23
24 def get_latest_version(opts)
25 get_latest_github_release('composer', 'composer', opts)
26 end
27 end
28end
lib/docs/scrapers/cordova.rb+16−1
@@ -24,10 +24,15 @@ module Docs · 23 unmodified lines
24 end24 end
25 25
26 options[:attribution] = <<-HTML26 options[:attribution] = <<-HTML
27 &copy; 2012&ndash;2017 The Apache Software Foundation<br>27 &copy; 2012&ndash;2018 The Apache Software Foundation<br>
28 Licensed under the Apache License 2.0.28 Licensed under the Apache License 2.0.
29 HTML29 HTML
30 30
31 version '8' do
32 self.release = '8.1.2'
33 self.base_url = 'https://cordova.apache.org/docs/en/8.x/'
34 end
35
31 version '7' do36 version '7' do
32 self.release = '7.1.0'37 self.release = '7.1.0'
33 self.base_url = 'https://cordova.apache.org/docs/en/7.x/'38 self.base_url = 'https://cordova.apache.org/docs/en/7.x/'
@@ -37,5 +42,15 @@ module Docs · 3 unmodified lines
37 self.release = '6.5.0'42 self.release = '6.5.0'
38 self.base_url = 'https://cordova.apache.org/docs/en/6.x/'43 self.base_url = 'https://cordova.apache.org/docs/en/6.x/'
39 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
40 end55 end
41end56end
lib/docs/scrapers/cpp.rb+8−1
@@ -3,7 +3,6 @@ module Docs · 2 unmodified lines
3 self.name = 'C++'3 self.name = 'C++'
4 self.slug = 'cpp'4 self.slug = 'cpp'
5 self.type = 'c'5 self.type = 'c'
6 self.dir = '/Users/Thibaut/DevDocs/Docs/cpp'
7 self.base_url = 'http://en.cppreference.com/w/cpp/'6 self.base_url = 'http://en.cppreference.com/w/cpp/'
8 self.root_path = 'header.html'7 self.root_path = 'header.html'
9 8
@@ -35,6 +34,14 @@ module Docs · 25 unmodified lines
35 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.34 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
36 HTML35 HTML
37 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
38 private45 private
39 46
40 def file_path_for(*)47 def file_path_for(*)
lib/docs/scrapers/crystal.rb+6−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Crystal < UrlScraper2 class Crystal < UrlScraper
3 self.type = 'crystal'3 self.type = 'crystal'
4 self.release = '0.25.1'4 self.release = '0.27.0'
5 self.base_url = 'https://crystal-lang.org/'5 self.base_url = 'https://crystal-lang.org/'
6 self.root_path = "api/#{release}/index.html"6 self.root_path = "api/#{release}/index.html"
7 self.initial_paths = %w(docs/index.html)7 self.initial_paths = %w(docs/index.html)
@@ -34,5 +34,10 @@ module Docs · 26 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/cypress.rb+36−0
@@ -0,0 +1,36 @@
1# frozen_string_literal: true
2
3module Docs
4 class Cypress < UrlScraper
5 self.name = 'Cypress'
6 self.type = 'cypress'
7 self.release = '3.4.1'
8 self.base_url = 'https://docs.cypress.io'
9 self.root_path = '/api/api/table-of-contents.html'
10 self.links = {
11 home: 'https://www.cypress.io/',
12 code: 'https://github.com/cypress-io/cypress',
13 }
14
15 html_filters.push 'cypress/entries', 'cypress/clean_html'
16
17 options[:container] = '#content'
18 options[:max_image_size] = 300_000
19 options[:include_default_entry] = true
20
21 options[:skip_patterns] = [/examples\//]
22 options[:skip_link] = ->(link) {
23 href = link.attr(:href)
24 href.nil? ? true : EntriesFilter::SECTIONS.none? { |section| href.match?("/#{section}/") }
25 }
26
27 options[:attribution] = <<-HTML
28 &copy; 2017 Cypress.io<br>
29 Licensed under the MIT License.
30 HTML
31
32 def get_latest_version(opts)
33 get_latest_github_release('cypress-io', 'cypress', opts)
34 end
35 end
36end
lib/docs/scrapers/d.rb+6−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class D < UrlScraper2 class D < UrlScraper
3 include MultipleBaseUrls3 include MultipleBaseUrls
4 4
5 self.release = '2.081.0'5 self.release = '2.083.0'
6 self.type = 'd'6 self.type = 'd'
7 self.base_urls = ['https://dlang.org/phobos/', 'https://dlang.org/spec/']7 self.base_urls = ['https://dlang.org/phobos/', 'https://dlang.org/spec/']
8 self.root_path = 'index.html'8 self.root_path = 'index.html'
@@ -26,5 +26,10 @@ module Docs · 17 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+5−1
@@ -17,7 +17,7 @@ module Docs · 16 unmodified lines
17 HTML17 HTML
18 18
19 version '5' do19 version '5' do
20 self.release = '5.4.0'20 self.release = '5.7.0'
21 self.base_url = 'https://github.com/d3/'21 self.base_url = 'https://github.com/d3/'
22 self.root_path = 'd3/blob/master/API.md'22 self.root_path = 'd3/blob/master/API.md'
23 23
@@ -58,5 +58,9 @@ module Docs · 34 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+41−0
@@ -0,0 +1,41 @@
1module Docs
2 class Dart < FileScraper
3 self.type = 'dart'
4 self.root_path = 'index.html'
5 self.links = {
6 home: 'https://www.dartlang.org/',
7 code: 'https://github.com/dart-lang/sdk'
8 }
9
10 html_filters.push 'dart/entries', 'dart/clean_html'
11
12 options[:fix_urls] = ->(url) do
13 # localhost/dart-web_audio/..dart-io/dart-io-library.html > localhost/dart-io/dart-io-library.html
14 url.remove!(/[^\/]+\/\.\./)
15 url
16 end
17
18 options[:attribution] = <<-HTML
19 &copy; 2012 the Dart project authors<br>
20 Licensed under the Creative Commons Attribution-ShareAlike License v4.0.
21 HTML
22
23 # Download the documentation from https://www.dartlang.org/tools/sdk/archive
24
25 version '2' do
26 self.release = '2.0.0'
27 self.base_url = "https://api.dartlang.org/stable/#{release}/"
28 end
29
30 version '1' do
31 self.release = '1.24.3'
32 self.base_url = "https://api.dartlang.org/stable/#{release}/"
33 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
40 end
41end
lib/docs/scrapers/django.rb+17−7
@@ -34,34 +34,44 @@ module Docs · 33 unmodified lines
34 Licensed under the BSD License.34 Licensed under the BSD License.
35 HTML35 HTML
36 36
37 version '2.2' do
38 self.release = '2.2.4'
39 self.base_url = 'https://docs.djangoproject.com/en/2.2/'
40 end
41
42 version '2.1' do
43 self.release = '2.1.11'
44 self.base_url = 'https://docs.djangoproject.com/en/2.1/'
45 end
46
37 version '2.0' do47 version '2.0' do
38 self.release = '2.0.7'48 self.release = '2.0.13'
39 self.dir = '/Users/Thibaut/DevDocs/Docs/Django20'
40 self.base_url = 'https://docs.djangoproject.com/en/2.0/'49 self.base_url = 'https://docs.djangoproject.com/en/2.0/'
41 end50 end
42 51
43 version '1.11' do52 version '1.11' do
44 self.release = '1.11.9'53 self.release = '1.11.23'
45 self.dir = '/Users/Thibaut/DevDocs/Docs/Django111'
46 self.base_url = 'https://docs.djangoproject.com/en/1.11/'54 self.base_url = 'https://docs.djangoproject.com/en/1.11/'
47 end55 end
48 56
49 version '1.10' do57 version '1.10' do
50 self.release = '1.10.8'58 self.release = '1.10.8'
51 self.dir = '/Users/Thibaut/DevDocs/Docs/Django110'
52 self.base_url = 'https://docs.djangoproject.com/en/1.10/'59 self.base_url = 'https://docs.djangoproject.com/en/1.10/'
53 end60 end
54 61
55 version '1.9' do62 version '1.9' do
56 self.release = '1.9.13'63 self.release = '1.9.13'
57 self.dir = '/Users/Thibaut/DevDocs/Docs/Django19'
58 self.base_url = 'https://docs.djangoproject.com/en/1.9/'64 self.base_url = 'https://docs.djangoproject.com/en/1.9/'
59 end65 end
60 66
61 version '1.8' do67 version '1.8' do
62 self.release = '1.8.18'68 self.release = '1.8.18'
63 self.dir = '/Users/Thibaut/DevDocs/Docs/Django18'
64 self.base_url = 'https://docs.djangoproject.com/en/1.8/'69 self.base_url = 'https://docs.djangoproject.com/en/1.8/'
65 end70 end
71
72 def get_latest_version(opts)
73 doc = fetch_doc('https://docs.djangoproject.com/', opts)
74 doc.at_css('#doc-versions > li.current > span > strong').content
75 end
66 end76 end
67end77end
lib/docs/scrapers/docker.rb+154−27
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Docker < UrlScraper2 class Docker < UrlScraper
3 self.type = 'docker'3 self.type = 'simple'
4 self.links = {4 self.links = {
5 home: 'https://docker.com/',5 home: 'https://docker.com/',
6 code: 'https://github.com/docker/docker'6 code: 'https://github.com/docker/docker'
@@ -15,15 +15,15 @@ module Docs · 8 unmodified lines
15 end15 end
16 16
17 options[:attribution] = <<-HTML17 options[:attribution] = <<-HTML
18 &copy; 2017 Docker, Inc.<br>18 &copy; 2019 Docker, Inc.<br>
19 Licensed under the Apache License, Version 2.0.<br>19 Licensed under the Apache License, Version 2.0.<br>
20 Docker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries.<br>20 Docker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries.<br>
21 Docker, Inc. and other parties may also have trademark rights in other terms used herein.21 Docker, Inc. and other parties may also have trademark rights in other terms used herein.
22 HTML22 HTML
23 23
24 version '17' do24 version '19' do
25 self.release = '17.06'25 self.release = '19.03'
26 self.base_url = 'https://docs.docker.com/'26 self.base_url = "https://docs.docker.com/"
27 27
28 html_filters.push 'docker/entries', 'docker/clean_html'28 html_filters.push 'docker/entries', 'docker/clean_html'
29 29
@@ -33,29 +33,150 @@ module Docs · 3 unmodified lines
33 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]33 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]
34 34
35 options[:replace_paths] = {35 options[:replace_paths] = {
36 'engine/installation/linux/docker-ee/linux-postinstall/' => 'engine/installation/linux/linux-postinstall/',36 'install/linux/ubuntu/' => 'install/linux/docker-ce',
37 'engine/installation/linux/docker-ee/' => 'engine/installation/',37 'get-started/part1' => 'get-started',
38 'engine/installation/linux/docker-ce/' => 'engine/installation/',38 'engine/installation/' => 'install',
39 'engine/installation/linux/' => 'engine/installation/',39 'engine/installation/linux/linux-postinstall/' => 'install/linux',
40 'engine/installation/windows/' => 'engine/installation/',40 'compose/overview/' => 'compose',
41 'engine/userguide/intro/' => 'engine/userguide/',41 'docker-cloud/' => 'docker-hub',
42 'engine/tutorials/dockervolumes/' => 'engine/admin/volumes/volumes/',42 'datacenter/install/linux/' => 'ee',
43 'engine/getstarted/' => 'get-started/',43 'engine/userguide/' => 'config/daemon',
44 'engine/tutorials/dockerimages/' => 'get-started/',44 'engine/admin/' => 'config/daemon',
45 'engine/tutorials/dockerrepos/' => 'get-started/',45 'opensource/get-help/' => 'opensource',
46 'engine/admin/host_integration/' => 'engine/admin/start-containers-automatically/',46 'engine/tutorials/dockerimages/' => 'get-started',
47 'engine/installation/linux/rhel/' => 'engine/installation/linux/docker-ee/rhel/',47 'engine/admin/volumes/bind-mounts/' => 'storage',
48 'engine/installation/linux/ubuntulinux/' => 'engine/installation/linux/docker-ee/ubuntu/',48 'engine/tutorials/dockervolumes/' => 'storage',
49 'engine/installation/linux/suse/' => 'engine/installation/linux/docker-ee/suse/',49 'engine/admin/volumes/volumes/' => 'storage',
50 'engine/admin/logging/' => 'engine/admin/logging/view_container_logs/',50 'engine/userguide/labels-custom-metadata/' => 'config',
51 'engine/swarm/how-swarm-mode-works/' => 'engine/swarm/how-swarm-mode-works/nodes/',51 'engine/userguide/eng-image/multistage-build/' => 'develop/develop-images',
52 'engine/installation/binaries/' => 'engine/installation/linux/docker-ce/binaries/',52 'engine/swarm/networking/' => 'network',
53 'engine/admin/resource_constraints/' => 'config/containers',
54 'engine/admin/logging/overview/' => 'config/containers/logging',
55 'engine/userguide/eng-image/dockerfile_best-practices/' => 'develop/develop-images',
56 'engine/tutorials/dockerrepos/' => 'get-started',
57 'engine/userguide/networking/' => 'network',
58 'engine/userguide/networking/get-started-overlay/' => 'network',
59 'engine/reference/commandline/swarm_join_token/' => 'engine/reference/commandline',
60 'engine/understanding-docker/' => 'engine',
61 'engine/userguide/dockervolumes/' => 'storage',
62 'engine/installation/binaries/' => 'install/linux/docker-ce',
63 'engine/userguide/networking/default_network/dockerlinks/' => 'network',
64 'engine/reference/api/' => 'develop/sdk',
65 'engine/admin/systemd/' => 'config/daemon',
66 'engine/userguide/storagedriver/imagesandcontainers/' => 'storage/storagedriver',
67 'engine/api/' => 'develop/sdk',
68 'engine/userguide/networking/get-started-overlay' => 'network',
69 'engine/userguide/networking/overlay-security-model/' => 'network',
70 'engine/installation/linux/docker-ce/binaries/' => 'install/linux/docker-ce',
71 'engine/admin/volumes/' => 'storage/volumes/',
72 'engine/userguide/networking//' => 'network',
73 'engine/reference/commandline' => 'engine/reference/commandline/docker',
53 'engine/reference/commandline/' => 'engine/reference/commandline/docker/',74 'engine/reference/commandline/' => 'engine/reference/commandline/docker/',
54 'engine/reference/api/' => 'engine/api/',75 }
55 'engine/userguide/dockervolumes/' => 'engine/admin/volumes/volumes/',76 end
56 'engine/understanding-docker/' => 'engine/docker-overview/',77
57 'engine/reference/commandline/swarm_join_token/' => 'engine/reference/commandline/swarm_join-token/',78 version '18' do
58 'engine/api/getting-started/' => 'engine/api/get-started/',79 self.release = '18.09'
80 self.base_url = "https://docs.docker.com/v#{release}/"
81
82 html_filters.push 'docker/entries', 'docker/clean_html'
83
84 options[:container] = '.wrapper .container-fluid .row'
85
86 options[:only_patterns] = [/\Aget-started\//, /\Aengine\//, /\Acompose\//, /\Amachine\//, /\Anotary\//]
87 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]
88
89 options[:replace_paths] = {
90 'install/linux/ubuntu/' => 'install/linux/docker-ce',
91 'get-started/part1' => 'get-started',
92 'engine/installation/' => 'install',
93 'engine/installation/linux/linux-postinstall/' => 'install/linux',
94 'compose/overview/' => 'compose',
95 'datacenter/install/linux/' => 'ee',
96 'engine/userguide/' => 'config/daemon',
97 'engine/admin/' => 'config/daemon',
98 'opensource/get-help/' => 'opensource',
99 'engine/tutorials/dockerimages/' => 'get-started',
100 'engine/admin/volumes/bind-mounts/' => 'storage',
101 'engine/tutorials/dockervolumes/' => 'storage',
102 'engine/admin/volumes/volumes/' => 'storage',
103 'engine/userguide/labels-custom-metadata/' => 'config',
104 'engine/reference/api/' => 'develop/sdk',
105 'engine/userguide/eng-image/multistage-build/' => 'develop/develop-images',
106 'engine/swarm/networking/' => 'network',
107 'engine/admin/resource_constraints/' => 'config/containers',
108 'engine/admin/logging/overview/' => 'config/containers/logging',
109 'engine/userguide/eng-image/dockerfile_best-practices/' => 'develop/develop-images',
110 'engine/tutorials/dockerrepos/' => 'get-started',
111 'engine/userguide/networking/' => 'network',
112 'engine/userguide/networking/get-started-overlay/' => 'network',
113 'engine/understanding-docker/' => 'engine',
114 'engine/reference/commandline/swarm_join_token/' => 'engine/reference/commandline',
115 'engine/userguide/dockervolumes/' => 'storage',
116 'engine/admin/systemd/' => 'config/daemon',
117 'engine/userguide/storagedriver/imagesandcontainers/' => 'storage/storagedriver',
118 'engine/installation/binaries/' => 'install/linux/docker-ce',
119 'engine/userguide/networking/default_network/dockerlinks/' => 'network',
120 'engine/userguide/networking/overlay-security-model/' => 'network',
121 'engine/userguide/networking/get-started-overlay' => 'network',
122 'engine/api/' => 'develop/sdk',
123 'engine/installation/linux/docker-ce/binaries/' => 'install/linux/docker-ce',
124 'engine/admin/volumes/' => 'storage/volumes/',
125 'engine/userguide/networking//' => 'network',
126 'engine/reference/commandline' => 'engine/reference/commandline/docker',
127 'engine/reference/commandline/' => 'engine/reference/commandline/docker/',
128 }
129 end
130
131 version '17' do
132 self.release = '17.12'
133 self.base_url = "https://docs.docker.com/v#{release}/"
134
135 html_filters.push 'docker/entries', 'docker/clean_html'
136
137 options[:container] = '.wrapper .container-fluid .row'
138
139 options[:only_patterns] = [/\Aget-started\//, /\Aengine\//, /\Acompose\//, /\Amachine\//, /\Anotary\//]
140 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]
141
142 options[:replace_paths] = {
143 'get-started/part1' => 'get-started',
144 'engine/installation/' => 'install',
145 'engine/installation/linux/linux-postinstall/' => 'install/linux',
146 'opensource/get-help/' => 'opensource',
147 'engine/admin/volumes/volumes/' => 'storage',
148 'engine/tutorials/dockerimages/' => 'get-started',
149 'engine/admin/volumes/bind-mounts/' => 'storage',
150 'engine/tutorials/dockervolumes/' => 'storage',
151 'datacenter/install/aws/' => 'docker-for-aws',
152 'engine/userguide/' => 'config/daemon',
153 'engine/admin/' => 'config/daemon',
154 'engine/userguide/labels-custom-metadata/' => 'config',
155 'engine/userguide/eng-image/multistage-build/' => 'develop/develop-images',
156 'engine/swarm/networking/' => 'network',
157 'engine/admin/resource_constraints/' => 'config/containers',
158 'engine/admin/logging/overview/' => 'config/containers/logging',
159 'engine/understanding-docker/' => 'engine',
160 'engine/userguide/eng-image/dockerfile_best-practices/' => 'develop/develop-images',
161 'engine/tutorials/dockerrepos/' => 'get-started',
162 'engine/userguide/networking/' => 'network',
163 'engine/reference/commandline/swarm_join_token/' => 'edge/engine/reference/commandline',
164 'engine/userguide/networking/get-started-overlay/' => 'network',
165 'engine/userguide/dockervolumes/' => 'storage',
166 'engine/installation/binaries/' => 'install/linux/docker-ce',
167 'engine/userguide/networking/default_network/dockerlinks/' => 'network',
168 'engine/reference/api/' => 'develop/sdk',
169 'engine/admin/live-restore/' => 'config/containers',
170 'engine/api/' => 'develop/sdk',
171 'engine/userguide/networking/get-started-overlay' => 'network',
172 'security/security/' => 'engine/security',
173 'engine/installation/linux/docker-ce/binaries/' => 'install/linux/docker-ce',
174 'engine/reference/commandline/' => 'edge/engine/reference/commandline',
175 'engine/admin/systemd/' => 'config/daemon',
176 'engine/userguide/storagedriver/imagesandcontainers/' => 'storage/storagedriver',
177 'engine/userguide/networking/overlay-security-model/' => 'network',
178 'engine/admin/volumes/' => 'storage/volumes/',
179 'engine/userguide/networking//' => 'network',
59 }180 }
60 end181 end
61 182
@@ -137,5 +258,11 @@ module Docs · 75 unmodified lines
137 options[:container] = '#docs'258 options[:container] = '#docs'
138 options[:only_patterns] << /\Aswarm\//259 options[:only_patterns] << /\Aswarm\//
139 end260 end
261
262 def get_latest_version(opts)
263 doc = fetch_doc('https://docs.docker.com/', opts)
264 label = doc.at_css('.nav-container button.dropdown-toggle').content.strip
265 label.scan(/([0-9.]+)/)[0][0]
266 end
140 end267 end
141end268end
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+6−1
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Electron < UrlScraper2 class Electron < UrlScraper
3 self.type = 'electron'3 self.type = 'simple'
4 self.base_url = 'https://electronjs.org/docs'4 self.base_url = 'https://electronjs.org/docs'
5 self.release = '2.0.2'5 self.release = '2.0.2'
6 self.links = {6 self.links = {
@@ -22,5 +22,10 @@ module Docs · 15 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+45−1
@@ -33,8 +33,47 @@ module Docs · 32 unmodified lines
33 "https://elixir-lang.org/getting-started/introduction.html" ]33 "https://elixir-lang.org/getting-started/introduction.html" ]
34 end34 end
35 35
36 version '1.9' do
37 self.release = '1.9.1'
38 self.base_urls = [
39 "https://hexdocs.pm/elixir/#{release}/",
40 "https://hexdocs.pm/eex/#{release}/",
41 "https://hexdocs.pm/ex_unit/#{release}/",
42 "https://hexdocs.pm/iex/#{release}/",
43 "https://hexdocs.pm/logger/#{release}/",
44 "https://hexdocs.pm/mix/#{release}/",
45 'https://elixir-lang.org/getting-started/'
46 ]
47 end
48
49 version '1.8' do
50 self.release = '1.8.2'
51 self.base_urls = [
52 "https://hexdocs.pm/elixir/#{release}/",
53 "https://hexdocs.pm/eex/#{release}/",
54 "https://hexdocs.pm/ex_unit/#{release}/",
55 "https://hexdocs.pm/iex/#{release}/",
56 "https://hexdocs.pm/logger/#{release}/",
57 "https://hexdocs.pm/mix/#{release}/",
58 'https://elixir-lang.org/getting-started/'
59 ]
60 end
61
62 version '1.7' do
63 self.release = '1.7.4'
64 self.base_urls = [
65 "https://hexdocs.pm/elixir/#{release}/",
66 "https://hexdocs.pm/eex/#{release}/",
67 "https://hexdocs.pm/ex_unit/#{release}/",
68 "https://hexdocs.pm/iex/#{release}/",
69 "https://hexdocs.pm/logger/#{release}/",
70 "https://hexdocs.pm/mix/#{release}/",
71 'https://elixir-lang.org/getting-started/'
72 ]
73 end
74
36 version '1.6' do75 version '1.6' do
37 self.release = '1.6.5'76 self.release = '1.6.6'
38 self.base_urls = [77 self.base_urls = [
39 "https://hexdocs.pm/elixir/#{release}/",78 "https://hexdocs.pm/elixir/#{release}/",
40 "https://hexdocs.pm/eex/#{release}/",79 "https://hexdocs.pm/eex/#{release}/",
@@ -84,5 +123,10 @@ module Docs · 43 unmodified lines
84 'https://elixir-lang.org/getting-started/'123 'https://elixir-lang.org/getting-started/'
85 ]124 ]
86 end125 end
126
127 def get_latest_version(opts)
128 doc = fetch_doc('https://hexdocs.pm/elixir/api-reference.html', opts)
129 doc.at_css('h2.sidebar-projectVersion').content.strip[1..-1]
130 end
87 end131 end
88end132end
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−4
@@ -42,22 +42,23 @@ module Docs · 41 unmodified lines
42 42
43 version '21' do43 version '21' do
44 self.release = '21.0'44 self.release = '21.0'
45 self.dir = '/Users/Thibaut/DevDocs/Docs/Erlang21'
46 end45 end
47 46
48 version '20' do47 version '20' do
49 self.release = '20.3'48 self.release = '20.3'
50 self.dir = '/Users/Thibaut/DevDocs/Docs/Erlang20'
51 end49 end
52 50
53 version '19' do51 version '19' do
54 self.release = '19.3'52 self.release = '19.3'
55 self.dir = '/Users/Thibaut/DevDocs/Docs/Erlang19'
56 end53 end
57 54
58 version '18' do55 version '18' do
59 self.release = '18.3'56 self.release = '18.3'
60 self.dir = '/Users/Thibaut/DevDocs/Docs/Erlang18'57 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 /, '')
61 end62 end
62 end63 end
63end64end
lib/docs/scrapers/eslint.rb+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Eslint < UrlScraper2 class Eslint < UrlScraper
3 self.name = 'ESLint'3 self.name = 'ESLint'
4 self.type = 'simple'4 self.type = 'simple'
5 self.release = '4.19.0'5 self.release = '5.8.0'
6 self.base_url = 'https://eslint.org/docs/'6 self.base_url = 'https://eslint.org/docs/'
7 self.root_path = 'user-guide/getting-started'7 self.root_path = 'user-guide/getting-started'
8 self.links = {8 self.links = {
@@ -20,5 +20,9 @@ module Docs · 11 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+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Express < UrlScraper2 class Express < UrlScraper
3 self.name = 'Express'3 self.name = 'Express'
4 self.type = 'express'4 self.type = 'express'
5 self.release = '4.16.1'5 self.release = '4.16.3'
6 self.base_url = 'http://expressjs.com/en/'6 self.base_url = 'http://expressjs.com/en/'
7 self.root_path = '4x/api.html'7 self.root_path = '4x/api.html'
8 self.initial_paths = %w(8 self.initial_paths = %w(
@@ -28,5 +28,9 @@ module Docs · 19 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+12−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Fish < UrlScraper2 class Fish < UrlScraper
3 self.name = 'Fish'3 self.name = 'Fish'
4 self.type = 'fish'4 self.type = 'simple'
5 self.root_path = 'index.html'5 self.root_path = 'index.html'
6 self.links = {6 self.links = {
7 home: 'https://fishshell.com/',7 home: 'https://fishshell.com/',
@@ -12,11 +12,17 @@ module Docs · 4 unmodified lines
12 12
13 options[:skip] = %w(design.html license.html)13 options[:skip] = %w(design.html license.html)
14 14
15 # https://fishshell.com/docs/current/license.html
15 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
16 &copy; 2005&ndash;2009 Axel Liljencrantz<br>17 &copy; 2005&ndash;2009 Axel Liljencrantz<br>
17 Licensed under the GNU General Public License, version 2.18 Licensed under the GNU General Public License, version 2.
18 HTML19 HTML
19 20
21 version '3.0' do
22 self.release = '3.0.1'
23 self.base_url = "https://fishshell.com/docs/#{version}/"
24 end
25
20 version '2.7' do26 version '2.7' do
21 self.release = '2.7.1'27 self.release = '2.7.1'
22 self.base_url = "https://fishshell.com/docs/#{version}/"28 self.base_url = "https://fishshell.com/docs/#{version}/"
@@ -46,5 +52,10 @@ module Docs · 23 unmodified lines
46 self.release = '2.2.0'52 self.release = '2.2.0'
47 self.base_url = "https://fishshell.com/docs/#{version}/"53 self.base_url = "https://fishshell.com/docs/#{version}/"
48 end54 end
55
56 def get_latest_version(opts)
57 doc = fetch_doc('http://fishshell.com/docs/current/index.html', opts)
58 doc.at_css('#toc-index').content.scan(/([0-9.]+)/)[0][0]
59 end
49 end60 end
50end61end
lib/docs/scrapers/flow.rb+6−2
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Flow < UrlScraper2 class Flow < UrlScraper
3 self.type = 'flow'3 self.type = 'simple'
4 self.release = '0.75.0'4 self.release = '0.85.0'
5 self.base_url = 'https://flow.org/en/docs/'5 self.base_url = 'https://flow.org/en/docs/'
6 self.links = {6 self.links = {
7 home: 'https://flow.org/',7 home: 'https://flow.org/',
@@ -18,5 +18,9 @@ module Docs · 10 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+6−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Git < UrlScraper2 class Git < UrlScraper
3 self.type = 'git'3 self.type = 'git'
4 self.release = '2.17.0'4 self.release = '2.19.1'
5 self.base_url = 'https://git-scm.com/docs'5 self.base_url = 'https://git-scm.com/docs'
6 self.initial_paths = %w(/git.html)6 self.initial_paths = %w(/git.html)
7 self.links = {7 self.links = {
@@ -19,5 +19,10 @@ module Docs · 11 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.rb+1−1
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Gnu < FileScraper2 class Gnu < FileScraper
3 self.type = 'gnu'3 self.type = 'simple'
4 self.root_path = 'index.html'4 self.root_path = 'index.html'
5 self.abstract = 'true'5 self.abstract = 'true'
6 6
lib/docs/scrapers/gnu/gcc.rb+6−8
@@ -48,13 +48,11 @@ module Docs · 47 unmodified lines
48 48
49 version '7' do49 version '7' do
50 self.release = '7.3.0'50 self.release = '7.3.0'
51 self.dir = '/Users/Thibaut/DevDocs/Docs/gcc7'
52 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"51 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
53 end52 end
54 53
55 version '7 CPP' do54 version '7 CPP' do
56 self.release = '7.3.0'55 self.release = '7.3.0'
57 self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp7'
58 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"56 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
59 57
60 options[:replace_paths] = CPP_PATHS58 options[:replace_paths] = CPP_PATHS
@@ -62,7 +60,6 @@ module Docs · 1 unmodified line
62 60
63 version '6' do61 version '6' do
64 self.release = '6.4.0'62 self.release = '6.4.0'
65 self.dir = '/Users/Thibaut/DevDocs/Docs/gcc6'
66 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"63 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
67 64
68 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'65 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -70,7 +67,6 @@ module Docs · 1 unmodified line
70 67
71 version '6 CPP' do68 version '6 CPP' do
72 self.release = '6.4.0'69 self.release = '6.4.0'
73 self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp6'
74 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"70 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
75 71
76 options[:replace_paths] = CPP_PATHS72 options[:replace_paths] = CPP_PATHS
@@ -78,7 +74,6 @@ module Docs · 1 unmodified line
78 74
79 version '5' do75 version '5' do
80 self.release = '5.4.0'76 self.release = '5.4.0'
81 self.dir = '/Users/Thibaut/DevDocs/Docs/gcc5'
82 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"77 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
83 78
84 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'79 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -86,7 +81,6 @@ module Docs · 1 unmodified line
86 81
87 version '5 CPP' do82 version '5 CPP' do
88 self.release = '5.4.0'83 self.release = '5.4.0'
89 self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp5'
90 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"84 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
91 85
92 options[:replace_paths] = CPP_PATHS86 options[:replace_paths] = CPP_PATHS
@@ -94,7 +88,6 @@ module Docs · 1 unmodified line
94 88
95 version '4' do89 version '4' do
96 self.release = '4.9.3'90 self.release = '4.9.3'
97 self.dir = '/Users/Thibaut/DevDocs/Docs/gcc4'
98 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"91 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
99 92
100 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'93 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -102,10 +95,15 @@ module Docs · 1 unmodified line
102 95
103 version '4 CPP' do96 version '4 CPP' do
104 self.release = '4.9.3'97 self.release = '4.9.3'
105 self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp4'
106 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"98 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
107 99
108 options[:replace_paths] = CPP_PATHS100 options[:replace_paths] = CPP_PATHS
109 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
110 end108 end
111end109end
lib/docs/scrapers/gnu/gnu_fortran.rb+6−4
@@ -8,26 +8,28 @@ module Docs · 7 unmodified lines
8 8
9 version '7' do9 version '7' do
10 self.release = '7.3.0'10 self.release = '7.3.0'
11 self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran7'
12 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"11 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
13 end12 end
14 13
15 version '6' do14 version '6' do
16 self.release = '6.4.0'15 self.release = '6.4.0'
17 self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran6'
18 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"16 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
19 end17 end
20 18
21 version '5' do19 version '5' do
22 self.release = '5.4.0'20 self.release = '5.4.0'
23 self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran5'
24 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"21 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
25 end22 end
26 23
27 version '4' do24 version '4' do
28 self.release = '4.9.3'25 self.release = '4.9.3'
29 self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran4'
30 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"26 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
31 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
32 end34 end
33end35end
lib/docs/scrapers/go.rb+6−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Go < UrlScraper2 class Go < UrlScraper
3 self.type = 'go'3 self.type = 'go'
4 self.release = '1.10.1'4 self.release = '1.12'
5 self.base_url = 'https://golang.org/pkg/'5 self.base_url = 'https://golang.org/pkg/'
6 self.links = {6 self.links = {
7 home: 'https://golang.org/',7 home: 'https://golang.org/',
@@ -24,6 +24,11 @@ module Docs · 16 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+6−1
@@ -29,7 +29,7 @@ module Docs · 28 unmodified lines
29 end29 end
30 30
31 version '3.0' do31 version '3.0' do
32 self.release = '3.0'32 self.release = '3.0.6'
33 self.base_url = "http://docs.godotengine.org/en/#{self.version}/"33 self.base_url = "http://docs.godotengine.org/en/#{self.version}/"
34 end34 end
35 35
@@ -37,5 +37,10 @@ module Docs · 1 unmodified line
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+26−0
@@ -0,0 +1,26 @@
1module Docs
2 class Graphite < UrlScraper
3 self.type = 'graphite'
4 self.release = '1.1.4'
5 self.base_url = 'https://graphite.readthedocs.io/en/latest/'
6 self.links = {
7 code: 'https://github.com/graphite-project/graphite-web'
8 }
9
10 html_filters.push 'graphite/entries', 'graphite/clean_html'
11
12 options[:container] = '.document > div'
13 options[:skip] = %w(releases.html who-is-using.html composer.html search.html py-modindex.html genindex.html)
14
15 options[:attribution] = <<-HTML
16 &copy; 2008&ndash;2012 Chris Davis<br>
17 &copy; 2011&ndash;2016 The Graphite Project<br>
18 Licensed under the Apache License, Version 2.0.
19 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
25 end
26end
lib/docs/scrapers/grunt.rb+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Grunt < UrlScraper2 class Grunt < UrlScraper
3 self.name = 'Grunt'3 self.name = 'Grunt'
4 self.type = 'grunt'4 self.type = 'simple'
5 self.release = '1.0.1'5 self.release = '1.0.1'
6 self.base_url = 'https://gruntjs.com/'6 self.base_url = 'https://gruntjs.com/'
7 self.root_path = 'getting-started'7 self.root_path = 'getting-started'
@@ -26,5 +26,9 @@ module Docs · 18 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+27−0
@@ -0,0 +1,27 @@
1module Docs
2 class Handlebars < UrlScraper
3 self.name = 'Handlebars.js'
4 self.slug = 'handlebars'
5 self.type = 'simple'
6 self.release = '4.0.12'
7 self.base_url = 'https://handlebarsjs.com/'
8 self.links = {
9 home: 'https://handlebarsjs.com/',
10 code: 'https://github.com/wycats/handlebars.js/'
11 }
12
13 html_filters.push 'handlebars/entries', 'handlebars/clean_html', 'title'
14
15 options[:container] = '#contents'
16 options[:root_title] = 'Handlebars.js'
17
18 options[:attribution] = <<-HTML
19 &copy; 2011&ndash;2017 by Yehuda Katz<br>
20 Licensed under the MIT License.
21 HTML
22
23 def get_latest_version(opts)
24 get_npm_version('handlebars', opts)
25 end
26 end
27end
lib/docs/scrapers/haskell.rb+9−2
@@ -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] = [
@@ -57,7 +57,7 @@ module Docs · 40 unmodified lines
57 end57 end
58 58
59 version '8' do59 version '8' do
60 self.release = '8.2.1'60 self.release = '8.6.1'
61 self.base_url = "https://downloads.haskell.org/~ghc/#{release}/docs/html/"61 self.base_url = "https://downloads.haskell.org/~ghc/#{release}/docs/html/"
62 end62 end
63 63
@@ -68,5 +68,12 @@ module Docs · 4 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+7−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Haxe < UrlScraper2 class Haxe < UrlScraper
3 self.name = 'Haxe'3 self.name = 'Haxe'
4 self.type = 'haxe'4 self.type = 'simple'
5 self.release = '3.4.7'5 self.release = '3.4.7'
6 self.base_url = 'https://api.haxe.org/'6 self.base_url = 'https://api.haxe.org/'
7 7
@@ -66,5 +66,11 @@ module Docs · 58 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+6−3
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Homebrew < UrlScraper2 class Homebrew < UrlScraper
3 self.name = 'Homebrew'3 self.name = 'Homebrew'
4 self.type = 'simple'4 self.type = 'simple'
5 self.release = '1.4.2'5 self.release = '2.1.9'
6 self.base_url = 'https://docs.brew.sh/'6 self.base_url = 'https://docs.brew.sh/'
7 self.links = {7 self.links = {
8 home: 'https://brew.sh',8 home: 'https://brew.sh',
@@ -13,12 +13,15 @@ module Docs · 4 unmodified lines
13 13
14 options[:container] = ->(filter) { filter.root_page? ? '#home' : '#page' }14 options[:container] = ->(filter) { filter.root_page? ? '#home' : '#page' }
15 15
16 options[:skip_patterns] = [/maintainer/i, /core\-contributor/i]16 options[:skip_patterns] = [/maintainer/i, /core\-contributor/i, /kickstarter/i]
17 options[:skip] = %w(Kickstarter-Supporters.html)
18 17
19 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
20 &copy; 2009&ndash;present Homebrew contributors<br>19 &copy; 2009&ndash;present Homebrew contributors<br>
21 Licensed under the BSD 2-Clause License.20 Licensed under the BSD 2-Clause License.
22 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_latest_github_release('Homebrew', 'brew', opts)
25 end
23 end26 end
24end27end
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+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Immutable < UrlScraper2 class Immutable < UrlScraper
3 self.name = 'Immutable.js'3 self.name = 'Immutable.js'
4 self.slug = 'immutable'4 self.slug = 'immutable'
5 self.type = 'immutable'5 self.type = 'simple'
6 self.release = '3.8.1'6 self.release = '3.8.1'
7 self.base_url = 'https://facebook.github.io/immutable-js/docs/'7 self.base_url = 'https://facebook.github.io/immutable-js/docs/'
8 self.links = {8 self.links = {
@@ -54,5 +54,9 @@ module Docs · 45 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+7−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Influxdata < UrlScraper2 class Influxdata < UrlScraper
3 self.name = 'InfluxData'3 self.name = 'InfluxData'
4 self.type = 'influxdata'4 self.type = 'simple'
5 self.release = '1.3'5 self.release = '1.3'
6 self.base_url = 'https://docs.influxdata.com/'6 self.base_url = 'https://docs.influxdata.com/'
7 self.links = {7 self.links = {
@@ -46,5 +46,11 @@ module Docs · 38 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+7−3
@@ -1,8 +1,8 @@
1module Docs1module Docs
2 class Jasmine < UrlScraper2 class Jasmine < UrlScraper
3 self.type = 'jasmine'3 self.type = 'simple'
4 self.release = '3.1.0'4 self.release = '3.3.0'
5 self.base_url = 'https://jasmine.github.io/api/3.0/'5 self.base_url = 'https://jasmine.github.io/api/3.2/'
6 self.root_path = 'index.html'6 self.root_path = 'index.html'
7 self.links = {7 self.links = {
8 home: 'https://jasmine.github.io/',8 home: 'https://jasmine.github.io/',
@@ -17,5 +17,9 @@ module Docs · 8 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+7−2
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Jest < UrlScraper2 class Jest < UrlScraper
3 self.type = 'jest'3 self.type = 'simple'
4 self.release = '23.3.0'4 self.release = '23.5.0'
5 self.base_url = 'https://jestjs.io/docs/en/'5 self.base_url = 'https://jestjs.io/docs/en/'
6 self.root_path = 'getting-started'6 self.root_path = 'getting-started'
7 self.links = {7 self.links = {
@@ -17,5 +17,10 @@ module Docs · 9 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+27−3
@@ -8,13 +8,33 @@ module Docs · 7 unmodified lines
8 options[:only_patterns] = [/\Amanual\//, /\Astdlib\//]8 options[:only_patterns] = [/\Amanual\//, /\Astdlib\//]
9 9
10 options[:attribution] = <<-HTML10 options[:attribution] = <<-HTML
11 &copy; 2009&ndash;2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors<br>11 &copy; 2009&ndash;2018 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors<br>
12 Licensed under the MIT License.12 Licensed under the MIT License.
13 HTML13 HTML
14 14
15 version '1.0' do
16 self.release = '1.0.1'
17 self.base_url = "https://docs.julialang.org/en/v#{release}/"
18 self.type = 'julia'
19
20 html_filters.push 'julia/entries', 'julia/clean_html'
21
22 options[:container] = '#docs'
23 end
24
25 version '0.7' do
26 self.release = '0.7.0'
27 self.base_url = "https://docs.julialang.org/en/v#{release}/"
28 self.type = 'julia'
29
30 html_filters.push 'julia/entries', 'julia/clean_html'
31
32 options[:container] = '#docs'
33 end
34
15 version '0.6' do35 version '0.6' do
16 self.release = '0.6.2'36 self.release = '0.6.2'
17 self.base_url = 'https://docs.julialang.org/en/release-0.6/'37 self.base_url = "https://docs.julialang.org/en/v#{release}/"
18 self.type = 'julia'38 self.type = 'julia'
19 39
20 html_filters.push 'julia/entries', 'julia/clean_html'40 html_filters.push 'julia/entries', 'julia/clean_html'
@@ -24,10 +44,14 @@ module Docs · 3 unmodified lines
24 44
25 version '0.5' do45 version '0.5' do
26 self.release = '0.5.2'46 self.release = '0.5.2'
27 self.base_url = 'https://docs.julialang.org/en/release-0.5/'47 self.base_url = "https://docs.julialang.org/en/v#{release}/"
28 self.type = 'sphinx_simple'48 self.type = 'sphinx_simple'
29 49
30 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'
31 end51 end
52
53 def get_latest_version(opts)
54 get_latest_github_release('JuliaLang', 'julia', opts)
55 end
32 end56 end
33end57end
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+5−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
3module Docs3module Docs
4 class Koa < Github4 class Koa < Github
5 self.base_url = 'https://github.com/koajs/koa/blob/master/docs/'5 self.base_url = 'https://github.com/koajs/koa/blob/master/docs/'
6 self.release = '2.5.1'6 self.release = '2.6.1'
7 7
8 self.root_path = 'api/index.md'8 self.root_path = 'api/index.md'
9 self.initial_paths = %w[9 self.initial_paths = %w[
@@ -34,5 +34,9 @@ module Docs · 24 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+15−3
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Kotlin < UrlScraper2 class Kotlin < UrlScraper
3 self.type = 'kotlin'3 self.type = 'kotlin'
4 self.release = '1.2.41'4 self.release = '1.3.41'
5 self.base_url = 'https://kotlinlang.org/'5 self.base_url = 'https://kotlinlang.org/'
6 self.root_path = 'api/latest/jvm/stdlib/index.html'6 self.root_path = 'api/latest/jvm/stdlib/index.html'
7 self.links = {7 self.links = {
@@ -22,11 +22,23 @@ module Docs · 14 unmodified lines
22 docs/events.html22 docs/events.html
23 docs/resources.html23 docs/resources.html
24 docs/reference/grammar.html)24 docs/reference/grammar.html)
25 options[:replace_paths] = { 'api/latest/jvm/stdlib/' => 'api/latest/jvm/stdlib/index.html' }25 options[:replace_paths] = {
26 'api/latest/jvm/stdlib/' => 'api/latest/jvm/stdlib/index.html',
27 'docs/reference/coroutines.html' => 'docs/reference/coroutines-overview.html',
28 'api/latest/jvm/stdlib/kotlin/fold.html' => 'api/latest/jvm/stdlib/kotlin.collections/fold.html',
29 'api/latest/jvm/stdlib/kotlin/get-or-else.html' => 'api/latest/jvm/stdlib/kotlin.collections/get-or-else.html',
30 'api/latest/jvm/stdlib/kotlin/map.html' => 'api/latest/jvm/stdlib/kotlin.collections/map.html',
31 'docs/tutorials/native/targeting-multiple-platforms.html' => 'docs/tutorials/native/basic-kotlin-native-app.html',
32 'api/latest/jvm/stdlib/kotlin/-throwable/print-stack-trace.html' => 'api/latest/jvm/stdlib/kotlin/print-stack-trace.html',
33 }
26 34
27 options[:attribution] = <<-HTML35 options[:attribution] = <<-HTML
28 &copy; 2010&ndash;2018 JetBrains s.r.o.<br>36 &copy; 2010&ndash;2019 JetBrains s.r.o.<br>
29 Licensed under the Apache License, Version 2.0.37 Licensed under the Apache License, Version 2.0.
30 HTML38 HTML
39
40 def get_latest_version(opts)
41 get_latest_github_release('JetBrains', 'kotlin', opts)
42 end
31 end43 end
32end44end
lib/docs/scrapers/laravel.rb+18−1
@@ -29,8 +29,21 @@ module Docs · 28 unmodified lines
29 Laravel is a trademark of Taylor Otwell.29 Laravel is a trademark of Taylor Otwell.
30 HTML30 HTML
31 31
32 version '5.7' do
33 self.release = '5.7.7'
34 self.root_path = '/api/5.7/index.html'
35 self.initial_paths = %w(/docs/5.7/installation /api/5.7/classes.html)
36
37 options[:only_patterns] = [%r{\A/api/5\.7/}, %r{\A/docs/5\.7/}]
38
39 options[:fix_urls] = ->(url) do
40 url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\/(?!\d)}, "#{Laravel.base_url}/docs/5.7/"
41 url
42 end
43 end
44
32 version '5.6' do45 version '5.6' do
33 self.release = '5.6.12'46 self.release = '5.6.33'
34 self.root_path = '/api/5.6/index.html'47 self.root_path = '/api/5.6/index.html'
35 self.initial_paths = %w(/docs/5.6/installation /api/5.6/classes.html)48 self.initial_paths = %w(/docs/5.6/installation /api/5.6/classes.html)
36 49
@@ -120,5 +133,9 @@ module Docs · 83 unmodified lines
120 url133 url
121 end134 end
122 end135 end
136
137 def get_latest_version(opts)
138 get_latest_github_release('laravel', 'laravel', opts)
139 end
123 end140 end
124end141end
lib/docs/scrapers/leaflet.rb+17−2
@@ -14,13 +14,23 @@ module Docs · 13 unmodified lines
14 options[:skip_links] = true14 options[:skip_links] = true
15 15
16 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
17 &copy; 2010&ndash;2018 Vladimir Agafonkin<br>17 &copy; 2010&ndash;2019 Vladimir Agafonkin<br>
18 &copy; 2010&ndash;2011, CloudMade<br>18 &copy; 2010&ndash;2011, CloudMade<br>
19 Maps &copy; OpenStreetMap contributors.19 Maps &copy; OpenStreetMap contributors.
20 HTML20 HTML
21 21
22 version '1.5' do
23 self.release = '1.5.0'
24 self.base_url = "https://leafletjs.com/reference-#{release}.html"
25 end
26
27 version '1.4' do
28 self.release = '1.4.0'
29 self.base_url = "https://leafletjs.com/reference-#{release}.html"
30 end
31
22 version '1.3' do32 version '1.3' do
23 self.release = '1.3.0'33 self.release = '1.3.4'
24 self.base_url = "https://leafletjs.com/reference-#{release}.html"34 self.base_url = "https://leafletjs.com/reference-#{release}.html"
25 end35 end
26 36
@@ -39,5 +49,10 @@ module Docs · 12 unmodified lines
39 self.base_url = "https://leafletjs.com/reference-#{release}.html"49 self.base_url = "https://leafletjs.com/reference-#{release}.html"
40 end50 end
41 51
52 def get_latest_version(opts)
53 doc = fetch_doc('https://leafletjs.com/index.html', opts)
54 link = doc.css('ul > li > a').to_a.select {|node| node.content == 'Docs'}.first
55 link['href'].scan(/reference-([0-9.]+)\.html/)[0][0]
56 end
42 end57 end
43end58end
lib/docs/scrapers/less.rb+7−1
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Less < UrlScraper2 class Less < UrlScraper
3 self.type = 'less'3 self.type = 'simple'
4 self.release = '2.7.2'4 self.release = '2.7.2'
5 self.base_url = 'http://lesscss.org'5 self.base_url = 'http://lesscss.org'
6 self.root_path = '/features'6 self.root_path = '/features'
@@ -21,5 +21,11 @@ module Docs · 14 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+6−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Lodash < UrlScraper2 class Lodash < UrlScraper
3 self.name = 'lodash'3 self.name = 'lodash'
4 self.slug = 'lodash'4 self.slug = 'lodash'
5 self.type = 'lodash'5 self.type = 'simple'
6 self.links = {6 self.links = {
7 home: 'https://lodash.com/',7 home: 'https://lodash.com/',
8 code: 'https://github.com/lodash/lodash/'8 code: 'https://github.com/lodash/lodash/'
@@ -32,5 +32,10 @@ module Docs · 23 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+12−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Marionette < UrlScraper2 class Marionette < UrlScraper
3 self.name = 'Marionette.js'3 self.name = 'Marionette.js'
4 self.slug = 'marionette'4 self.slug = 'marionette'
5 self.type = 'marionette'5 self.type = 'simple'
6 self.root_path = 'index'6 self.root_path = 'index'
7 self.links = {7 self.links = {
8 home: 'https://marionettejs.com/',8 home: 'https://marionettejs.com/',
@@ -18,6 +18,13 @@ module Docs · 9 unmodified lines
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20 20
21 version '4' do
22 self.release = '4.0.0'
23 self.base_url = "https://marionettejs.com/docs/v#{release}/"
24
25 html_filters.push 'marionette/entries_v3'
26 end
27
21 version '3' do28 version '3' do
22 self.release = '3.5.1'29 self.release = '3.5.1'
23 self.base_url = "https://marionettejs.com/docs/v#{release}/"30 self.base_url = "https://marionettejs.com/docs/v#{release}/"
@@ -31,5 +38,9 @@ module Docs · 7 unmodified lines
31 38
32 html_filters.push 'marionette/entries_v2'39 html_filters.push 'marionette/entries_v2'
33 end40 end
41
42 def get_latest_version(opts)
43 get_npm_version('backbone.marionette', opts)
44 end
34 end45 end
35end46end
lib/docs/scrapers/markdown.rb+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Markdown < UrlScraper2 class Markdown < UrlScraper
3 self.name = 'Markdown'3 self.name = 'Markdown'
4 self.type = 'markdown'4 self.type = 'simple'
5 self.base_url = 'http://daringfireball.net/projects/markdown/syntax'5 self.base_url = 'http://daringfireball.net/projects/markdown/syntax'
6 6
7 html_filters.push 'markdown/clean_html', 'markdown/entries'7 html_filters.push 'markdown/clean_html', 'markdown/entries'
@@ -13,5 +13,9 @@ module Docs · 5 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+42−11
@@ -6,7 +6,7 @@ module Docs · 5 unmodified lines
6 self.type = 'sphinx'6 self.type = 'sphinx'
7 self.root_path = 'index.html'7 self.root_path = 'index.html'
8 self.links = {8 self.links = {
9 home: 'http://matplotlib.org/',9 home: 'https://matplotlib.org/',
10 code: 'https://github.com/matplotlib/matplotlib'10 code: 'https://github.com/matplotlib/matplotlib'
11 }11 }
12 12
@@ -16,35 +16,66 @@ module Docs · 3 unmodified lines
16 options[:skip] = %w(api_changes.html tutorial.html faq.html)16 options[:skip] = %w(api_changes.html tutorial.html faq.html)
17 17
18 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
19 &copy; 2012&ndash;2017 Matplotlib Development Team. All rights reserved.<br>19 &copy; 2012&ndash;2018 Matplotlib Development Team. All rights reserved.<br>
20 Licensed under the Matplotlib License Agreement.20 Licensed under the Matplotlib License Agreement.
21 HTML21 HTML
22 22
23 version '3.1' do
24 self.release = '3.1.1'
25 self.base_urls = [
26 "https://matplotlib.org/#{release}/api/",
27 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
28 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
29 ]
30 end
31
32 version '3.0' do
33 self.release = '3.0.0'
34 self.base_urls = [
35 "https://matplotlib.org/#{release}/api/",
36 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
37 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
38 ]
39 end
40
41 version '2.2' do
42 self.release = '2.2.3'
43 self.base_urls = [
44 "https://matplotlib.org/#{release}/api/",
45 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
46 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
47 ]
48 end
49
23 version '2.1' do50 version '2.1' do
24 self.release = '2.1.0'51 self.release = '2.1.0'
25 self.base_urls = [52 self.base_urls = [
26 "http://matplotlib.org/#{release}/api/",53 "https://matplotlib.org/#{release}/api/",
27 "http://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",54 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
28 "http://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"55 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
29 ]56 ]
30 end57 end
31 58
32 version '2.0' do59 version '2.0' do
33 self.release = '2.0.2'60 self.release = '2.0.2'
34 self.base_urls = [61 self.base_urls = [
35 "http://matplotlib.org/#{release}/api/",62 "https://matplotlib.org/#{release}/api/",
36 "http://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",63 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
37 "http://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"64 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
38 ]65 ]
39 end66 end
40 67
41 version '1.5' do68 version '1.5' do
42 self.release = '1.5.3'69 self.release = '1.5.3'
43 self.base_urls = [70 self.base_urls = [
44 "http://matplotlib.org/#{release}/api/",71 "https://matplotlib.org/#{release}/api/",
45 "http://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",72 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
46 "http://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"73 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
47 ]74 ]
48 end75 end
76
77 def get_latest_version(opts)
78 get_latest_github_release('matplotlib', 'matplotlib', opts)
79 end
49 end80 end
50end81end
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+6−2
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Mocha < UrlScraper2 class Mocha < UrlScraper
3 self.type = 'mocha'3 self.type = 'simple'
4 self.release = '5.0.1'4 self.release = '5.2.0'
5 self.base_url = 'https://mochajs.org/'5 self.base_url = 'https://mochajs.org/'
6 self.links = {6 self.links = {
7 home: 'https://mochajs.org/',7 home: 'https://mochajs.org/',
@@ -18,5 +18,9 @@ module Docs · 10 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+7−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Mongoose < UrlScraper2 class Mongoose < UrlScraper
3 self.name = 'Mongoose'3 self.name = 'Mongoose'
4 self.type = 'mongoose'4 self.type = 'simple'
5 self.release = '4.13.1'5 self.release = '4.13.1'
6 self.base_url = 'http://mongoosejs.com/docs/'6 self.base_url = 'http://mongoosejs.com/docs/'
7 self.root_path = 'index.html'7 self.root_path = 'index.html'
@@ -26,5 +26,11 @@ module Docs · 18 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+10−4
@@ -2,11 +2,11 @@ module Docs · 1 unmodified line
2 class Nginx < UrlScraper2 class Nginx < UrlScraper
3 self.name = 'nginx'3 self.name = 'nginx'
4 self.type = 'nginx'4 self.type = 'nginx'
5 self.release = '1.15.0'5 self.release = '1.17.2'
6 self.base_url = 'https://nginx.org/en/docs/'6 self.base_url = 'https://nginx.org/en/docs/'
7 self.links = {7 self.links = {
8 home: 'https://nginx.org/',8 home: 'https://nginx.org/',
9 code: 'http://hg.nginx.org/nginx'9 code: 'https://hg.nginx.org/nginx'
10 }10 }
11 11
12 html_filters.push 'nginx/clean_html', 'nginx/entries'12 html_filters.push 'nginx/clean_html', 'nginx/entries'
@@ -21,9 +21,15 @@ module Docs · 8 unmodified lines
21 options[:skip_patterns] = [/\/faq\//]21 options[:skip_patterns] = [/\/faq\//]
22 22
23 options[:attribution] = <<-HTML23 options[:attribution] = <<-HTML
24 &copy; 2002-2018 Igor Sysoev<br>24 &copy; 2002-2019 Igor Sysoev<br>
25 &copy; 2011-2018 Nginx, Inc.<br>25 &copy; 2011-2019 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+8−3
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Nim < UrlScraper2 class Nim < UrlScraper
3 self.type = 'nim'3 self.type = 'simple'
4 self.release = '0.18.0'4 self.release = '0.19.0'
5 self.base_url = 'https://nim-lang.org/docs/'5 self.base_url = 'https://nim-lang.org/docs/'
6 self.root_path = 'overview.html'6 self.root_path = 'overview.html'
7 self.links = {7 self.links = {
@@ -14,8 +14,13 @@ module Docs · 6 unmodified lines
14 options[:skip] = %w(theindex.html docgen.txt)14 options[:skip] = %w(theindex.html docgen.txt)
15 15
16 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
17 &copy; 2006&ndash;2017 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+13−3
@@ -23,17 +23,22 @@ module Docs · 22 unmodified lines
23 HTML23 HTML
24 24
25 version do25 version do
26 self.release = '10.6.0'26 self.release = '12.8.0'
27 self.base_url = 'https://nodejs.org/dist/latest-v12.x/docs/api/'
28 end
29
30 version '10 LTS' do
31 self.release = '10.13.0'
27 self.base_url = 'https://nodejs.org/dist/latest-v10.x/docs/api/'32 self.base_url = 'https://nodejs.org/dist/latest-v10.x/docs/api/'
28 end33 end
29 34
30 version '8 LTS' do35 version '8 LTS' do
31 self.release = '8.11.2'36 self.release = '8.12.0'
32 self.base_url = 'https://nodejs.org/dist/latest-v8.x/docs/api/'37 self.base_url = 'https://nodejs.org/dist/latest-v8.x/docs/api/'
33 end38 end
34 39
35 version '6 LTS' do40 version '6 LTS' do
36 self.release = '6.14.2'41 self.release = '6.14.4'
37 self.base_url = 'https://nodejs.org/dist/latest-v6.x/docs/api/'42 self.base_url = 'https://nodejs.org/dist/latest-v6.x/docs/api/'
38 end43 end
39 44
@@ -41,5 +46,10 @@ module Docs · 1 unmodified line
41 self.release = '4.9.1'46 self.release = '4.9.1'
42 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/'
43 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
44 end54 end
45end55end
lib/docs/scrapers/nokogiri2.rb+12−4
@@ -1,9 +1,13 @@
1module Docs1module Docs
2 class Nokogiri2 < Rdoc2 class Nokogiri2 < Rdoc
3 # Instructions:
4 # 1. Download the latest release at https://github.com/sparklemotion/nokogiri/releases
5 # 2. Run "bundle install && bundle exec rake docs" (in the Nokogiri directory)
6 # 4. Copy the "doc" directory to "docs/nokgiri"
7
3 self.name = 'Nokogiri'8 self.name = 'Nokogiri'
4 self.slug = 'nokogiri'9 self.slug = 'nokogiri'
5 self.release = '1.8.1'10 self.release = '1.9.0'
6 self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Nokogiri'
7 11
8 html_filters.replace 'rdoc/entries', 'nokogiri2/entries'12 html_filters.replace 'rdoc/entries', 'nokogiri2/entries'
9 13
@@ -11,9 +15,13 @@ module Docs · 1 unmodified line
11 options[:only_patterns] = [/\ANokogiri/, /\AXSD/]15 options[:only_patterns] = [/\ANokogiri/, /\AXSD/]
12 16
13 options[:attribution] = <<-HTML17 options[:attribution] = <<-HTML
14 &copy; 2008&ndash;2017 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo<br>18 &copy; 2008&ndash;2018 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo,<br>
15 Patrick Mahoney, Yoko Harada, Akinori Musha, John Shahid<br>19 Patrick Mahoney, Yoko Harada, Akinori Musha, John Shahid, Lars Kanis<br>
16 Licensed under the MIT License.20 Licensed under the MIT License.
17 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_latest_github_release('sparklemotion', 'nokogiri', opts)
25 end
18 end26 end
19end27end
lib/docs/scrapers/npm.rb+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Npm < UrlScraper2 class Npm < UrlScraper
3 self.name = 'npm'3 self.name = 'npm'
4 self.type = 'npm'4 self.type = 'npm'
5 self.release = '6.1.0'5 self.release = '6.4.0'
6 self.base_url = 'https://docs.npmjs.com/'6 self.base_url = 'https://docs.npmjs.com/'
7 self.force_gzip = true7 self.force_gzip = true
8 self.links = {8 self.links = {
@@ -29,5 +29,9 @@ module Docs · 20 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−1
@@ -2,7 +2,6 @@ module Docs · 1 unmodified line
2 class Numpy < FileScraper2 class Numpy < FileScraper
3 self.name = 'NumPy'3 self.name = 'NumPy'
4 self.type = 'sphinx'4 self.type = 'sphinx'
5 self.dir = '/Users/Thibaut/DevDocs/Docs/numpy/reference/'
6 self.root_path = 'index.html'5 self.root_path = 'index.html'
7 self.links = {6 self.links = {
8 home: 'http://www.numpy.org/',7 home: 'http://www.numpy.org/',
@@ -50,5 +49,9 @@ module Docs · 41 unmodified lines
50 self.release = '1.10.4'49 self.release = '1.10.4'
51 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/"
52 end51 end
52
53 def get_latest_version(opts)
54 get_latest_github_release('numpy', 'numpy', opts)
55 end
53 end56 end
54end57end
lib/docs/scrapers/openjdk.rb+22−3
@@ -1,11 +1,10 @@
1module Docs1module Docs
2 class Openjdk < FileScraper2 class Openjdk < FileScraper
3 # Downloaded from packages.debian.org/sid/openjdk-8-doc
4 # Extracting subdirectory /usr/share/doc/openjdk-8-jre-headless/api
3 self.name = 'OpenJDK'5 self.name = 'OpenJDK'
4 self.type = 'openjdk'6 self.type = 'openjdk'
5 self.root_path = 'overview-summary.html'7 self.root_path = 'overview-summary.html'
6 # Downloaded from packages.debian.org/sid/openjdk-8-doc
7 # Extracting subdirectory /usr/share/doc/openjdk-8-jre-headless/api
8 self.dir = '/Users/Thibaut/DevDocs/Docs/OpenJDK'
9 8
10 html_filters.insert_after 'internal_urls', 'openjdk/clean_urls'9 html_filters.insert_after 'internal_urls', 'openjdk/clean_urls'
11 html_filters.push 'openjdk/entries', 'openjdk/clean_html'10 html_filters.push 'openjdk/entries', 'openjdk/clean_html'
@@ -87,5 +86,25 @@ module Docs · 75 unmodified lines
87 def read_file(path)86 def read_file(path)
88 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
89 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
90 end109 end
91end110end
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+7−3
@@ -2,8 +2,8 @@ module Docs · 1 unmodified line
2 class Padrino < UrlScraper2 class Padrino < UrlScraper
3 self.slug = 'padrino'3 self.slug = 'padrino'
4 self.type = 'rubydoc'4 self.type = 'rubydoc'
5 self.release = '0.14.1'5 self.release = '0.14.4'
6 self.base_url = 'http://www.rubydoc.info/github/padrino/padrino-framework/'6 self.base_url = 'https://www.rubydoc.info/github/padrino/padrino-framework/'
7 self.root_path = 'file/README.rdoc'7 self.root_path = 'file/README.rdoc'
8 self.initial_paths = %w(index2)8 self.initial_paths = %w(index2)
9 self.links = {9 self.links = {
@@ -16,12 +16,16 @@ module Docs · 6 unmodified lines
16 options[:container] = ->(filter) { filter.root_page? ? '#filecontents' : '#content' }16 options[:container] = ->(filter) { filter.root_page? ? '#filecontents' : '#content' }
17 17
18 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
19 &copy; 2010&ndash;2016 Padrino<br>19 &copy; 2010&ndash;2019 Padrino<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22 22
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+28−6
@@ -14,35 +14,57 @@ module Docs · 13 unmodified lines
14 options[:container] = '.document'14 options[:container] = '.document'
15 15
16 options[:skip] = %w(internals.html release.html contributing.html whatsnew.html)16 options[:skip] = %w(internals.html release.html contributing.html whatsnew.html)
17 options[:skip_patterns] = [/whatsnew\//]
17 18
18 options[:attribution] = <<-HTML19 options[:attribution] = <<-HTML
19 &copy; 2008&ndash;2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team<br>20 &copy; 2008&ndash;2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team<br>
20 Licensed under the 3-clause BSD License.21 Licensed under the 3-clause BSD License.
21 HTML22 HTML
22 23
24 version '0.25' do
25 self.release = '0.25.0'
26 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
27 end
28
29 version '0.24' do
30 self.release = '0.24.2'
31 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
32 end
33
34 version '0.23' do
35 self.release = '0.23.4'
36 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
37 end
38
23 version '0.22' do39 version '0.22' do
24 self.release = '0.22.0'40 self.release = '0.22.0'
25 self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"41 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
26 end42 end
27 43
28 version '0.21' do44 version '0.21' do
29 self.release = '0.21.0'45 self.release = '0.21.1'
30 self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"46 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
31 end47 end
32 48
33 version '0.20' do49 version '0.20' do
34 self.release = '0.20.3'50 self.release = '0.20.3'
35 self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"51 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
36 end52 end
37 53
38 version '0.19' do54 version '0.19' do
39 self.release = '0.19.2'55 self.release = '0.19.2'
40 self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"56 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
41 end57 end
42 58
43 version '0.18' do59 version '0.18' do
44 self.release = '0.18.1'60 self.release = '0.18.1'
45 self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"61 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
62 end
63
64 def get_latest_version(opts)
65 doc = fetch_doc('http://pandas.pydata.org/pandas-docs/stable/', opts)
66 label = doc.at_css('.body > .section > p').content
67 label.scan(/Version: ([0-9.]+)/)[0][0]
46 end68 end
47 end69 end
48end70end
lib/docs/scrapers/perl.rb+6−1
@@ -2,7 +2,6 @@ module Docs · 1 unmodified line
2 class Perl < FileScraper2 class Perl < FileScraper
3 self.name = 'Perl'3 self.name = 'Perl'
4 self.type = 'perl'4 self.type = 'perl'
5 self.dir = '/Users/Thibaut/DevDocs/Docs/Perl'
6 self.root_path = 'index.html'5 self.root_path = 'index.html'
7 self.links = {6 self.links = {
8 home: 'https://www.perl.org/'7 home: 'https://www.perl.org/'
@@ -44,5 +43,11 @@ module Docs · 35 unmodified lines
44 self.release = '5.20.2'43 self.release = '5.20.2'
45 self.base_url = "https://perldoc.perl.org/#{self.release}/"44 self.base_url = "https://perldoc.perl.org/#{self.release}/"
46 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
47 end52 end
48end53end
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+6−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Phoenix < UrlScraper2 class Phoenix < UrlScraper
3 self.type = 'elixir'3 self.type = 'elixir'
4 self.release = '1.3.2'4 self.release = '1.3.4'
5 self.base_url = 'https://hexdocs.pm/'5 self.base_url = 'https://hexdocs.pm/'
6 self.root_path = 'phoenix/Phoenix.html'6 self.root_path = 'phoenix/Phoenix.html'
7 self.initial_paths = %w(7 self.initial_paths = %w(
@@ -46,5 +46,10 @@ module Docs · 38 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+9−4
@@ -1,10 +1,12 @@
1module Docs1module Docs
2 class Php < FileScraper2 class Php < FileScraper
3 # Downloaded from php.net/download-docs.php
4
3 include FixInternalUrlsBehavior5 include FixInternalUrlsBehavior
4 6
5 self.name = 'PHP'7 self.name = 'PHP'
6 self.type = 'php'8 self.type = 'php'
7 self.release = '7.2.5'9 self.release = '7.2.9'
8 self.base_url = 'https://secure.php.net/manual/en/'10 self.base_url = 'https://secure.php.net/manual/en/'
9 self.root_path = 'index.html'11 self.root_path = 'index.html'
10 self.initial_paths = %w(12 self.initial_paths = %w(
@@ -23,9 +25,6 @@ module Docs · 12 unmodified lines
23 code: 'https://git.php.net/?p=php-src.git;a=summary'25 code: 'https://git.php.net/?p=php-src.git;a=summary'
24 }26 }
25 27
26 # Downloaded from php.net/download-docs.php
27 self.dir = '/Users/Thibaut/DevDocs/Docs/PHP'
28
29 html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title'28 html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title'
30 text_filters.push 'php/fix_urls'29 text_filters.push 'php/fix_urls'
31 30
@@ -67,5 +66,11 @@ module Docs · 35 unmodified lines
67 &copy; 1997&ndash;2018 The PHP Documentation Group<br>66 &copy; 1997&ndash;2018 The PHP Documentation Group<br>
68 Licensed under the Creative Commons Attribution License v3.0 or later.67 Licensed under the Creative Commons Attribution License v3.0 or later.
69 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
70 end75 end
71end76end
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+23−0
@@ -0,0 +1,23 @@
1module Docs
2 class Puppeteer < Github
3 self.release = '1.10.0'
4 self.base_url = 'https://github.com/GoogleChrome/puppeteer/blob/v1.8.0/docs/api.md'
5 self.links = {
6 code: 'https://github.com/GoogleChrome/puppeteer'
7 }
8
9 html_filters.push 'puppeteer/entries', 'puppeteer/clean_html'
10
11 options[:container] = '.markdown-body'
12
13 options[:attribution] = <<-HTML
14 &copy; 2017 Google Inc<br>
15 Licensed under the Apache License 2.0.
16 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
22 end
23end
lib/docs/scrapers/pygame.rb+25−0
@@ -0,0 +1,25 @@
1module Docs
2 class Pygame < UrlScraper
3 self.type = 'simple'
4 self.release = '1.9.6'
5 self.base_url = 'https://www.pygame.org/docs/'
6 self.root_path = 'py-modindex.html'
7 self.links = {
8 home: 'https://www.pygame.org/',
9 code: 'https://github.com/pygame/pygame'
10 }
11
12 html_filters.push 'pygame/clean_html', 'pygame/entries'
13
14 options[:only_patterns] = [/ref\//]
15
16 options[:attribution] = <<-HTML
17 &copy; Pygame Developers.<br>
18 Licensed under the GNU LGPL License version 2.1.
19 HTML
20
21 def get_latest_version(opts)
22 get_latest_github_release('pygame', 'pygame', opts)
23 end
24 end
25end
lib/docs/scrapers/python.rb+13−12
@@ -23,36 +23,37 @@ module Docs · 22 unmodified lines
23 Licensed under the PSF License.23 Licensed under the PSF License.
24 HTML24 HTML
25 25
26 version '3.7' do26 version '3.7' do # docs.python.org/3.7/download.html
27 self.release = '3.7.0'27 self.release = '3.7.4'
28 self.dir = '/Users/Thibaut/DevDocs/Docs/Python37' # docs.python.org/3.7/download.html
29 self.base_url = 'https://docs.python.org/3.7/'28 self.base_url = 'https://docs.python.org/3.7/'
30 29
31 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'30 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'
32 end31 end
33 32
34 version '3.6' do33 version '3.6' do # docs.python.org/3.6/download.html
35 self.release = '3.6.6'34 self.release = '3.6.9'
36 self.dir = '/Users/Thibaut/DevDocs/Docs/Python36' # docs.python.org/3.6/download.html
37 self.base_url = 'https://docs.python.org/3.6/'35 self.base_url = 'https://docs.python.org/3.6/'
38 36
39 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'37 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'
40 end38 end
41 39
42 version '3.5' do40 version '3.5' do # docs.python.org/3.5/download.html
43 self.release = '3.5.3'41 self.release = '3.5.7'
44 self.dir = '/Users/Thibaut/DevDocs/Docs/Python35' # docs.python.org/3.5/download.html
45 self.base_url = 'https://docs.python.org/3.5/'42 self.base_url = 'https://docs.python.org/3.5/'
46 43
47 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'44 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'
48 end45 end
49 46
50 version '2.7' do47 version '2.7' do # docs.python.org/2.7/download.html
51 self.release = '2.7.13'48 self.release = '2.7.16'
52 self.dir = '/Users/Thibaut/DevDocs/Docs/Python27' # docs.python.org/2.7/download.html
53 self.base_url = 'https://docs.python.org/2.7/'49 self.base_url = 'https://docs.python.org/2.7/'
54 50
55 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'
56 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
57 end58 end
58end59end
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+136−0
@@ -0,0 +1,136 @@
1module Docs
2 class Qt < UrlScraper
3 self.name = 'Qt'
4 self.type = 'qt'
5 self.initial_paths = %w(classes.html qmltypes.html)
6 self.root_path = 'index.html'
7 self.links = {
8 home: 'https://www.qt.io',
9 code: 'https://code.qt.io/cgit/'
10 }
11
12 html_filters.push 'qt/entries', 'qt/clean_html'
13
14 options[:container] = '.main'
15 options[:max_image_size] = 156_000
16 options[:skip_patterns] = [
17 # License, copyright attributions
18 /3rdparty/,
19 /attribution/,
20 /license/,
21 /licensing/,
22
23 # Examples, guides, tutorials
24 /example/,
25 /guide$/,
26 /tutorial/,
27 /porting/,
28 /usecase/,
29 /topic/,
30 /^modelview/,
31 /deploy(ing|ment)/,
32 /building/,
33
34 # Old versions, changelog
35 /obsolete/,
36 /compatibility/,
37 /^whatsnew/,
38 /^newclasses/,
39
40 # Deprecated modules
41 /(qtopengl|qgl)/,
42 /qt?script/,
43
44 # Indexes
45 /members/,
46 /module/,
47 /overview/,
48 /^qopenglfunctions/,
49
50 # Tooling
51 /^(qt)?(linguist|assistant|qdbusviewer)/,
52 ]
53
54 options[:skip] = [
55 "qt5-intro.html",
56 "compatmap.html",
57
58 # Indexes
59 "classes.html",
60 "qtmodules.html",
61 "modules-qml.html",
62 "modules-cpp.html",
63 "functions.html",
64 "namespaces.html",
65 "qmltypes.html",
66 "qt3d-qml.html",
67 "qmlbasictypes.html",
68 "guibooks.html",
69 "annotated.html",
70 "overviews-main.html",
71 "reference-overview.html",
72
73 # Tutorials
74 "qtvirtualkeyboard-build.html",
75
76 # Copyright
77 "trademarks.html",
78 "lgpl.html",
79 "bughowto.html",
80
81 # Changelogs
82 "changes.html",
83 "qtlocation-changes.html",
84 "sourcebreaks.html",
85
86 # Best practice guides
87 "accessible.html",
88 "accessible-qtquick.html",
89 "sharedlibrary.html",
90 "exceptionsafety.html",
91 "scalability.html",
92 "session.html",
93 "appicon.html",
94 "accelerators.html",
95
96 # Other
97 "ecmascript.html",
98 "qtremoteobjects-interaction.html",
99 ]
100
101 options[:attribution] = <<-HTML
102 &copy; The Qt Company Ltd<br>
103 Licensed under the GNU Free Documentation License, Version 1.3.
104 HTML
105
106 version '5.13' do
107 self.release = '5.13'
108 self.base_url = 'https://doc.qt.io/qt-5.13/'
109 end
110
111 version '5.12' do
112 self.release = '5.12'
113 self.base_url = 'https://doc.qt.io/qt-5.12/'
114 end
115
116 version '5.11' do
117 self.release = '5.11'
118 self.base_url = 'https://doc.qt.io/archives/qt-5.11/'
119 end
120
121 version '5.9' do
122 self.release = '5.9'
123 self.base_url = 'https://doc.qt.io/qt-5.9/'
124 end
125
126 version '5.6' do
127 self.release = '5.6'
128 self.base_url = 'https://doc.qt.io/archives/qt-5.6/'
129 end
130
131 def get_latest_version(opts)
132 doc = fetch_doc('https://doc.qt.io/qt-5/index.html', opts)
133 doc.at_css('.mainContent h1.title').content.sub(/Qt /, '')
134 end
135 end
136end
lib/docs/scrapers/ramda.rb+7−3
@@ -1,8 +1,8 @@
1module Docs1module Docs
2 class Ramda < UrlScraper2 class Ramda < UrlScraper
3 self.type = 'ramda'3 self.type = 'ramda'
4 self.release = '0.25.0'4 self.release = '0.26.1'
5 self.base_url = "http://ramdajs.com/#{release}/docs/"5 self.base_url = "https://ramdajs.com/#{release}/docs/"
6 self.links = {6 self.links = {
7 home: 'http://ramdajs.com/',7 home: 'http://ramdajs.com/',
8 code: 'https://github.com/ramda/ramda/'8 code: 'https://github.com/ramda/ramda/'
@@ -15,6 +15,10 @@ module Docs · 6 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
lib/docs/scrapers/rdoc/minitest.rb+12−2
@@ -1,9 +1,14 @@
1module Docs1module Docs
2 class Minitest < Rdoc2 class Minitest < Rdoc
3 # Instructions:
4 # 1. Run "gem update rdoc hoe"
5 # 2. Download the source code at https://github.com/seattlerb/minitest
6 # 3. Run "rake docs" (in the Minitest directory)
7 # 4. Copy the "docs" directory to "docs/minitest"
8
3 self.name = 'Ruby / Minitest'9 self.name = 'Ruby / Minitest'
4 self.slug = 'minitest'10 self.slug = 'minitest'
5 self.release = '5.10.3'11 self.release = '5.11.3'
6 self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Minitest' # rake docs
7 self.links = {12 self.links = {
8 code: 'https://github.com/seattlerb/minitest'13 code: 'https://github.com/seattlerb/minitest'
9 }14 }
@@ -16,5 +21,10 @@ module Docs · 6 unmodified lines
16 &copy; Ryan Davis, seattle.rb<br>21 &copy; Ryan Davis, seattle.rb<br>
17 Licensed under the MIT License.22 Licensed under the MIT License.
18 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
19 end29 end
20end30end
lib/docs/scrapers/rdoc/rails.rb+14−3
@@ -1,10 +1,17 @@
1module Docs1module Docs
2 class Rails < Rdoc2 class Rails < Rdoc
3 # Instructions:
4 # 1. Download a release at https://github.com/rails/rails/releases
5 # 2. Open "railties/lib/rails/api/task.rb" and comment out any code related to sdoc ("configure_sdoc")
6 # 3. Run "bundle install --without db && bundle exec rake rdoc" (in the Rails directory)
7 # 4. Run "cd guides && bundle exec rake guides:generate:html"
8 # 5. Copy the "guides/output" directory to "html/guides"
9 # 6. Copy the "html" directory to "docs/rails~[version]"
10
3 include FixInternalUrlsBehavior11 include FixInternalUrlsBehavior
4 12
5 self.name = 'Ruby on Rails'13 self.name = 'Ruby on Rails'
6 self.slug = 'rails'14 self.slug = 'rails'
7 self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Rails'
8 self.initial_paths = %w(guides/index.html)15 self.initial_paths = %w(guides/index.html)
9 self.links = {16 self.links = {
10 home: 'http://rubyonrails.org/',17 home: 'http://rubyonrails.org/',
@@ -68,7 +75,7 @@ module Docs · 57 unmodified lines
68 end75 end
69 76
70 version '5.2' do77 version '5.2' do
71 self.release = '5.2.0'78 self.release = '5.2.2'
72 end79 end
73 80
74 version '5.1' do81 version '5.1' do
@@ -80,11 +87,15 @@ module Docs · 5 unmodified lines
80 end87 end
81 88
82 version '4.2' do89 version '4.2' do
83 self.release = '4.2.10'90 self.release = '4.2.11'
84 end91 end
85 92
86 version '4.1' do93 version '4.1' do
87 self.release = '4.1.16'94 self.release = '4.1.16'
88 end95 end
96
97 def get_latest_version(opts)
98 get_latest_github_release('rails', 'rails', opts)
99 end
89 end100 end
90end101end
lib/docs/scrapers/rdoc/ruby.rb+19−19
@@ -1,16 +1,9 @@
1module Docs1module Docs
2 class Ruby < Rdoc2 class Ruby < Rdoc
3 # Generated with:3 # Instructions:
4 # ./configure && make html4 # 1. Download Ruby's source code
5 # or:5 # 2. Run "./configure && make html" (in the Ruby directory)
6 # rdoc \6 # 3. Copy the ".ext/html" directory to "docs/ruby~[version]"
7 # --root . \
8 # --page-dir doc \
9 # --encoding=UTF-8 \
10 # --visibility=public \
11 # --format=darkfish \
12 # --no-line-numbers \
13 # --op html .
14 7
15 include FixInternalUrlsBehavior8 include FixInternalUrlsBehavior
16 9
@@ -77,23 +70,30 @@ module Docs · 60 unmodified lines
77 HTML70 HTML
78 71
79 version '2.5' do72 version '2.5' do
80 self.release = '2.5.0'73 self.release = '2.5.3'
81 self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Ruby25'
82 end74 end
83 75
84 version '2.4' do76 version '2.4' do
85 self.release = '2.4.3'77 self.release = '2.4.5'
86 self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Ruby24'
87 end78 end
88 79
89 version '2.3' do80 version '2.3' do
90 self.release = '2.3.6'81 self.release = '2.3.8'
91 self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Ruby23'
92 end82 end
93 83
94 version '2.2' do84 version '2.2' do
95 self.release = '2.2.9'85 self.release = '2.2.10'
96 self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Ruby22'86 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 end97 end
98 end98 end
99end99end
lib/docs/scrapers/react.rb+6−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class React < UrlScraper2 class React < UrlScraper
3 self.name = 'React'3 self.name = 'React'
4 self.type = 'simple'4 self.type = 'simple'
5 self.release = '16.4.0'5 self.release = '16.6.0'
6 self.base_url = 'https://reactjs.org/docs/'6 self.base_url = 'https://reactjs.org/docs/'
7 self.root_path = 'hello-world.html'7 self.root_path = 'hello-world.html'
8 self.links = {8 self.links = {
@@ -30,5 +30,10 @@ module Docs · 21 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+7−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Redis < UrlScraper2 class Redis < UrlScraper
3 self.type = 'redis'3 self.type = 'redis'
4 self.release = '4.0.9'4 self.release = '5.0.0'
5 self.base_url = 'https://redis.io/commands'5 self.base_url = 'https://redis.io/commands'
6 self.links = {6 self.links = {
7 home: 'https://redis.io/',7 home: 'https://redis.io/',
@@ -19,5 +19,11 @@ module Docs · 11 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+5−1
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Redux < UrlScraper2 class Redux < UrlScraper
3 self.type = 'redux'3 self.type = 'simple'
4 self.release = '3.7.2'4 self.release = '3.7.2'
5 self.base_url = 'http://redux.js.org/docs/'5 self.base_url = 'http://redux.js.org/docs/'
6 self.links = {6 self.links = {
@@ -20,5 +20,9 @@ module Docs · 13 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+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Requirejs < UrlScraper2 class Requirejs < UrlScraper
3 self.name = 'RequireJS'3 self.name = 'RequireJS'
4 self.type = 'requirejs'4 self.type = 'simple'
5 self.release = '2.3.5'5 self.release = '2.3.5'
6 self.base_url = 'http://requirejs.org/docs/'6 self.base_url = 'http://requirejs.org/docs/'
7 self.links = {7 self.links = {
@@ -30,5 +30,9 @@ module Docs · 22 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+14−5
@@ -1,9 +1,11 @@
1# frozen_string_literal: true
2
1module Docs3module Docs
2 class Rust < UrlScraper4 class Rust < UrlScraper
3 self.type = 'rust'5 self.type = 'rust'
4 self.release = '1.26.0'6 self.release = '1.30.1'
5 self.base_url = 'https://doc.rust-lang.org/'7 self.base_url = 'https://doc.rust-lang.org/'
6 self.root_path = 'book/first-edition/index.html'8 self.root_path = 'book/second-edition/index.html'
7 self.initial_paths = %w(9 self.initial_paths = %w(
8 reference/introduction.html10 reference/introduction.html
9 collections/index.html11 collections/index.html
@@ -17,17 +19,18 @@ module Docs · 7 unmodified lines
17 html_filters.push 'rust/entries', 'rust/clean_html'19 html_filters.push 'rust/entries', 'rust/clean_html'
18 20
19 options[:only_patterns] = [21 options[:only_patterns] = [
20 /\Abook\/first-edition\//,22 /\Abook\/second-edition\//,
21 /\Areference\//,23 /\Areference\//,
22 /\Acollections\//,24 /\Acollections\//,
23 /\Astd\// ]25 /\Astd\// ]
24 26
25 options[:skip] = %w(book/first-edition/README.html)27 options[:skip] = %w(book/second-edition/README.html)
26 options[:skip_patterns] = [/(?<!\.html)\z/]28 options[:skip_patterns] = [/(?<!\.html)\z/, /\/print\.html/]
27 29
28 options[:fix_urls] = ->(url) do30 options[:fix_urls] = ->(url) do
29 url.sub! %r{(#{Rust.base_url}.+/)\z}, '\1index.html'31 url.sub! %r{(#{Rust.base_url}.+/)\z}, '\1index.html'
30 url.sub! '/unicode/u_str', '/unicode/str/'32 url.sub! '/unicode/u_str', '/unicode/str/'
33 url.sub! '/std/std/', '/std/'
31 url34 url
32 end35 end
33 36
@@ -36,6 +39,12 @@ module Docs · 2 unmodified lines
36 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.
37 HTML40 HTML
38 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
39 private48 private
40 49
41 REDIRECT_RGX = /http-equiv="refresh"/i50 REDIRECT_RGX = /http-equiv="refresh"/i
lib/docs/scrapers/salt_stack.rb+36−0
@@ -0,0 +1,36 @@
1module Docs
2 # The official documentation website is heavily rate-limited
3 #
4 # The documentation can be generated like this (replace 2019.2 with the correct tag):
5 # $ git clone https://github.com/saltstack/salt.git --branch 2019.2 --depth 1
6 # $ cd salt/doc
7 # $ pip install sphinx
8 # $ make html
9 #
10 # The generated html can be found in salt/doc/_build/html
11 class SaltStack < FileScraper
12 self.type = 'simple'
13 self.slug = 'saltstack'
14 self.release = '2019.2.0'
15 self.base_url = 'https://docs.saltstack.com/en/latest/'
16 self.root_path = 'ref/index.html'
17 self.links = {
18 home: 'https://www.saltstack.com/',
19 code: 'https://github.com/saltstack/salt'
20 }
21
22 html_filters.push 'salt_stack/clean_html', 'salt_stack/entries'
23
24 options[:only_patterns] = [/all\//]
25 options[:container] = '.body-content > .section'
26
27 options[:attribution] = <<-HTML
28 &copy; 2019 SaltStack.<br>
29 Licensed under the Apache License, Version 2.0.
30 HTML
31
32 def get_latest_version(opts)
33 get_latest_github_release('saltstack', 'salt', opts)
34 end
35 end
36end
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/scala.rb+61−0
@@ -0,0 +1,61 @@
1module Docs
2 class Scala < FileScraper
3 self.name = 'Scala'
4 self.type = 'scala'
5 self.links = {
6 home: 'http://www.scala-lang.org/',
7 code: 'https://github.com/scala/scala'
8 }
9
10 options[:container] = '#content-container'
11 options[:attribution] = <<-HTML
12 &copy; 2002-2019 EPFL, with contributions from Lightbend.<br>
13 Licensed under the Apache License, Version 2.0.
14 HTML
15
16 # https://downloads.lightbend.com/scala/2.13.0/scala-docs-2.13.0.zip
17 # Extract api/scala-library into docs/scala~2.13_library
18 version '2.13 Library' do
19 self.release = '2.13.0'
20 self.base_url = 'https://www.scala-lang.org/api/2.13.0/'
21 self.root_path = 'index.html'
22
23 html_filters.push 'scala/entries', 'scala/clean_html'
24 end
25
26 # https://downloads.lightbend.com/scala/2.13.0/scala-docs-2.13.0.zip
27 # Extract api/scala-reflect into docs/scala~2.13_reflection
28 version '2.13 Reflection' do
29 self.release = '2.13.0'
30 self.base_url = 'https://www.scala-lang.org/api/2.13.0/scala-reflect/'
31 self.root_path = 'index.html'
32
33 html_filters.push 'scala/entries', 'scala/clean_html'
34 end
35
36 # https://downloads.lightbend.com/scala/2.12.9/scala-docs-2.12.9.zip
37 # Extract api/scala-library into docs/scala~2.12_library
38 version '2.12 Library' do
39 self.release = '2.12.9'
40 self.base_url = 'https://www.scala-lang.org/api/2.12.9/'
41 self.root_path = 'index.html'
42
43 html_filters.push 'scala/entries', 'scala/clean_html'
44 end
45
46 # https://downloads.lightbend.com/scala/2.12.9/scala-docs-2.12.9.zip
47 # Extract api/scala-reflect into docs/scala~2.12_reflection
48 version '2.12 Reflection' do
49 self.release = '2.12.9'
50 self.base_url = 'https://www.scala-lang.org/api/2.12.9/scala-reflect/'
51 self.root_path = 'index.html'
52
53 html_filters.push 'scala/entries', 'scala/clean_html'
54 end
55
56 def get_latest_version(opts)
57 doc = fetch_doc('https://www.scala-lang.org/api/current/', opts)
58 doc.at_css('#doc-version').content
59 end
60 end
61end
lib/docs/scrapers/scikit_image.rb+7−2
@@ -3,8 +3,8 @@ module Docs · 2 unmodified lines
3 self.name = 'scikit-image'3 self.name = 'scikit-image'
4 self.slug = 'scikit_image'4 self.slug = 'scikit_image'
5 self.type = 'sphinx'5 self.type = 'sphinx'
6 self.release = '0.13.1'6 self.release = '0.14.1'
7 self.base_url = 'http://scikit-image.org/docs/0.13.x/'7 self.base_url = 'http://scikit-image.org/docs/0.14.x/'
8 self.links = {8 self.links = {
9 home: 'http://scikit-image.org/',9 home: 'http://scikit-image.org/',
10 code: 'https://github.com/scikit-image/scikit-image'10 code: 'https://github.com/scikit-image/scikit-image'
@@ -20,5 +20,10 @@ module Docs · 9 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+6−2
@@ -3,7 +3,7 @@ module Docs · 2 unmodified lines
3 self.name = 'scikit-learn'3 self.name = 'scikit-learn'
4 self.slug = 'scikit_learn'4 self.slug = 'scikit_learn'
5 self.type = 'sphinx'5 self.type = 'sphinx'
6 self.release = '0.19.1'6 self.release = '0.20.0'
7 self.base_url = 'http://scikit-learn.org/stable/'7 self.base_url = 'http://scikit-learn.org/stable/'
8 self.root_path = 'documentation.html'8 self.root_path = 'documentation.html'
9 self.force_gzip = true9 self.force_gzip = true
@@ -21,9 +21,13 @@ module Docs · 11 unmodified lines
21 options[:max_image_size] = 256_00021 options[:max_image_size] = 256_000
22 22
23 options[:attribution] = <<-HTML23 options[:attribution] = <<-HTML
24 &copy; 2007&ndash;2017 The scikit-learn developers<br>24 &copy; 2007&ndash;2018 The scikit-learn developers<br>
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+18−8
@@ -4,7 +4,7 @@ module Docs · 3 unmodified lines
4 self.slug = 'sinon'4 self.slug = 'sinon'
5 self.type = 'sinon'5 self.type = 'sinon'
6 self.links = {6 self.links = {
7 home: 'http://sinonjs.org/',7 home: 'https://sinonjs.org/',
8 code: 'https://github.com/sinonjs/sinon'8 code: 'https://github.com/sinonjs/sinon'
9 }9 }
10 10
@@ -18,34 +18,44 @@ module Docs · 7 unmodified lines
18 Licensed under the BSD License.18 Licensed under the BSD License.
19 HTML19 HTML
20 20
21 version '7' do
22 self.release = '7.1.1'
23 self.base_url = "https://sinonjs.org/releases/v#{release}/"
24 end
25
21 version '6' do26 version '6' do
22 self.release = '6.1.3'27 self.release = '6.3.5'
23 self.base_url = "http://sinonjs.org/releases/v#{release}/"28 self.base_url = "https://sinonjs.org/releases/v#{release}/"
24 end29 end
25 30
26 version '5' do31 version '5' do
27 self.release = '5.1.0'32 self.release = '5.1.0'
28 self.base_url = "http://sinonjs.org/releases/v#{release}/"33 self.base_url = "https://sinonjs.org/releases/v#{release}/"
29 end34 end
30 35
31 version '4' do36 version '4' do
32 self.release = '4.5.0'37 self.release = '4.5.0'
33 self.base_url = "http://sinonjs.org/releases/v#{release}/"38 self.base_url = "https://sinonjs.org/releases/v#{release}/"
34 end39 end
35 40
36 version '3' do41 version '3' do
37 self.release = '3.3.0'42 self.release = '3.3.0'
38 self.base_url = "http://sinonjs.org/releases/v#{release}/"43 self.base_url = "https://sinonjs.org/releases/v#{release}/"
39 end44 end
40 45
41 version '2' do46 version '2' do
42 self.release = '2.4.1'47 self.release = '2.4.1'
43 self.base_url = "http://sinonjs.org/releases/v#{release}/"48 self.base_url = "https://sinonjs.org/releases/v#{release}/"
44 end49 end
45 50
46 version '1' do51 version '1' do
47 self.release = '1.17.7'52 self.release = '1.17.7'
48 self.base_url = "http://sinonjs.org/releases/v#{release}/"53 self.base_url = "https://sinonjs.org/releases/v#{release}/"
54 end
55
56 def get_latest_version(opts)
57 body = fetch('https://sinonjs.org/', opts)
58 body.scan(/\/releases\/v([0-9.]+)/)[0][0]
49 end59 end
50 end60 end
51end61end
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+6−2
@@ -2,8 +2,7 @@ module Docs · 1 unmodified line
2 class Sqlite < FileScraper2 class Sqlite < FileScraper
3 self.name = 'SQLite'3 self.name = 'SQLite'
4 self.type = 'sqlite'4 self.type = 'sqlite'
5 self.release = '3.23.1'5 self.release = '3.25.2'
6 self.dir = '/Users/Thibaut/DevDocs/Docs/sqlite/'
7 self.base_url = 'https://sqlite.org/'6 self.base_url = 'https://sqlite.org/'
8 self.root_path = 'docs.html'7 self.root_path = 'docs.html'
9 self.initial_paths = %w(keyword_index.html)8 self.initial_paths = %w(keyword_index.html)
@@ -42,6 +41,11 @@ module Docs · 32 unmodified lines
42 41
43 options[:attribution] = 'SQLite is in the Public Domain.'42 options[:attribution] = 'SQLite is in the Public Domain.'
44 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
45 private49 private
46 50
47 def parse(response)51 def parse(response)
lib/docs/scrapers/statsmodels.rb+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Statsmodels < UrlScraper2 class Statsmodels < UrlScraper
3 self.type = 'sphinx'3 self.type = 'sphinx'
4 self.release = '0.8.0'4 self.release = '0.9.0'
5 self.base_url = 'http://www.statsmodels.org/stable/'5 self.base_url = 'http://www.statsmodels.org/stable/'
6 self.root_path = 'index.html'6 self.root_path = 'index.html'
7 self.links = {7 self.links = {
@@ -21,5 +21,9 @@ module Docs · 13 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+17−8
@@ -26,44 +26,53 @@ module Docs · 25 unmodified lines
26 Licensed under the MIT License.26 Licensed under the MIT License.
27 HTML27 HTML
28 28
29 version '4.1' do
30 self.release = '4.1.7'
31 self.base_url = "https://api.symfony.com/#{version}/"
32 end
33
29 version '4.0' do34 version '4.0' do
30 self.release = '4.0.3'35 self.release = '4.0.3'
31 self.base_url = "http://api.symfony.com/#{version}/"36 self.base_url = "https://api.symfony.com/#{version}/"
32 end37 end
33 38
34 version '3.4' do39 version '3.4' do
35 self.release = '3.4.3'40 self.release = '3.4.3'
36 self.base_url = "http://api.symfony.com/#{version}/"41 self.base_url = "https://api.symfony.com/#{version}/"
37 end42 end
38 43
39 version '3.3' do44 version '3.3' do
40 self.release = '3.3.15'45 self.release = '3.3.15'
41 self.base_url = "http://api.symfony.com/#{version}/"46 self.base_url = "https://api.symfony.com/#{version}/"
42 end47 end
43 48
44 version '3.2' do49 version '3.2' do
45 self.release = '3.2.13'50 self.release = '3.2.13'
46 self.base_url = "http://api.symfony.com/#{version}/"51 self.base_url = "https://api.symfony.com/#{version}/"
47 end52 end
48 53
49 version '3.1' do54 version '3.1' do
50 self.release = '3.1.8'55 self.release = '3.1.8'
51 self.base_url = "http://api.symfony.com/#{version}/"56 self.base_url = "https://api.symfony.com/#{version}/"
52 end57 end
53 58
54 version '3.0' do59 version '3.0' do
55 self.release = '3.0.1'60 self.release = '3.0.1'
56 self.base_url = "http://api.symfony.com/#{version}/"61 self.base_url = "https://api.symfony.com/#{version}/"
57 end62 end
58 63
59 version '2.8' do64 version '2.8' do
60 self.release = '2.8.28'65 self.release = '2.8.28'
61 self.base_url = "http://api.symfony.com/#{version}/"66 self.base_url = "https://api.symfony.com/#{version}/"
62 end67 end
63 68
64 version '2.7' do69 version '2.7' do
65 self.release = '2.7.35'70 self.release = '2.7.35'
66 self.base_url = "http://api.symfony.com/#{version}/"71 self.base_url = "https://api.symfony.com/#{version}/"
72 end
73
74 def get_latest_version(opts)
75 get_latest_github_release('symfony', 'symfony', opts)
67 end76 end
68 end77 end
69end78end
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+11−6
@@ -4,14 +4,14 @@ module Docs · 3 unmodified lines
4 self.root_path = 'index.html'4 self.root_path = 'index.html'
5 self.initial_paths = %w(extensions/index.html)5 self.initial_paths = %w(extensions/index.html)
6 self.links = {6 self.links = {
7 home: 'https://twig.sensiolabs.org/',7 home: 'https://twig.symfony.com/',
8 code: 'https://github.com/twigphp/Twig'8 code: 'https://github.com/twigphp/Twig'
9 }9 }
10 10
11 options[:attribution] = <<-HTML11 options[:attribution] = <<-HTML
12 &copy; 2009&ndash;2017 by the Twig Team<br>12 &copy; 2009&ndash;2018 by the Twig Team<br>
13 Licensed under the three clause BSD license.<br>13 Licensed under the three clause BSD license.<br>
14 The Twig logo is &copy; 2010&ndash;2017 SensioLabs14 The Twig logo is &copy; 2010&ndash;2018 Symfony
15 HTML15 HTML
16 16
17 html_filters.push 'twig/clean_html', 'twig/entries'17 html_filters.push 'twig/clean_html', 'twig/entries'
@@ -20,13 +20,18 @@ module Docs · 2 unmodified lines
20 options[:skip] = %w(deprecated.html advanced_legacy.html)20 options[:skip] = %w(deprecated.html advanced_legacy.html)
21 21
22 version '2' do22 version '2' do
23 self.release = '2.4.3'23 self.release = '2.5.0'
24 self.base_url = 'https://twig.sensiolabs.org/doc/2.x/'24 self.base_url = 'https://twig.symfony.com/doc/2.x/'
25 end25 end
26 26
27 version '1' do27 version '1' do
28 self.release = '1.34.3'28 self.release = '1.34.3'
29 self.base_url = 'https://twig.sensiolabs.org/doc/1.x/'29 self.base_url = 'https://twig.symfony.com/doc/1.x/'
30 end
31
32 def get_latest_version(opts)
33 tags = get_github_tags('twigphp', 'Twig', opts)
34 tags[0]['name'][1..-1]
30 end35 end
31 end36 end
32end37end
lib/docs/scrapers/typescript.rb+6−2
@@ -1,8 +1,8 @@
1module Docs1module Docs
2 class Typescript < UrlScraper2 class Typescript < UrlScraper
3 self.name = 'TypeScript'3 self.name = 'TypeScript'
4 self.type = 'typescript'4 self.type = 'simple'
5 self.release = '2.9.0'5 self.release = '3.1.6'
6 self.base_url = 'https://www.typescriptlang.org/docs/'6 self.base_url = 'https://www.typescriptlang.org/docs/'
7 self.root_path = 'tutorial.html'7 self.root_path = 'tutorial.html'
8 self.links = {8 self.links = {
@@ -24,6 +24,10 @@ module Docs · 15 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+9−4
@@ -3,10 +3,10 @@ module Docs · 2 unmodified lines
3 self.name = 'Underscore.js'3 self.name = 'Underscore.js'
4 self.slug = 'underscore'4 self.slug = 'underscore'
5 self.type = 'underscore'5 self.type = 'underscore'
6 self.release = '1.9.0'6 self.release = '1.9.1'
7 self.base_url = 'http://underscorejs.org'7 self.base_url = 'https://underscorejs.org'
8 self.links = {8 self.links = {
9 home: 'http://underscorejs.org',9 home: 'https://underscorejs.org',
10 code: 'https://github.com/jashkenas/underscore'10 code: 'https://github.com/jashkenas/underscore'
11 }11 }
12 12
@@ -17,8 +17,13 @@ module Docs · 4 unmodified lines
17 options[:skip_links] = true17 options[:skip_links] = true
18 18
19 options[:attribution] = <<-HTML19 options[:attribution] = <<-HTML
20 &copy; 2009&ndash;2017 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+7−2
@@ -1,8 +1,8 @@
1module Docs1module Docs
2 class Vagrant < UrlScraper2 class Vagrant < UrlScraper
3 self.name = 'Vagrant'3 self.name = 'Vagrant'
4 self.type = 'vagrant'4 self.type = 'simple'
5 self.release = '2.1.1'5 self.release = '2.2.0'
6 self.base_url = 'https://www.vagrantup.com/docs/'6 self.base_url = 'https://www.vagrantup.com/docs/'
7 self.root_path = 'index.html'7 self.root_path = 'index.html'
8 self.links = {8 self.links = {
@@ -18,5 +18,10 @@ module Docs · 9 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/vue_router.rb+29−0
@@ -0,0 +1,29 @@
1module Docs
2 class VueRouter < UrlScraper
3 self.name = 'Vue Router'
4 self.slug = 'vue_router'
5 self.type = 'simple'
6 self.release = '3.1.2'
7 self.base_url = 'https://router.vuejs.org/'
8 self.links = {
9 home: 'https://router.vuejs.org',
10 code: 'https://github.com/vuejs/vue-router'
11 }
12
13 html_filters.push 'vue_router/entries', 'vue_router/clean_html'
14
15 options[:skip_patterns] = [
16 # Other languages
17 /^(zh|ja|ru|kr|fr)\//,
18 ]
19
20 options[:attribution] = <<-HTML
21 &copy; 2013&ndash;present Evan You<br>
22 Licensed under the MIT License.
23 HTML
24
25 def get_latest_version(opts)
26 get_latest_github_release('vuejs', 'vue-router', opts)
27 end
28 end
29end
lib/docs/scrapers/vulkan.rb+6−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Vulkan < UrlScraper2 class Vulkan < UrlScraper
3 self.name = 'Vulkan'3 self.name = 'Vulkan'
4 self.type = 'vulkan'4 self.type = 'simple'
5 self.release = '1.0.59'5 self.release = '1.0.59'
6 self.base_url = 'https://www.khronos.org/registry/vulkan/specs/1.0/'6 self.base_url = 'https://www.khronos.org/registry/vulkan/specs/1.0/'
7 self.root_path = 'apispec.html'7 self.root_path = 'apispec.html'
@@ -20,5 +20,10 @@ module Docs · 12 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+5−1
@@ -4,7 +4,7 @@ module Docs · 3 unmodified lines
4 self.type = 'webpack'4 self.type = 'webpack'
5 5
6 version do6 version do
7 self.release = '4.12.0'7 self.release = '4.16.5'
8 self.base_url = 'https://webpack.js.org/'8 self.base_url = 'https://webpack.js.org/'
9 self.root_path = 'guides/'9 self.root_path = 'guides/'
10 self.initial_paths = %w(10 self.initial_paths = %w(
@@ -68,5 +68,9 @@ module Docs · 57 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/wordpress.rb+44−0
@@ -0,0 +1,44 @@
1module Docs
2 class Wordpress < UrlScraper
3 self.name = 'WordPress'
4 self.type = 'wordpress'
5 self.release = '5.2.2'
6 self.base_url = 'https://developer.wordpress.org/reference/'
7 self.initial_paths = %w(
8 functions/
9 hooks/
10 classes/
11 )
12
13 self.links = {
14 home: 'https://wordpress.org/',
15 code: 'https://github.com/WordPress/WordPress'
16 }
17
18 html_filters.push 'wordpress/entries', 'wordpress/clean_html'
19
20 options[:container] = '#content-area'
21 options[:trailing_slash] = false
22 options[:only_patterns] = [
23 /\Afunctions\//,
24 /\Ahooks\//,
25 /\Aclasses\//
26 ]
27
28 options[:skip_patterns] = [
29 /\Afunctions\/page\/\d+/,
30 /\Ahooks\/page\/\d+/,
31 /\Aclasses\/page\/\d+/
32 ]
33
34 options[:attribution] = <<-HTML
35 &copy; 2003&ndash;2019 WordPress Foundation<br>
36 Licensed under the GNU GPLv2+ License.
37 HTML
38
39 def get_latest_version(opts)
40 doc = fetch_doc('https://wordpress.org/download/releases/', opts)
41 doc.at_css('.releases.latest td').content
42 end
43 end
44end
lib/docs/scrapers/yarn.rb+6−2
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Yarn < UrlScraper2 class Yarn < UrlScraper
3 self.type = 'yarn'3 self.type = 'simple'
4 self.release = '1.7.0'4 self.release = '1.12.1'
5 self.base_url = 'https://yarnpkg.com/en/docs/'5 self.base_url = 'https://yarnpkg.com/en/docs/'
6 self.links = {6 self.links = {
7 home: 'https://yarnpkg.com/',7 home: 'https://yarnpkg.com/',
@@ -20,5 +20,9 @@ module Docs · 12 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/docs/subscribers/requester_subscriber.rb+13−0
@@ -0,0 +1,13 @@
1# frozen_string_literal: true
2
3module Docs
4 class RequesterSubscriber < Subscriber
5 self.namespace = 'requester'
6
7 def handle_response(event)
8 if event.duration > 10_000
9 log "WARN: #{format_url event.payload[:url]} was slow to process (#{(event.duration / 1000).round}s)"
10 end
11 end
12 end
13end
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/docs.thor+117−15
@@ -12,15 +12,29 @@ class DocsCLI < Thor · 11 unmodified lines
12 end12 end
13 13
14 desc 'list', 'List available documentations'14 desc 'list', 'List available documentations'
15 option :packaged, type: :boolean
15 def list16 def list
16 output = Docs.all.flat_map do |doc|17 if options[:packaged]
17 name = doc.to_s.demodulize.underscore18 slugs = Dir[File.join(Docs.store_path, '*.tar.gz')].map { |f| File.basename(f, '.tar.gz') }
18 if doc.versioned?19 names = find_docs_by_slugs(slugs).map do |doc|
19 doc.versions.map { |_doc| "#{name}@#{_doc.version}" }20 name = if doc.version?
20 else21 "#{doc.superclass.to_s.demodulize.underscore}@#{doc.version}"
21 name22 else
23 doc.to_s.demodulize.underscore
24 end
25 end
26 else
27 names = Docs.all.flat_map do |doc|
28 name = doc.to_s.demodulize.underscore
29 if doc.versioned?
30 doc.versions.map { |_doc| "#{name}@#{_doc.version}" }
31 else
32 name
33 end
22 end34 end
23 end.join("\n")35 end
36
37 output = names.join("\n")
24 38
25 require 'tty-pager'39 require 'tty-pager'
26 TTY::Pager.new.page(output)40 TTY::Pager.new.page(output)
@@ -39,7 +53,7 @@ class DocsCLI < Thor · 12 unmodified lines
39 Docs.install_report :store if options[:verbose]53 Docs.install_report :store if options[:verbose]
40 if options[:debug]54 if options[:debug]
41 GC.disable55 GC.disable
42 Docs.install_report :filter, :request56 Docs.install_report :filter, :request, :doc
43 end57 end
44 58
45 if Docs.generate_page(name, options[:version], path)59 if Docs.generate_page(name, options[:version], path)
@@ -62,7 +76,7 @@ class DocsCLI < Thor · 16 unmodified lines
62 Docs.rescue_errors = true76 Docs.rescue_errors = true
63 Docs.install_report :store if options[:verbose]77 Docs.install_report :store if options[:verbose]
64 Docs.install_report :scraper if options[:debug]78 Docs.install_report :scraper if options[:debug]
65 Docs.install_report :progress_bar, :doc, :image if $stdout.tty?79 Docs.install_report :progress_bar, :doc, :image, :requester if $stdout.tty?
66 80
67 require 'unix_utils' if options[:package]81 require 'unix_utils' if options[:package]
68 82
@@ -149,15 +163,55 @@ class DocsCLI < Thor · 80 unmodified lines
149 option :dryrun, type: :boolean163 option :dryrun, type: :boolean
150 option :packaged, type: :boolean164 option :packaged, type: :boolean
151 def upload(*names)165 def upload(*names)
152 names = Dir[File.join(Docs.store_path, '*.tar.gz')].map { |f| File.basename(f, '.tar.gz') } if options[:packaged]166 require 'net/sftp'
153 docs = find_docs(names)167
168 if options[:packaged]
169 slugs = Dir[File.join(Docs.store_path, '*.tar.gz')].map { |f| File.basename(f, '.tar.gz') }
170 docs = find_docs_by_slugs(slugs)
171 else
172 docs = find_docs(names)
173 end
174
154 assert_docs(docs)175 assert_docs(docs)
176
177 # Verify files are present
155 docs.each do |doc|178 docs.each do |doc|
156 puts "Syncing #{doc.path}..."179 unless Dir.exists?(File.join(Docs.store_path, doc.path))
157 cmd = "aws s3 sync #{File.join(Docs.store_path, doc.path)} s3://docs.devdocs.io/#{doc.path} --delete"180 puts "ERROR: directory #{File.join(Docs.store_path, doc.path)} not found."
181 return
182 end
183
184 unless File.exists?(File.join(Docs.store_path, "#{doc.path}.tar.gz"))
185 puts "ERROR: package for '#{doc.slug}' documentation not found. Run 'thor docs:package #{doc.slug}' to create it."
186 return
187 end
188 end
189
190 # Sync files with S3 (used by the web app)
191 puts '[S3] Begin syncing.'
192 docs.each do |doc|
193 puts "[S3] Syncing #{doc.path}..."
194 cmd = "aws s3 sync #{File.join(Docs.store_path, doc.path)} s3://devdocs-assets/#{doc.path} --delete --profile devdocs"
158 cmd << ' --dryrun' if options[:dryrun]195 cmd << ' --dryrun' if options[:dryrun]
159 system(cmd)196 system(cmd)
160 end197 end
198 puts '[S3] Done syncing.'
199
200 # Upload packages to dl.devdocs.io (used by the "thor docs:download" command)
201 puts '[MaxCDN] Begin uploading.'
202 Net::SFTP.start('ftp.devdocs-dl.devdocs.netdna-cdn.com', ENV['DEVDOCS_DL_USERNAME'], password: ENV['DEVDOCS_DL_PASSWORD']) do |sftp|
203 docs.each do |doc|
204 filename = "#{doc.path}.tar.gz"
205 print "[MaxCDN] Uploading #{filename}..."
206 if options[:dryrun]
207 print "\n"
208 else
209 sftp.upload! File.join(Docs.store_path, filename), File.join('', 'public_html', filename)
210 print " OK\n"
211 end
212 end
213 end
214 puts '[MaxCDN] Done uploading.'
161 end215 end
162 216
163 desc 'commit', '[private]'217 desc 'commit', '[private]'
@@ -172,12 +226,60 @@ class DocsCLI < Thor · 8 unmodified lines
172 handle_doc_not_found_error(error)226 handle_doc_not_found_error(error)
173 end227 end
174 228
229 desc 'prepare_deploy', 'Internal task executed before deployment'
230 def prepare_deploy
231 puts 'Docs -- BEGIN'
232
233 require 'open-uri'
234 require 'thread'
235
236 docs = Docs.all_versions
237 time = Time.now.to_i
238 mutex = Mutex.new
239
240 (1..6).map do
241 Thread.new do
242 while doc = docs.shift
243 dir = File.join(Docs.store_path, doc.path)
244 FileUtils.mkpath(dir)
245
246 ['index.json', 'meta.json'].each do |filename|
247 open("https://docs.devdocs.io/#{doc.path}/#{filename}?#{time}") do |file|
248 mutex.synchronize do
249 path = File.join(dir, filename)
250 File.write(path, file.read)
251 end
252 end
253 end
254
255 puts "Docs -- Downloaded #{doc.slug}"
256 end
257 end
258 end.map(&:join)
259
260 puts 'Docs -- Generating manifest...'
261 generate_manifest
262
263 puts 'Docs -- DONE'
264 end
265
175 private266 private
176 267
177 def find_docs(names)268 def find_docs(names)
178 names.map do |name|269 names.flat_map do |name|
179 name, version = name.split(/@|~/)270 name, version = name.split(/@|~/)
180 Docs.find(name, version)271 if version == 'all'
272 Docs.find(name, false).versions
273 else
274 Docs.find(name, version)
275 end
276 end
277 end
278
279 def find_docs_by_slugs(slugs)
280 slugs.flat_map do |slug|
281 slug, version = slug.split(/~/)
282 Docs.find_by_slug(slug, version)
181 end283 end
182 end284 end
183 285
lib/tasks/sprites.thor+213−0
@@ -0,0 +1,213 @@
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 return unless items_with_icons.length > 0
35
36 log_details(items_with_icons, icons_per_row)
37
38 generate_spritesheet(16, items_with_icons, 'assets/images/sprites/docs.png') {|item| item[:icon_16]}
39 generate_spritesheet(32, items_with_icons, 'assets/images/sprites/docs@2x.png') {|item| item[:icon_32]}
40
41 # Add Mongoose's icon details to docs without custom icons
42 default_item = items_with_icons.find {|item| item[:type] == 'mongoose'}
43 items_without_icons.each do |item|
44 item[:row] = default_item[:row]
45 item[:col] = default_item[:col]
46 item[:dark_icon_fix] = default_item[:dark_icon_fix]
47 end
48
49 save_manifest(items, icons_per_row, 'assets/images/sprites/docs.json')
50
51 if options[:remove_public_icons]
52 logger.info('Removing public/icons')
53 FileUtils.rm_rf('public/icons')
54 end
55 end
56
57 private
58
59 def get_items
60 items = Docs.all.map do |doc|
61 base_path = "public/icons/docs/#{doc.slug}"
62 {
63 :type => doc.slug,
64 :path_16 => "#{base_path}/16.png",
65 :path_32 => "#{base_path}/16@2x.png"
66 }
67 end
68
69 # Checking paths against an array of possible paths is faster than 200+ File.exist? calls
70 files = Dir.glob('public/icons/docs/**/*.png')
71
72 items.each do |item|
73 item[:has_icons] = files.include?(item[:path_16]) && files.include?(item[:path_32])
74 end
75 end
76
77 def get_icon(path, max_size)
78 icon = ChunkyPNG::Image.from_file(path)
79
80 # Check if the icon is too big
81 # If it is, resize the image without changing the aspect ratio
82 if icon.width > max_size || icon.height > max_size
83 ratio = icon.width.to_f / icon.height
84 new_width = (icon.width >= icon.height ? max_size : max_size * ratio).floor
85 new_height = (icon.width >= icon.height ? max_size / ratio : max_size).floor
86
87 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}")
88
89 icon.resample_nearest_neighbor!(new_width, new_height)
90 end
91
92 icon
93 end
94
95 def get_sidebar_background
96 # This is a hacky way to get the background color of the sidebar
97 # Unfortunately, it's not possible to get the value of a SCSS variable from a Thor task
98 # Because hard-coding the value is even worse, we extract it using some regex
99 path = 'assets/stylesheets/global/_variables-dark.scss'
100 regex = /--sidebarBackground:\s+([^;]+);/
101 ChunkyPNG::Color.parse(File.read(path)[regex, 1])
102 end
103
104 def needs_dark_icon_fix(icon, bg_color)
105 # Determine whether the icon needs to be grayscaled if the user has enabled the dark theme
106 # The logic is roughly based on https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast
107 contrast = icon.pixels.select {|pixel| ChunkyPNG::Color.a(pixel) > 0}.map do |pixel|
108 get_contrast(bg_color, pixel)
109 end
110
111 avg = contrast.reduce(:+) / contrast.size.to_f
112 avg < 3.5
113 end
114
115 def get_contrast(base, other)
116 # Calculating the contrast ratio as described in the WCAG 2.0:
117 # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
118 l1 = get_luminance(base) + 0.05
119 l2 = get_luminance(other) + 0.05
120 ratio = l1 / l2
121 l2 > l1 ? 1 / ratio : ratio
122 end
123
124 def get_luminance(color)
125 rgb = [
126 ChunkyPNG::Color.r(color).to_f,
127 ChunkyPNG::Color.g(color).to_f,
128 ChunkyPNG::Color.b(color).to_f
129 ]
130
131 # Calculating the relative luminance as described in the WCAG 2.0:
132 # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
133
134 rgb.map! do |value|
135 value /= 255
136 value < 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4
137 end
138
139 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]
140 end
141
142 def generate_spritesheet(size, items_with_icons, output_path, &item_to_icon)
143 logger.info("Generating spritesheet #{output_path} with icons of size #{size} x #{size}")
144
145 icons_per_row = Math.sqrt(items_with_icons.length).ceil
146 spritesheet = ChunkyPNG::Image.new(size * icons_per_row, size * icons_per_row)
147
148 items_with_icons.each do |item|
149 icon = item_to_icon.call(item)
150
151 # Calculate the base coordinates
152 base_x = item[:col] * size
153 base_y = item[:row] * size
154
155 # Center the icon if it's not a perfect rectangle
156 x = base_x + ((size - icon.width) / 2).floor
157 y = base_y + ((size - icon.height) / 2).floor
158
159 spritesheet.compose!(icon, x, y)
160 end
161
162 FileUtils.mkdir_p(File.dirname(output_path))
163 spritesheet.save(output_path)
164 end
165
166 def save_manifest(items, icons_per_row, path)
167 logger.info("Saving spritesheet details to #{path}")
168
169 FileUtils.mkdir_p(File.dirname(path))
170
171 # Only save the details that the scss file needs
172 manifest_items = items.map do |item|
173 {
174 :type => item[:type],
175 :row => item[:row],
176 :col => item[:col],
177 :dark_icon_fix => item[:dark_icon_fix]
178 }
179 end
180
181 manifest = {:icons_per_row => icons_per_row, :items => manifest_items}
182
183 File.open(path, 'w') do |f|
184 f.write(JSON.generate(manifest))
185 end
186 end
187
188 def log_details(items_with_icons, icons_per_row)
189 logger.debug("Amount of icons: #{items_with_icons.length}")
190 logger.debug("Amount of icons needing the dark icon fix: #{items_with_icons.count {|item| item[:dark_icon_fix]}}")
191 logger.debug("Amount of icons per row: #{icons_per_row}")
192
193 max_type_length = items_with_icons.map {|item| item[:type].length}.max
194 border = "+#{'-' * (max_type_length + 2)}+#{'-' * 5}+#{'-' * 8}+#{'-' * 15}+"
195
196 logger.debug(border)
197 logger.debug("| #{'Type'.ljust(max_type_length)} | Row | Column | Dark icon fix |")
198 logger.debug(border)
199
200 items_with_icons.each do |item|
201 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)} |")
202 end
203
204 logger.debug(border)
205 end
206
207 def logger
208 @logger ||= Logger.new($stdout).tap do |logger|
209 logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
210 logger.formatter = proc {|severity, datetime, progname, msg| "#{msg}\n"}
211 end
212 end
213end
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/favicon.pxm+0−0

Binary file not shown.

public/favicon@2x.pxm+0−0

Binary file not shown.

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/bash/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/bash/SOURCE+1−0
@@ -0,0 +1 @@
1https://github.com/odb/official-bash-logo
public/icons/docs/bluebird/16@2x.png+0−0

Binary file not shown.

public/icons/docs/composer/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/composer/SOURCE+1−0
@@ -0,0 +1 @@
1https://github.com/composer/getcomposer.org/blob/master/web/img/logo-composer-transparent.png
public/icons/docs/css/CSS3.sketch/Data+0−0

Binary file not shown.

public/icons/docs/css/CSS3.sketch/QuickLook/Preview.png+0−0

Binary file not shown.

public/icons/docs/css/CSS3.sketch/QuickLook/Thumbnail.png+0−0

Binary file not shown.

public/icons/docs/css/CSS3.sketch/fonts+0−0

Empty file added.

public/icons/docs/css/CSS3.sketch/version+1−0
@@ -0,0 +1 @@
110
public/icons/docs/cypress/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/cypress/SOURCE+1−0
@@ -0,0 +1 @@
1https://github.com/cypress-io/cypress-documentation/raw/develop/themes/cypress/source/img/favicon.ico
public/icons/docs/dart/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/dart/SOURCE+1−0
@@ -0,0 +1 @@
1https://github.com/dart-lang/logos
public/icons/docs/dom/DOM.sketch/Data+0−0

Binary file not shown.

public/icons/docs/dom/DOM.sketch/QuickLook/Preview.png+0−0

Binary file not shown.

public/icons/docs/dom/DOM.sketch/QuickLook/Thumbnail.png+0−0

Binary file not shown.

public/icons/docs/dom/DOM.sketch/fonts+0−0

Empty file added.

public/icons/docs/dom/DOM.sketch/version+1−0
@@ -0,0 +1 @@
110
public/icons/docs/dom_events/DOM_events.sketch/Data+0−0

Binary file not shown.

public/icons/docs/dom_events/DOM_events.sketch/QuickLook/Preview.png+0−0

Binary file not shown.

public/icons/docs/dom_events/DOM_events.sketch/QuickLook/Thumbnail.png+0−0

Binary file not shown.

public/icons/docs/dom_events/DOM_events.sketch/fonts+0−0

Empty file added.

public/icons/docs/dom_events/DOM_events.sketch/version+1−0
@@ -0,0 +1 @@
110
public/icons/docs/express/16.pxm+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/handlebars/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/handlebars/SOURCE+1−0
@@ -0,0 +1 @@
1https://github.com/yahoo/formatjs-site/tree/master/public/img
public/icons/docs/html/HTML5.sketch/Data+0−0

Binary file not shown.

public/icons/docs/html/HTML5.sketch/QuickLook/Preview.png+0−0

Binary file not shown.

public/icons/docs/html/HTML5.sketch/QuickLook/Thumbnail.png+0−0

Binary file not shown.

public/icons/docs/html/HTML5.sketch/fonts+0−0

Empty file added.

public/icons/docs/html/HTML5.sketch/version+1−0
@@ -0,0 +1 @@
110
public/icons/docs/http/16.pxm+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/javascript/16.pxm+0−0

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/less/less.pxm+0−0

Binary file not shown.

public/icons/docs/moment/moment.sketch/Data+0−0

Binary file not shown.

public/icons/docs/moment/moment.sketch/QuickLook/Preview.png+0−0

Binary file not shown.

public/icons/docs/moment/moment.sketch/QuickLook/Thumbnail.png+0−0

Binary file not shown.

public/icons/docs/moment/moment.sketch/fonts+0−0

Empty file added.

public/icons/docs/moment/moment.sketch/metadata+14−0
@@ -0,0 +1,14 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5 <key>app</key>
6 <string>com.bohemiancoding.sketch</string>
7 <key>build</key>
8 <integer>5302</integer>
9 <key>commit</key>
10 <string>9460a4bc62af5e9ba50dd4143578fd9401710ce5</string>
11 <key>version</key>
12 <integer>18</integer>
13</dict>
14</plist>
public/icons/docs/moment/moment.sketch/version+1−0
@@ -0,0 +1 @@
118
public/icons/docs/nokogiri/icon.pxm+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/puppeteer/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/puppeteer/SOURCE+1−0
@@ -0,0 +1 @@
1https://github.com/GoogleChromeLabs/pptr.dev/tree/master/src/favicons
public/icons/docs/pygame/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/qt/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/qt/SOURCE+1−0
@@ -0,0 +1 @@
1https://commons.wikimedia.org/wiki/File:Qt_logo_2016.svg
public/icons/docs/rails/rails.pxm+0−0

Binary file not shown.

public/icons/docs/saltstack/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/saltstack/SOURCE+1−0
@@ -0,0 +1 @@
1https://github.com/saltstack/salt/blob/develop/doc/_static/salt-logo.svg
public/icons/docs/sass/sass.pxm+0−0

Binary file not shown.

public/icons/docs/scala/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/support_tables/16.pxm+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/vue_router/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/vue_router/SOURCE+1−0
@@ -0,0 +1 @@
1https://github.com/vuejs/vuejs.org/blob/master/assets/logo.ai
public/icons/docs/wordpress/16.png+0−0

Binary file not shown.

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

Binary file not shown.

public/icons/docs/wordpress/SOURCE+1−0
@@ -0,0 +1 @@
1https://wordpress.org/about/logos/
public/icons/docs/xpath/XPath.sketch/Data+0−0

Binary file not shown.

public/icons/docs/xpath/XPath.sketch/QuickLook/Preview.png+0−0

Binary file not shown.

public/icons/docs/xpath/XPath.sketch/QuickLook/Thumbnail.png+0−0

Binary file not shown.

public/icons/docs/xpath/XPath.sketch/metadata+18−0
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5 <key>app</key>
6 <string>com.bohemiancoding.sketch</string>
7 <key>build</key>
8 <integer>5355</integer>
9 <key>commit</key>
10 <string>b7d299b0a34651d1a0e066786b75aa36168d5809</string>
11 <key>fonts</key>
12 <array/>
13 <key>length</key>
14 <integer>22373</integer>
15 <key>version</key>
16 <integer>18</integer>
17</dict>
18</plist>
public/icons/docs/xpath/XPath.sketch/version+1−0
@@ -0,0 +1 @@
118
public/images/apple-icon.pxm+0−0

Binary file not shown.

public/images/fluid-icon.pxm+0−0

Binary file not shown.

public/images/icon.pxm+0−0

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

public/images/webapp-icon-small.pxm+0−0

Binary file not shown.

public/manifest.json+11−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,10 +26,20 @@ · 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",
31 "type": "image/png"37 "type": "image/png"
38 },
39 {
40 "src": "/images/webapp-icon-512.png",
41 "sizes": "512x512",
42 "type": "image/png"
32 }43 }
33 ]44 ]
34}45}
test/app_test.rb+36−90
@@ -11,6 +11,21 @@ class AppTest < MiniTest::Spec · 10 unmodified lines
11 App11 App
12 end12 end
13 13
14 before do
15 current_session.env('HTTPS', 'on')
16 end
17
18 it 'redirects to HTTPS' do
19 get 'http://example.com/test?q=1', {}, 'HTTPS' => 'off'
20 assert last_response.redirect?
21 assert_equal 'https://example.com/test?q=1', last_response['Location']
22 end
23
24 it 'returns HSTS header' do
25 get 'https://example.com/test'
26 assert_equal 'max-age=31536000; includeSubDomains', last_response['Strict-Transport-Security']
27 end
28
14 describe "/" do29 describe "/" do
15 it "works" do30 it "works" do
16 get '/'31 get '/'
@@ -20,30 +35,13 @@ class AppTest < MiniTest::Spec · 3 unmodified lines
20 it "redirects to /#q= when there is a 'q' query param" do35 it "redirects to /#q= when there is a 'q' query param" do
21 get '/search', q: 'foo'36 get '/search', q: 'foo'
22 assert last_response.redirect?37 assert last_response.redirect?
23 assert_equal 'http://example.org/#q=foo', last_response['Location']38 assert_equal 'https://example.org/#q=foo', last_response['Location']
24 end39 end
25 40
26 it "redirects without the query string" do41 it "redirects without the query string" do
27 get '/', foo: 'bar'42 get '/', foo: 'bar'
28 assert last_response.redirect?43 assert last_response.redirect?
29 assert_equal 'http://example.org/', last_response['Location']44 assert_equal 'https://example.org/', last_response['Location']
30 end
31
32 it "sets default size" do
33 get '/'
34 assert_includes last_response.body, 'data-size="20rem"'
35 end
36
37 it "sets size from cookie" do
38 set_cookie('size=42')
39 get '/'
40 assert_includes last_response.body, 'data-size="42px"'
41 end
42
43 it "sets layout from cookie" do
44 set_cookie('layout=foo')
45 get '/'
46 assert_includes last_response.body, '<body class="foo">'
47 end45 end
48 end46 end
49 47
@@ -52,7 +50,7 @@ class AppTest < MiniTest::Spec · 2 unmodified lines
52 %w(offline about news help).each do |page|50 %w(offline about news help).each do |page|
53 get "/#{page}", {}, 'HTTP_USER_AGENT' => MODERN_BROWSER51 get "/#{page}", {}, 'HTTP_USER_AGENT' => MODERN_BROWSER
54 assert last_response.redirect?52 assert last_response.redirect?
55 assert_equal "http://example.org/#/#{page}", last_response['Location']53 assert_equal "https://example.org/#/#{page}", last_response['Location']
56 end54 end
57 end55 end
58 56
@@ -61,7 +59,7 @@ class AppTest < MiniTest::Spec · 2 unmodified lines
61 set_cookie('foo=bar')59 set_cookie('foo=bar')
62 get "/#{page}", {}, 'HTTP_USER_AGENT' => MODERN_BROWSER60 get "/#{page}", {}, 'HTTP_USER_AGENT' => MODERN_BROWSER
63 assert last_response.redirect?61 assert last_response.redirect?
64 assert_equal 'http://example.org/', last_response['Location']62 assert_equal 'https://example.org/', last_response['Location']
65 assert last_response['Set-Cookie'].start_with?("initial_path=%2F#{page}; path=/; expires=")63 assert last_response['Set-Cookie'].start_with?("initial_path=%2F#{page}; path=/; expires=")
66 end64 end
67 end65 end
@@ -71,63 +69,11 @@ class AppTest < MiniTest::Spec · 3 unmodified lines
71 it "redirects to /#q=" do69 it "redirects to /#q=" do
72 get '/search'70 get '/search'
73 assert last_response.redirect?71 assert last_response.redirect?
74 assert_equal 'http://example.org/#q=', last_response['Location']72 assert_equal 'https://example.org/#q=', last_response['Location']
75 73
76 get '/search', q: 'foo'74 get '/search', q: 'foo'
77 assert last_response.redirect?75 assert last_response.redirect?
78 assert_equal 'http://example.org/#q=foo', last_response['Location']76 assert_equal 'https://example.org/#q=foo', last_response['Location']
79 end
80 end
81
82 describe "/manifest.appcache" do
83 it "works" do
84 get '/manifest.appcache'
85 assert last_response.ok?
86 end
87
88 it "works with cookie" do
89 set_cookie('docs=css/html~5')
90 get '/manifest.appcache'
91 assert last_response.ok?
92 assert_includes last_response.body, '/css/index.json?1420139788'
93 assert_includes last_response.body, '/html~5/index.json?1420139791'
94 end
95
96 it "ignores invalid docs in the cookie" do
97 set_cookie('docs=foo')
98 get '/manifest.appcache'
99 assert last_response.ok?
100 refute_includes last_response.body, 'foo'
101 end
102
103 it "has the word 'default' when no 'dark' cookie is set" do
104 get '/manifest.appcache'
105 assert_includes last_response.body, '# default'
106 refute_includes last_response.body, '# dark'
107 end
108
109 it "has the word 'dark' when the cookie is set" do
110 set_cookie('dark=1')
111 get '/manifest.appcache'
112 assert_includes last_response.body, '# dark'
113 refute_includes last_response.body, '# default'
114 end
115
116 it "sets default size" do
117 get '/manifest.appcache'
118 assert_includes last_response.body, '20rem'
119 end
120
121 it "sets size from cookie" do
122 set_cookie('size=42')
123 get '/manifest.appcache'
124 assert_includes last_response.body, '42px'
125 end
126
127 it "sets layout from cookie" do
128 set_cookie('layout=foo_layout')
129 get '/manifest.appcache'
130 assert_includes last_response.body, 'foo_layout'
131 end77 end
132 end78 end
133 79
@@ -148,7 +94,7 @@ class AppTest < MiniTest::Spec · 14 unmodified lines
148 set_cookie('docs=html~5')94 set_cookie('docs=html~5')
149 get '/html~5/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER95 get '/html~5/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER
150 assert last_response.redirect?96 assert last_response.redirect?
151 assert_equal 'http://example.org/', last_response['Location']97 assert_equal 'https://example.org/', last_response['Location']
152 assert last_response['Set-Cookie'].start_with?("initial_path=%2Fhtml%7E5%2F; path=/; expires=")98 assert last_response['Set-Cookie'].start_with?("initial_path=%2Fhtml%7E5%2F; path=/; expires=")
153 end99 end
154 100
@@ -161,13 +107,13 @@ class AppTest < MiniTest::Spec · 6 unmodified lines
161 set_cookie('docs=html~5')107 set_cookie('docs=html~5')
162 get '/html/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER108 get '/html/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER
163 assert last_response.redirect?109 assert last_response.redirect?
164 assert_equal 'http://example.org/', last_response['Location']110 assert_equal 'https://example.org/', last_response['Location']
165 assert last_response['Set-Cookie'].start_with?("initial_path=%2Fhtml%2F; path=/; expires=")111 assert last_response['Set-Cookie'].start_with?("initial_path=%2Fhtml%2F; path=/; expires=")
166 end112 end
167 113
168 it "renders when the doc exists and is enabled, and the request is from Googlebot" do114 it "renders when the doc exists and is enabled, and the request is from Googlebot" do
169 set_cookie('docs=html')115 set_cookie('docs=html')
170 get '/html/', {}, 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'116 get '/html/', {}, 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +https://www.google.com/bot.html)'
171 assert last_response.ok?117 assert last_response.ok?
172 end118 end
173 119
@@ -187,17 +133,17 @@ class AppTest < MiniTest::Spec · 13 unmodified lines
187 it "redirects with trailing slash" do133 it "redirects with trailing slash" do
188 get '/html'134 get '/html'
189 assert last_response.redirect?135 assert last_response.redirect?
190 assert_equal 'http://example.org/html/', last_response['Location']136 assert_equal 'https://example.org/html/', last_response['Location']
191 137
192 get '/html', bar: 'baz'138 get '/html', bar: 'baz'
193 assert last_response.redirect?139 assert last_response.redirect?
194 assert_equal 'http://example.org/html/?bar=baz', last_response['Location']140 assert_equal 'https://example.org/html/?bar=baz', last_response['Location']
195 end141 end
196 142
197 it "redirects old docs" do143 it "redirects old docs" do
198 get '/iojs/'144 get '/iojs/'
199 assert last_response.redirect?145 assert last_response.redirect?
200 assert_equal 'http://example.org/node/', last_response['Location']146 assert_equal 'https://example.org/node/', last_response['Location']
201 end147 end
202 end148 end
203 149
@@ -232,17 +178,17 @@ class AppTest < MiniTest::Spec · 28 unmodified lines
232 it "redirects with trailing slash" do178 it "redirects with trailing slash" do
233 get '/css-foo'179 get '/css-foo'
234 assert last_response.redirect?180 assert last_response.redirect?
235 assert_equal 'http://example.org/css-foo/', last_response['Location']181 assert_equal 'https://example.org/css-foo/', last_response['Location']
236 182
237 get '/css-foo', bar: 'baz'183 get '/css-foo', bar: 'baz'
238 assert last_response.redirect?184 assert last_response.redirect?
239 assert_equal 'http://example.org/css-foo/?bar=baz', last_response['Location']185 assert_equal 'https://example.org/css-foo/?bar=baz', last_response['Location']
240 end186 end
241 187
242 it "redirects old docs" do188 it "redirects old docs" do
243 get '/yii1-foo/'189 get '/yii1-foo/'
244 assert last_response.redirect?190 assert last_response.redirect?
245 assert_equal 'http://example.org/yii~1.1-foo/', last_response['Location']191 assert_equal 'https://example.org/yii~1.1-foo/', last_response['Location']
246 end192 end
247 end193 end
248 194
@@ -263,17 +209,17 @@ class AppTest < MiniTest::Spec · 14 unmodified lines
263 it "redirects without trailing slash" do209 it "redirects without trailing slash" do
264 get '/css/foo/'210 get '/css/foo/'
265 assert last_response.redirect?211 assert last_response.redirect?
266 assert_equal 'http://example.org/css/foo', last_response['Location']212 assert_equal 'https://example.org/css/foo', last_response['Location']
267 213
268 get '/css/foo/', bar: 'baz'214 get '/css/foo/', bar: 'baz'
269 assert last_response.redirect?215 assert last_response.redirect?
270 assert_equal 'http://example.org/css/foo?bar=baz', last_response['Location']216 assert_equal 'https://example.org/css/foo?bar=baz', last_response['Location']
271 end217 end
272 218
273 it "redirects old docs" do219 it "redirects old docs" do
274 get '/python2/foo'220 get '/python2/foo'
275 assert last_response.redirect?221 assert last_response.redirect?
276 assert_equal 'http://example.org/python~2.7/foo', last_response['Location']222 assert_equal 'https://example.org/python~2.7/foo', last_response['Location']
277 end223 end
278 end224 end
279 225
@@ -281,7 +227,7 @@ class AppTest < MiniTest::Spec · 1 unmodified line
281 it "returns to the asset path" do227 it "returns to the asset path" do
282 get '/docs.json'228 get '/docs.json'
283 assert last_response.redirect?229 assert last_response.redirect?
284 assert_equal 'http://example.org/assets/docs.json', last_response['Location']230 assert_equal 'https://example.org/assets/docs.json', last_response['Location']
285 end231 end
286 end232 end
287 233
@@ -289,7 +235,7 @@ class AppTest < MiniTest::Spec · 1 unmodified line
289 it "returns to the asset path" do235 it "returns to the asset path" do
290 get '/application.js'236 get '/application.js'
291 assert last_response.redirect?237 assert last_response.redirect?
292 assert_equal 'http://example.org/assets/application.js', last_response['Location']238 assert_equal 'https://example.org/assets/application.js', last_response['Location']
293 end239 end
294 end240 end
295 241
@@ -297,7 +243,7 @@ class AppTest < MiniTest::Spec · 1 unmodified line
297 it "returns to the asset path" do243 it "returns to the asset path" do
298 get '/application.css'244 get '/application.css'
299 assert last_response.redirect?245 assert last_response.redirect?
300 assert_equal 'http://example.org/assets/application.css', last_response['Location']246 assert_equal 'https://example.org/assets/application.css', last_response['Location']
301 end247 end
302 end248 end
303 249
test/lib/docs/core/scrapers/file_scraper_test.rb+90−48
@@ -2,16 +2,23 @@ require 'test_helper' · 1 unmodified line
2require 'docs'2require 'docs'
3 3
4class FileScraperTest < MiniTest::Spec4class FileScraperTest < MiniTest::Spec
5 ROOT_PATH = File.expand_path('../../../../../../', __FILE__)
6
5 class Scraper < Docs::FileScraper7 class Scraper < Docs::FileScraper
6 self.dir = '/'
7 self.html_filters = Docs::FilterStack.new8 self.html_filters = Docs::FilterStack.new
8 self.text_filters = Docs::FilterStack.new9 self.text_filters = Docs::FilterStack.new
10
11 version 'version' do; end
9 end12 end
10 13
11 let :scraper do14 let :scraper do
12 Scraper.new15 Scraper.new
13 end16 end
14 17
18 let :versioned_scraper do
19 Scraper.versions.first.new
20 end
21
15 let :response do22 let :response do
16 OpenStruct.new body: 'body', url: Docs::URL.parse(Scraper.base_url)23 OpenStruct.new body: 'body', url: Docs::URL.parse(Scraper.base_url)
17 end24 end
@@ -22,9 +29,16 @@ class FileScraperTest < MiniTest::Spec · 4 unmodified lines
22 end29 end
23 end30 end
24 31
32 describe "#source_directory" do
33 it "returns the directory at docs/[slug]" do
34 assert_equal File.join(ROOT_PATH, 'docs', 'scraper'), scraper.source_directory
35 assert_equal File.join(ROOT_PATH, 'docs', 'scraper~version'), versioned_scraper.source_directory
36 end
37 end
38
25 describe "#request_one" do39 describe "#request_one" do
26 let :path do40 let :path do
27 File.join(Scraper.dir, 'path')41 'path'
28 end42 end
29 43
30 let :result do44 let :result do
@@ -35,20 +49,34 @@ class FileScraperTest < MiniTest::Spec · 4 unmodified lines
35 stub(scraper).read_file49 stub(scraper).read_file
36 end50 end
37 51
38 it "reads a file" do52 context "when the source directory doesn't exist" do
39 mock(scraper).read_file(path)53 it "raises an error" do
40 result54 assert_raises Docs::SetupError do
55 result
56 end
57 end
41 end58 end
42 59
43 describe "the returned response object" do60 context "when the source directory exists" do
44 it "has a #body" do61 before do
45 stub(scraper).read_file { 'body' }62 stub(scraper).assert_source_directory_exists
46 assert_equal 'body', result.body63 end
64
65 it "reads a file" do
66 mock(scraper).read_file(File.join(ROOT_PATH, 'docs/scraper', path))
67 result
47 end68 end
48 69
49 it "has a #url" do70 describe "the returned response object" do
50 assert_equal path, result.url.to_s71 it "has a #body" do
51 assert_instance_of Docs::URL, result.url72 stub(scraper).read_file { 'body' }
73 assert_equal 'body', result.body
74 end
75
76 it "has a #url" do
77 assert_equal path, result.url.to_s
78 assert_instance_of Docs::URL, result.url
79 end
52 end80 end
53 end81 end
54 end82 end
@@ -58,49 +86,63 @@ class FileScraperTest < MiniTest::Spec · 3 unmodified lines
58 %w(one two)86 %w(one two)
59 end87 end
60 88
61 it "requests the given url" do89 context "when the source directory doesn't exist" do
62 mock(scraper).request_one('url')90 it "raises an error" do
63 scraper.send(:request_all, 'url') {}91 assert_raises Docs::SetupError do
64 end92 scraper.send(:request_all, urls) {}
65 93 end
66 it "requests the given urls" do94 end
67 requests = []
68 stub(scraper).request_one { |url| requests << url; nil }
69 scraper.send(:request_all, urls) {}
70 assert_equal urls, requests
71 end
72
73 it "yields the responses" do
74 responses = []
75 stub(scraper).request_one { |url| urls.index(url) }
76 scraper.send(:request_all, urls) { |response| responses << response; nil }
77 assert_equal (0...urls.length).to_a, responses
78 end95 end
79 96
80 context "when the block returns an array" do97 context "when the source directory exists" do
81 let :next_urls do98 before do
82 %w(three four)99 stub(scraper).assert_source_directory_exists
83 end100 end
84 101
85 let :all_urls do102 it "requests the given url" do
86 urls + %w(three four)103 mock(scraper).request_one('url')
104 scraper.send(:request_all, 'url') {}
87 end105 end
88 106
89 it "requests the returned urls" do107 it "requests the given urls" do
90 requests = []108 requests = []
91 stub(scraper).request_one { |url| requests << url; url }109 stub(scraper).request_one { |url| requests << url; nil }
92 scraper.send(:request_all, urls) { [next_urls.shift].compact }110 scraper.send(:request_all, urls) {}
93 assert_equal all_urls, requests111 assert_equal urls, requests
94 end112 end
95 113
96 it "yields their responses" do114 it "yields the responses" do
97 responses = []115 responses = []
98 stub(scraper).request_one { |url| all_urls.index(url) }116 stub(scraper).request_one { |url| urls.index(url) }
99 scraper.send :request_all, urls do |response|117 scraper.send(:request_all, urls) { |response| responses << response; nil }
100 responses << response118 assert_equal (0...urls.length).to_a, responses
101 [next_urls.shift].compact119 end
120
121 context "when the block returns an array" do
122 let :next_urls do
123 %w(three four)
124 end
125
126 let :all_urls do
127 urls + %w(three four)
128 end
129
130 it "requests the returned urls" do
131 requests = []
132 stub(scraper).request_one { |url| requests << url; url }
133 scraper.send(:request_all, urls) { [next_urls.shift].compact }
134 assert_equal all_urls, requests
135 end
136
137 it "yields their responses" do
138 responses = []
139 stub(scraper).request_one { |url| all_urls.index(url) }
140 scraper.send :request_all, urls do |response|
141 responses << response
142 [next_urls.shift].compact
143 end
144 assert_equal (0...all_urls.length).to_a, responses
102 end145 end
103 assert_equal (0...all_urls.length).to_a, responses
104 end146 end
105 end147 end
106 end148 end
@@ -123,16 +165,16 @@ class FileScraperTest < MiniTest::Spec · 16 unmodified lines
123 165
124 describe "#read_file" do166 describe "#read_file" do
125 let :result do167 let :result do
126 scraper.send :read_file, 'file'168 scraper.send :read_file, File.join(ROOT_PATH, 'docs', 'scraper', 'file')
127 end169 end
128 170
129 it "returns the file's content when the file exists" do171 it "returns the file's content when the file exists in the source directory" do
130 stub(File).read('file') { 'content' }172 stub(File).read(File.join(ROOT_PATH, 'docs', 'scraper', 'file')) { 'content' }
131 assert_equal 'content', result173 assert_equal 'content', result
132 end174 end
133 175
134 it "returns nil when the file doesn't exist" do176 it "returns nil when the file doesn't exist" do
135 stub(File).read('file') { raise }177 stub(File).read(File.join(ROOT_PATH, 'docs', 'scraper', 'file')) { raise }
136 assert_nil result178 assert_nil result
137 end179 end
138 end180 end
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+4−3
@@ -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">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 %>">
@@ -30,9 +31,9 @@ · 10 unmodified lines
30 <link rel="apple-touch-icon" sizes="152x152" href="<%= App.cdn_origin %>/images/apple-icon-152.png">31 <link rel="apple-touch-icon" sizes="152x152" href="<%= App.cdn_origin %>/images/apple-icon-152.png">
31 <link rel="apple-touch-icon" sizes="160x160" href="<%= App.cdn_origin %>/images/apple-icon-160.png">32 <link rel="apple-touch-icon" sizes="160x160" href="<%= App.cdn_origin %>/images/apple-icon-160.png">
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 <link rel="stylesheet" href="<%= main_stylesheet_path %>" data-alt="<%= alternate_stylesheet_path %>">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
@@ -5,15 +5,16 @@ · 4 unmodified lines
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 %>">
11 <link rel="manifest" href="/manifest.json">12 <link rel="manifest" href="/manifest.json">
12 <link rel="icon" type="image/x-icon" href="<%= App.cdn_origin %>/favicon.ico">13 <link rel="icon" type="image/x-icon" href="<%= App.cdn_origin %>/favicon.ico">
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 <link rel="stylesheet" href="<%= main_stylesheet_path %>" data-alt="<%= alternate_stylesheet_path %>">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+61−0
@@ -0,0 +1,61 @@
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 try {
39 const response = await fetch(event.request);
40
41 if (!response.ok) {
42 throw new Error(`The HTTP request failed with status code ${response.status}`);
43 }
44
45 return response;
46 } catch (err) {
47 const url = new URL(event.request.url);
48
49 <%# 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 %>
50 <%# The index page will make sure the correct documentation or a proper offline page is shown %>
51 const pathname = url.pathname;
52 const filename = pathname.substr(1 + pathname.lastIndexOf('/')).split(/\#|\?/g)[0];
53 if (url.origin === location.origin && !filename.includes('.')) {
54 const cachedIndex = await caches.match('/');
55 if (cachedIndex) return cachedIndex;
56 }
57
58 throw err;
59 }
60 })());
61});
views/unsupported.erb+7−10
@@ -10,20 +10,17 @@ · 9 unmodified lines
10 <h1 class="_fail-title">Your browser is unsupported, sorry.</h1>10 <h1 class="_fail-title">Your browser is unsupported, sorry.</h1>
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 Chrome and Firefox</li>13 <li>Recent versions of Firefox, Chrome, or Opera</li>
14 <li>Safari 5.1+</li>14 <li>Safari 11.1+</li>
15 <li>Opera 12.1+</li>15 <li>Edge 17+</li>
16 <li>Internet Explorer 10+</li>16 <li>iOS 11.3+</li>
17 <li>iOS 6+</li>
18 <li>Android 4.1+</li>
19 <li>Windows Phone 8+</li>
20 </ul>17 </ul>
21 <p class="_fail-text">18 <p class="_fail-text">
22 If you're unable to upgrade, I apologize.19 If you're unable to upgrade, we apologize.
23 I decided to prioritize speed and new features over support for older browsers.20 We decided to prioritize speed and new features over support for older browsers.
24 </p>21 </p>
25 <p class="_fail-text">22 <p class="_fail-text">
26 &mdash; Thibaut <a href="https://twitter.com/DevDocs" class="_fail-link">@DevDocs</a>23 &mdash; <a href="https://twitter.com/DevDocs">@DevDocs</a>
27 </p>24 </p>
28 </div>25 </div>
29</body>26</body>