History

Fix merge conflict

Jasper van Merle committed 7 years ago

Commit 74323fd2 parents 2f96086 85d591e503 files · +10961 −5041Browse files

Unified · Split

503 files
  1. .editorconfig +11 −0
  2. .github/CONTRIBUTING.md +6 −16
  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 −0
  8. .travis.yml +25 −0
  9. CODE_OF_CONDUCT.md +2 −0
  10. COPYRIGHT +4 −4
  11. Dockerfile +5 −2
  12. Dockerfile-alpine +5 −2
  13. Gemfile +12 −6
  14. Gemfile.lock +86 −67
  15. ISSUE_TEMPLATE.md +0 −7
  16. PULL_REQUEST_TEMPLATE.md +11 −0
  17. Procfile +1 −0
  18. README.md +51 −31
  19. Rakefile +4 −0
  20. assets/images/.gitignore +1 −0
  21. assets/images/docs-1.png +0 −0
  22. assets/images/docs-1@2x.png +0 −0
  23. assets/images/docs-2.png +0 −0
  24. assets/images/docs-2@2x.png +0 −0
  25. assets/javascripts/app/app.coffee +21 −14
  26. assets/javascripts/app/appcache.coffee +0 −42
  27. assets/javascripts/app/config.coffee.erb +3 −0
  28. assets/javascripts/app/serviceworker.coffee +49 −0
  29. assets/javascripts/app/settings.coffee +34 −1
  30. assets/javascripts/app/shortcuts.coffee +3 −0
  31. assets/javascripts/app/update_checker.coffee +3 −3
  32. assets/javascripts/lib/cookies_store.coffee +5 −1
  33. assets/javascripts/lib/favicon.coffee +64 −0
  34. assets/javascripts/lib/license.coffee +1 −1
  35. assets/javascripts/lib/page.coffee +18 −2
  36. assets/javascripts/lib/util.coffee +5 −1
  37. assets/javascripts/models/doc.coffee +3 −1
  38. assets/javascripts/news.json +35 −3
  39. assets/javascripts/templates/error_tmpl.coffee +11 −14
  40. assets/javascripts/templates/notice_tmpl.coffee +1 −1
  41. assets/javascripts/templates/notif_tmpl.coffee +9 −3
  42. assets/javascripts/templates/pages/about_tmpl.coffee +146 −72
  43. assets/javascripts/templates/pages/help_tmpl.coffee +6 −5
  44. assets/javascripts/templates/pages/news_tmpl.coffee.erb +1 −1
  45. assets/javascripts/templates/pages/offline_tmpl.coffee +10 −5
  46. assets/javascripts/templates/pages/root_tmpl.coffee.erb +11 −13
  47. assets/javascripts/templates/pages/settings_tmpl.coffee +8 −0
  48. assets/javascripts/tracking.js +27 −23
  49. assets/javascripts/vendor/fastclick.js +2 −2
  50. assets/javascripts/vendor/prism.js +879 −417
  51. assets/javascripts/vendor/raven.js +2544 −2655
  52. assets/javascripts/views/content/content.coffee +7 −1
  53. assets/javascripts/views/content/entry_page.coffee +2 −1
  54. assets/javascripts/views/content/offline_page.coffee +1 −0
  55. assets/javascripts/views/content/root_page.coffee +10 −3
  56. assets/javascripts/views/content/settings_page.coffee +10 −12
  57. assets/javascripts/views/content/type_page.coffee +1 −0
  58. assets/javascripts/views/layout/document.coffee +8 −6
  59. assets/javascripts/views/layout/mobile.coffee +2 −2
  60. assets/javascripts/views/layout/resizer.coffee +3 −10
  61. assets/javascripts/views/layout/settings.coffee +1 −9
  62. assets/javascripts/views/search/search.coffee +13 −1
  63. assets/javascripts/views/search/search_scope.coffee +23 −5
  64. assets/javascripts/views/sidebar/sidebar.coffee +1 −1
  65. assets/stylesheets/application-dark.css.scss +0 −101
  66. assets/stylesheets/application.css.scss +15 −6
  67. assets/stylesheets/components/_app.scss +5 −5
  68. assets/stylesheets/components/_content.scss +30 −32
  69. assets/stylesheets/components/_fail.scss +2 −4
  70. assets/stylesheets/components/_header.scss +26 −35
  71. assets/stylesheets/components/_mobile.scss +2 −2
  72. assets/stylesheets/components/_notice.scss +5 −5
  73. assets/stylesheets/components/_notif.scss +16 −12
  74. assets/stylesheets/components/_page.scss +4 −4
  75. assets/stylesheets/components/_path.scss +6 −6
  76. assets/stylesheets/components/_prism.scss +25 −32
  77. assets/stylesheets/components/_settings.scss +12 −12
  78. assets/stylesheets/components/_sidebar.scss +37 −39
  79. assets/stylesheets/global/_base.scss +40 −31
  80. assets/stylesheets/global/_classes.scss +24 −25
  81. assets/stylesheets/global/_icons.scss +0 −166
  82. assets/stylesheets/global/_icons.scss.erb +43 −0
  83. assets/stylesheets/global/_mixins.scss +15 −0
  84. assets/stylesheets/global/_print.scss +57 −0
  85. assets/stylesheets/global/_variables-dark.scss +53 −69
  86. assets/stylesheets/global/_variables-light.scss +76 −0
  87. assets/stylesheets/global/_variables.scss +25 −82
  88. assets/stylesheets/pages/_angular.scss +2 −0
  89. assets/stylesheets/pages/_angularjs.scss +1 −1
  90. assets/stylesheets/pages/_async.scss +2 −2
  91. assets/stylesheets/pages/_bash.scss +12 −0
  92. assets/stylesheets/pages/_bootstrap.scss +1 −1
  93. assets/stylesheets/pages/_c.scss +4 −2
  94. assets/stylesheets/pages/_clojure.scss +1 −1
  95. assets/stylesheets/pages/_d.scss +3 −3
  96. assets/stylesheets/pages/_dart.scss +12 −0
  97. assets/stylesheets/pages/_dojo.scss +1 −1
  98. assets/stylesheets/pages/_ember.scss +2 −2
  99. assets/stylesheets/pages/_erlang.scss +1 −1
  100. assets/stylesheets/pages/_graphite.scss +7 −0
  101. assets/stylesheets/pages/_jekyll.scss +7 −0
  102. assets/stylesheets/pages/_jquery.scss +2 −2
  103. assets/stylesheets/pages/_love.scss +2 −2
  104. assets/stylesheets/pages/_mdn.scss +28 −3
  105. assets/stylesheets/pages/_meteor.scss +2 −2
  106. assets/stylesheets/pages/_node.scss +3 −0
  107. assets/stylesheets/pages/_npm.scss +1 −1
  108. assets/stylesheets/pages/_phalcon.scss +1 −1
  109. assets/stylesheets/pages/_php.scss +2 −2
  110. assets/stylesheets/pages/_postgres.scss +12 −7
  111. assets/stylesheets/pages/_python.scss +6 −0
  112. assets/stylesheets/pages/_qt.scss +9 −0
  113. assets/stylesheets/pages/_rdoc.scss +6 −13
  114. assets/stylesheets/pages/_react_native.scss +1 −1
  115. assets/stylesheets/pages/_redis.scss +2 −2
  116. assets/stylesheets/pages/_rfc.scss +1 −1
  117. assets/stylesheets/pages/_rust.scss +10 −1
  118. assets/stylesheets/pages/_simple.scss +1 −31
  119. assets/stylesheets/pages/_sphinx.scss +3 −2
  120. assets/stylesheets/pages/_sphinx_simple.scss +1 −1
  121. assets/stylesheets/pages/_support_tables.scss +1 −1
  122. assets/stylesheets/pages/_terraform.scss +4 −0
  123. assets/stylesheets/pages/_yard.scss +1 −1
  124. assets/stylesheets/pages/_yii.scss +1 −1
  125. docs/adding-docs.md +24 −0
  126. docs/filter-reference.md +224 −0
  127. docs/maintainers.md +102 −0
  128. docs/scraper-reference.md +227 −0
  129. lib/app.rb +51 −47
  130. lib/docs.rb +18 −1
  131. lib/docs/core/doc.rb +118 −0
  132. lib/docs/core/filter.rb +3 −12
  133. lib/docs/core/manifest.rb +4 −22
  134. lib/docs/core/requester.rb +8 −4
  135. lib/docs/core/scraper.rb +1 −1
  136. lib/docs/core/scrapers/file_scraper.rb +21 −7
  137. lib/docs/core/scrapers/url_scraper.rb +46 −1
  138. lib/docs/core/url.rb +1 −1
  139. lib/docs/filters/angular/clean_html.rb +9 −1
  140. lib/docs/filters/angular/entries.rb +2 −0
  141. lib/docs/filters/ansible/clean_html.rb +2 −2
  142. lib/docs/filters/ansible/entries.rb +27 −3
  143. lib/docs/filters/babel/clean_html.rb +38 −0
  144. lib/docs/filters/babel/entries.rb +40 −0
  145. lib/docs/filters/bash/clean_html.rb +74 −0
  146. lib/docs/filters/bash/entries.rb +56 −0
  147. lib/docs/filters/bluebird/clean_html.rb +36 −0
  148. lib/docs/filters/bluebird/entries.rb +48 −0
  149. lib/docs/filters/bootstrap/clean_html_v3.rb +1 −5
  150. lib/docs/filters/bootstrap/clean_html_v4.rb +3 −5
  151. lib/docs/filters/c/clean_html.rb +11 −2
  152. lib/docs/filters/cmake/entries.rb +1 −0
  153. lib/docs/filters/codeception/clean_html.rb +6 −0
  154. lib/docs/filters/codeception/entries.rb +1 −1
  155. lib/docs/filters/codeceptjs/entries.rb +2 −2
  156. lib/docs/filters/cordova/clean_html.rb +1 −1
  157. lib/docs/filters/core/parse_cf_email.rb +21 −0
  158. lib/docs/filters/cpp/entries.rb +2 −0
  159. lib/docs/filters/cpp/fix_urls.rb +0 −1
  160. lib/docs/filters/crystal/clean_html.rb +1 −1
  161. lib/docs/filters/crystal/entries.rb +1 −1
  162. lib/docs/filters/css/entries.rb +23 −12
  163. lib/docs/filters/d/entries.rb +2 −0
  164. lib/docs/filters/dart/clean_html.rb +66 −0
  165. lib/docs/filters/dart/entries.rb +42 −0
  166. lib/docs/filters/dom/clean_html.rb +1 −1
  167. lib/docs/filters/dom/entries.rb +28 −3
  168. lib/docs/filters/electron/clean_html.rb +10 −2
  169. lib/docs/filters/electron/entries.rb +8 −8
  170. lib/docs/filters/elixir/clean_html.rb +6 −0
  171. lib/docs/filters/eslint/clean_html.rb +28 −0
  172. lib/docs/filters/eslint/entries.rb +25 −0
  173. lib/docs/filters/express/clean_html.rb +5 −0
  174. lib/docs/filters/flow/clean_html.rb +0 −4
  175. lib/docs/filters/flow/entries.rb +1 −0
  176. lib/docs/filters/github/clean_html.rb +1 −1
  177. lib/docs/filters/go/clean_html.rb +1 −0
  178. lib/docs/filters/graphite/clean_html.rb +27 −0
  179. lib/docs/filters/graphite/entries.rb +46 −0
  180. lib/docs/filters/handlebars/clean_html.rb +44 −0
  181. lib/docs/filters/handlebars/entries.rb +19 −0
  182. lib/docs/filters/haskell/clean_html.rb +4 −0
  183. lib/docs/filters/haskell/entries.rb +2 −2
  184. lib/docs/filters/homebrew/clean_html.rb +24 −0
  185. lib/docs/filters/homebrew/entries.rb +34 −0
  186. lib/docs/filters/html/entries.rb +1 −1
  187. lib/docs/filters/javascript/clean_html.rb +2 −2
  188. lib/docs/filters/javascript/entries.rb +2 −2
  189. lib/docs/filters/jekyll/clean_html.rb +43 −0
  190. lib/docs/filters/jekyll/entries.rb +28 −0
  191. lib/docs/filters/jest/clean_html.rb +4 −3
  192. lib/docs/filters/jest/entries.rb +1 −1
  193. lib/docs/filters/jsdoc/clean_html.rb +26 −0
  194. lib/docs/filters/jsdoc/entries.rb +28 −0
  195. lib/docs/filters/koa/clean_html.rb +42 −0
  196. lib/docs/filters/koa/entries.rb +36 −0
  197. lib/docs/filters/kotlin/clean_html.rb +7 −0
  198. lib/docs/filters/kotlin/entries.rb +3 −1
  199. lib/docs/filters/laravel/clean_html.rb +0 −4
  200. lib/docs/filters/leaflet/clean_html.rb +40 −0
  201. lib/docs/filters/leaflet/entries.rb +33 −0
  202. lib/docs/filters/mdn/clean_html.rb +90 −0
  203. lib/docs/filters/nginx/clean_html.rb +4 −0
  204. lib/docs/filters/nginx/entries.rb +1 −1
  205. lib/docs/filters/node/clean_html.rb +1 −5
  206. lib/docs/filters/node/entries.rb +2 −0
  207. lib/docs/filters/npm/clean_html.rb +1 −1
  208. lib/docs/filters/npm/entries.rb +2 −2
  209. lib/docs/filters/pandas/entries.rb +5 −3
  210. lib/docs/filters/postgresql/clean_html.rb +34 −5
  211. lib/docs/filters/postgresql/entries.rb +23 −21
  212. lib/docs/filters/postgresql/extract_metadata.rb +10 −8
  213. lib/docs/filters/postgresql/normalize_class_names.rb +13 −0
  214. lib/docs/filters/puppeteer/clean_html.rb +24 −0
  215. lib/docs/filters/puppeteer/entries.rb +41 −0
  216. lib/docs/filters/pygame/clean_html.rb +102 −0
  217. lib/docs/filters/pygame/entries.rb +29 −0
  218. lib/docs/filters/python/clean_html.rb +5 −3
  219. lib/docs/filters/python/entries_v2.rb +1 −7
  220. lib/docs/filters/python/entries_v3.rb +15 −17
  221. lib/docs/filters/qt/clean_html.rb +32 −0
  222. lib/docs/filters/qt/entries.rb +142 −0
  223. lib/docs/filters/ramda/clean_html.rb +1 −1
  224. lib/docs/filters/rdoc/clean_html.rb +4 −2
  225. lib/docs/filters/react/entries.rb +7 −2
  226. lib/docs/filters/react_native/clean_html.rb +7 −2
  227. lib/docs/filters/react_native/entries.rb +9 −11
  228. lib/docs/filters/redis/entries.rb +2 −1
  229. lib/docs/filters/rust/clean_html.rb +39 −6
  230. lib/docs/filters/rust/entries.rb +7 −5
  231. lib/docs/filters/scikit_learn/entries.rb +1 −0
  232. lib/docs/filters/sqlite/entries.rb +2 −1
  233. lib/docs/filters/svg/entries.rb +2 −2
  234. lib/docs/filters/terraform/clean_html.rb +33 −0
  235. lib/docs/filters/terraform/entries.rb +76 −0
  236. lib/docs/filters/typescript/entries.rb +1 −0
  237. lib/docs/filters/vagrant/entries.rb +1 −0
  238. lib/docs/filters/vue/clean_html.rb +0 −4
  239. lib/docs/filters/webpack/entries.rb +2 −2
  240. lib/docs/filters/yarn/clean_html.rb +0 −4
  241. lib/docs/scrapers/angular.rb +73 −21
  242. lib/docs/scrapers/angularjs.rb +16 −3
  243. lib/docs/scrapers/ansible.rb +57 −13
  244. lib/docs/scrapers/apache.rb +7 −2
  245. lib/docs/scrapers/apache_pig.rb +6 −1
  246. lib/docs/scrapers/async.rb +7 −2
  247. lib/docs/scrapers/babel.rb +31 −0
  248. lib/docs/scrapers/backbone.rb +5 −0
  249. lib/docs/scrapers/bash.rb +26 −0
  250. lib/docs/scrapers/bluebird.rb +26 −0
  251. lib/docs/scrapers/bootstrap.rb +10 −4
  252. lib/docs/scrapers/bottle.rb +6 −0
  253. lib/docs/scrapers/bower.rb +7 −3
  254. lib/docs/scrapers/c.rb +14 −1
  255. lib/docs/scrapers/cakephp.rb +12 −2
  256. lib/docs/scrapers/chai.rb +5 −1
  257. lib/docs/scrapers/chef.rb +5 −0
  258. lib/docs/scrapers/clojure.rb +18 −3
  259. lib/docs/scrapers/cmake.rb +44 −8
  260. lib/docs/scrapers/codeception.rb +9 −4
  261. lib/docs/scrapers/codeceptjs.rb +8 −4
  262. lib/docs/scrapers/codeigniter.rb +8 −2
  263. lib/docs/scrapers/coffeescript.rb +6 −2
  264. lib/docs/scrapers/cordova.rb +17 −2
  265. lib/docs/scrapers/cpp.rb +15 −1
  266. lib/docs/scrapers/crystal.rb +7 −2
  267. lib/docs/scrapers/d.rb +7 −2
  268. lib/docs/scrapers/d3.rb +22 −2
  269. lib/docs/scrapers/dart.rb +41 −0
  270. lib/docs/scrapers/django.rb +21 −5
  271. lib/docs/scrapers/docker.rb +154 −27
  272. lib/docs/scrapers/dojo.rb +5 −0
  273. lib/docs/scrapers/drupal.rb +5 −0
  274. lib/docs/scrapers/electron.rb +14 −9
  275. lib/docs/scrapers/elixir.rb +59 −2
  276. lib/docs/scrapers/ember.rb +5 −0
  277. lib/docs/scrapers/erlang.rb +10 −4
  278. lib/docs/scrapers/eslint.rb +28 −0
  279. lib/docs/scrapers/express.rb +5 −1
  280. lib/docs/scrapers/falcon.rb +11 −1
  281. lib/docs/scrapers/fish.rb +17 −1
  282. lib/docs/scrapers/flow.rb +7 −3
  283. lib/docs/scrapers/git.rb +7 −2
  284. lib/docs/scrapers/gnu.rb +1 −1
  285. lib/docs/scrapers/gnu/gcc.rb +8 −10
  286. lib/docs/scrapers/gnu/gnu_fortran.rb +7 −5
  287. lib/docs/scrapers/go.rb +11 −1
  288. lib/docs/scrapers/godot.rb +12 −2
  289. lib/docs/scrapers/graphite.rb +26 −0
  290. lib/docs/scrapers/grunt.rb +5 −1
  291. lib/docs/scrapers/handlebars.rb +27 −0
  292. lib/docs/scrapers/haskell.rb +9 −2
  293. lib/docs/scrapers/haxe.rb +32 −14
  294. lib/docs/scrapers/homebrew.rb +27 −0
  295. lib/docs/scrapers/http.rb +3 −0
  296. lib/docs/scrapers/immutable.rb +5 −1
  297. lib/docs/scrapers/influxdata.rb +7 −1
  298. lib/docs/scrapers/jasmine.rb +7 −3
  299. lib/docs/scrapers/jekyll.rb +37 −0
  300. lib/docs/scrapers/jest.rb +9 −4
  301. lib/docs/scrapers/jquery/jquery_core.rb +9 −1
  302. lib/docs/scrapers/jquery/jquery_mobile.rb +5 −0
  303. lib/docs/scrapers/jquery/jquery_ui.rb +4 −0
  304. lib/docs/scrapers/jsdoc.rb +29 −0
  305. lib/docs/scrapers/julia.rb +28 −4
  306. lib/docs/scrapers/knockout.rb +4 −0
  307. lib/docs/scrapers/koa.rb +42 −0
  308. lib/docs/scrapers/kotlin.rb +15 −3
  309. lib/docs/scrapers/laravel.rb +31 −1
  310. lib/docs/scrapers/leaflet.rb +58 −0
  311. lib/docs/scrapers/less.rb +7 −1
  312. lib/docs/scrapers/liquid.rb +5 −0
  313. lib/docs/scrapers/lodash.rb +8 −3
  314. lib/docs/scrapers/love.rb +5 −0
  315. lib/docs/scrapers/lua.rb +5 −0
  316. lib/docs/scrapers/marionette.rb +13 −2
  317. lib/docs/scrapers/markdown.rb +5 −1
  318. lib/docs/scrapers/matplotlib.rb +48 −8
  319. lib/docs/scrapers/mdn/css.rb +2 −0
  320. lib/docs/scrapers/mdn/dom.rb +2 −0
  321. lib/docs/scrapers/mdn/dom_events.rb +4 −0
  322. lib/docs/scrapers/mdn/html.rb +2 −0
  323. lib/docs/scrapers/mdn/javascript.rb +2 −0
  324. lib/docs/scrapers/mdn/mdn.rb +7 −1
  325. lib/docs/scrapers/mdn/svg.rb +2 −0
  326. lib/docs/scrapers/mdn/xslt_xpath.rb +2 −0
  327. lib/docs/scrapers/meteor.rb +6 −1
  328. lib/docs/scrapers/mocha.rb +7 −3
  329. lib/docs/scrapers/modernizr.rb +4 −0
  330. lib/docs/scrapers/moment.rb +6 −1
  331. lib/docs/scrapers/mongoose.rb +9 −2
  332. lib/docs/scrapers/nginx.rb +10 −4
  333. lib/docs/scrapers/nginx_lua_module.rb +12 −3
  334. lib/docs/scrapers/nim.rb +8 −3
  335. lib/docs/scrapers/node.rb +18 −3
  336. lib/docs/scrapers/nokogiri2.rb +12 −4
  337. lib/docs/scrapers/npm.rb +5 −1
  338. lib/docs/scrapers/numpy.rb +9 −1
  339. lib/docs/scrapers/openjdk.rb +22 −3
  340. lib/docs/scrapers/opentsdb.rb +4 −0
  341. lib/docs/scrapers/padrino.rb +7 −3
  342. lib/docs/scrapers/pandas.rb +36 −4
  343. lib/docs/scrapers/perl.rb +6 −1
  344. lib/docs/scrapers/phalcon.rb +5 −0
  345. lib/docs/scrapers/phaser.rb +4 −0
  346. lib/docs/scrapers/phoenix.rb +6 −1
  347. lib/docs/scrapers/php.rb +13 −8
  348. lib/docs/scrapers/phpunit.rb +7 −1
  349. lib/docs/scrapers/postgresql.rb +21 −4
  350. lib/docs/scrapers/pug.rb +5 −1
  351. lib/docs/scrapers/puppeteer.rb +23 −0
  352. lib/docs/scrapers/pygame.rb +25 −0
  353. lib/docs/scrapers/python.rb +20 −11
  354. lib/docs/scrapers/q.rb +5 −1
  355. lib/docs/scrapers/qt.rb +136 −0
  356. lib/docs/scrapers/ramda.rb +7 −3
  357. lib/docs/scrapers/rdoc/minitest.rb +12 −2
  358. lib/docs/scrapers/rdoc/rails.rb +21 −6
  359. lib/docs/scrapers/rdoc/ruby.rb +22 −17
  360. lib/docs/scrapers/react.rb +7 −2
  361. lib/docs/scrapers/react_native.rb +8 −3
  362. lib/docs/scrapers/redis.rb +8 −2
  363. lib/docs/scrapers/redux.rb +5 −1
  364. lib/docs/scrapers/relay.rb +5 −0
  365. lib/docs/scrapers/requirejs.rb +5 −1
  366. lib/docs/scrapers/rethinkdb.rb +4 −0
  367. lib/docs/scrapers/rust.rb +14 −5
  368. lib/docs/scrapers/sass.rb +5 −1
  369. lib/docs/scrapers/scikit_image.rb +7 −2
  370. lib/docs/scrapers/scikit_learn.rb +7 −2
  371. lib/docs/scrapers/sinon.rb +27 −7
  372. lib/docs/scrapers/socketio.rb +4 −0
  373. lib/docs/scrapers/sqlite.rb +6 −2
  374. lib/docs/scrapers/statsmodels.rb +5 −1
  375. lib/docs/scrapers/support_tables.rb +6 −0
  376. lib/docs/scrapers/symfony.rb +28 −9
  377. lib/docs/scrapers/tcl_tk.rb +5 −0
  378. lib/docs/scrapers/tensorflow.rb +6 −2
  379. lib/docs/scrapers/terraform.rb +27 −0
  380. lib/docs/scrapers/twig.rb +11 −6
  381. lib/docs/scrapers/typescript.rb +6 −2
  382. lib/docs/scrapers/underscore.rb +12 −3
  383. lib/docs/scrapers/vagrant.rb +8 −3
  384. lib/docs/scrapers/vue.rb +6 −2
  385. lib/docs/scrapers/vulkan.rb +6 −1
  386. lib/docs/scrapers/webpack.rb +5 −1
  387. lib/docs/scrapers/yarn.rb +7 −3
  388. lib/docs/scrapers/yii.rb +4 −0
  389. lib/docs/subscribers/doc_subscriber.rb +4 −0
  390. lib/docs/subscribers/requester_subscriber.rb +13 −0
  391. lib/tasks/assets.thor +3 −0
  392. lib/tasks/docs.thor +117 −15
  393. lib/tasks/sprites.thor +213 −0
  394. lib/tasks/updates.thor +315 −0
  395. newrelic.yml +221 −0
  396. public/docs/docs.json +0 −1
  397. public/favicon.pxm +0 −0
  398. public/favicon@2x.pxm +0 −0
  399. public/icons/docs/babel/16.png +0 −0
  400. public/icons/docs/babel/16@2x.png +0 −0
  401. public/icons/docs/babel/SOURCE +1 −0
  402. public/icons/docs/bash/16.png +0 −0
  403. public/icons/docs/bash/16@2x.png +0 −0
  404. public/icons/docs/bash/SOURCE +1 −0
  405. public/icons/docs/bluebird/16.png +0 −0
  406. public/icons/docs/bluebird/16@2x.png +0 −0
  407. public/icons/docs/bluebird/SOURCE +1 −0
  408. public/icons/docs/css/CSS3.sketch/Data +0 −0
  409. public/icons/docs/css/CSS3.sketch/QuickLook/Preview.png +0 −0
  410. public/icons/docs/css/CSS3.sketch/QuickLook/Thumbnail.png +0 −0
  411. public/icons/docs/css/CSS3.sketch/fonts +0 −0
  412. public/icons/docs/css/CSS3.sketch/version +1 −0
  413. public/icons/docs/dart/16.png +0 −0
  414. public/icons/docs/dart/16@2x.png +0 −0
  415. public/icons/docs/dart/SOURCE +1 −0
  416. public/icons/docs/dom/DOM.sketch/Data +0 −0
  417. public/icons/docs/dom/DOM.sketch/QuickLook/Preview.png +0 −0
  418. public/icons/docs/dom/DOM.sketch/QuickLook/Thumbnail.png +0 −0
  419. public/icons/docs/dom/DOM.sketch/fonts +0 −0
  420. public/icons/docs/dom/DOM.sketch/version +1 −0
  421. public/icons/docs/dom_events/DOM_events.sketch/Data +0 −0
  422. public/icons/docs/dom_events/DOM_events.sketch/QuickLook/Preview.png +0 −0
  423. public/icons/docs/dom_events/DOM_events.sketch/QuickLook/Thumbnail.png +0 −0
  424. public/icons/docs/dom_events/DOM_events.sketch/fonts +0 −0
  425. public/icons/docs/dom_events/DOM_events.sketch/version +1 −0
  426. public/icons/docs/eslint/16.png +0 −0
  427. public/icons/docs/eslint/16@2x.png +0 −0
  428. public/icons/docs/eslint/SOURCE +1 −0
  429. public/icons/docs/express/16.pxm +0 −0
  430. public/icons/docs/express/16@2x.pxm +0 −0
  431. public/icons/docs/falcon/16.png +0 −0
  432. public/icons/docs/falcon/16@2x.png +0 −0
  433. public/icons/docs/falcon/SOURCE +1 −1
  434. public/icons/docs/handlebars/16.png +0 −0
  435. public/icons/docs/handlebars/16@2x.png +0 −0
  436. public/icons/docs/handlebars/SOURCE +1 −0
  437. public/icons/docs/homebrew/16.png +0 −0
  438. public/icons/docs/homebrew/16@2x.png +0 −0
  439. public/icons/docs/homebrew/SOURCE +1 −0
  440. public/icons/docs/html/HTML5.sketch/Data +0 −0
  441. public/icons/docs/html/HTML5.sketch/QuickLook/Preview.png +0 −0
  442. public/icons/docs/html/HTML5.sketch/QuickLook/Thumbnail.png +0 −0
  443. public/icons/docs/html/HTML5.sketch/fonts +0 −0
  444. public/icons/docs/html/HTML5.sketch/version +1 −0
  445. public/icons/docs/http/16.pxm +0 −0
  446. public/icons/docs/http/16@2x.pxm +0 −0
  447. public/icons/docs/javascript/16.pxm +0 −0
  448. public/icons/docs/javascript/16@2x.pxm +0 −0
  449. public/icons/docs/jekyll/16.png +0 −0
  450. public/icons/docs/jekyll/16@2x.png +0 −0
  451. public/icons/docs/jekyll/SOURCE +1 −0
  452. public/icons/docs/knockout/16@2x.pxm +0 −0
  453. public/icons/docs/leaflet/16.png +0 −0
  454. public/icons/docs/leaflet/16@2x.png +0 −0
  455. public/icons/docs/leaflet/SOURCE +1 −0
  456. public/icons/docs/less/less.pxm +0 −0
  457. public/icons/docs/mocha/16.png +0 −0
  458. public/icons/docs/mocha/16@2x.png +0 −0
  459. public/icons/docs/moment/moment.sketch/Data +0 −0
  460. public/icons/docs/moment/moment.sketch/QuickLook/Preview.png +0 −0
  461. public/icons/docs/moment/moment.sketch/QuickLook/Thumbnail.png +0 −0
  462. public/icons/docs/moment/moment.sketch/fonts +0 −0
  463. public/icons/docs/moment/moment.sketch/metadata +14 −0
  464. public/icons/docs/moment/moment.sketch/version +1 −0
  465. public/icons/docs/nokogiri/icon.pxm +0 −0
  466. public/icons/docs/php/16@2x.pxm +0 −0
  467. public/icons/docs/puppeteer/16.png +0 −0
  468. public/icons/docs/puppeteer/16@2x.png +0 −0
  469. public/icons/docs/puppeteer/SOURCE +1 −0
  470. public/icons/docs/pygame/16.png +0 −0
  471. public/icons/docs/pygame/16@2x.png +0 −0
  472. public/icons/docs/qt/16.png +0 −0
  473. public/icons/docs/qt/16@2x.png +0 −0
  474. public/icons/docs/qt/SOURCE +1 −0
  475. public/icons/docs/rails/rails.pxm +0 −0
  476. public/icons/docs/sass/sass.pxm +0 −0
  477. public/icons/docs/support_tables/16.pxm +0 −0
  478. public/icons/docs/support_tables/16@2x.pxm +0 −0
  479. public/icons/docs/terraform/16.png +0 −0
  480. public/icons/docs/terraform/16@2x.png +0 −0
  481. public/icons/docs/xpath/XPath.sketch/Data +0 −0
  482. public/icons/docs/xpath/XPath.sketch/QuickLook/Preview.png +0 −0
  483. public/icons/docs/xpath/XPath.sketch/QuickLook/Thumbnail.png +0 −0
  484. public/icons/docs/xpath/XPath.sketch/metadata +18 −0
  485. public/icons/docs/xpath/XPath.sketch/version +1 −0
  486. public/images/apple-icon.pxm +0 −0
  487. public/images/fluid-icon.pxm +0 −0
  488. public/images/icon.pxm +0 −0
  489. public/images/webapp-icon-192.png +0 −0
  490. public/images/webapp-icon-512.png +0 −0
  491. public/images/webapp-icon-small.pxm +0 −0
  492. public/manifest.json +11 −0
  493. public/opensearch.xml +3 −3
  494. test/app_test.rb +36 −90
  495. test/lib/docs/core/doc_test.rb +21 −0
  496. test/lib/docs/core/manifest_test.rb +11 −40
  497. test/lib/docs/core/scrapers/file_scraper_test.rb +90 −48
  498. views/app.erb +1 −13
  499. views/index.erb +4 −3
  500. views/manifest.erb +0 −14
  501. views/other.erb +3 −2
  502. views/service-worker.js.erb +61 −0
  503. views/unsupported.erb +7 −10
.editorconfig+11−0
@@ -0,0 +1,11 @@
1+root = true
2+
3+[*]
4+charset = utf-8
5+indent_style = space
6+indent_size = 2
7+insert_final_newline = true
8+trim_trailing_whitespace = true
9+
10+[*.md]
11+trim_trailing_whitespace = false
.github/CONTRIBUTING.mdrenamed from CONTRIBUTING.md+6−16
@@ -24,20 +24,18 @@ Want to contribute? Great. Please review the following guidelines carefully and · 23 unmodified lines
2424 ## Requesting new features
2525
2626 1. Search for similar feature requests; someone may have already requested it.
27−2. Make sure your feature fits DevDocs's [vision](https://github.com/Thibaut/devdocs/blob/master/README.md#vision).
27+2. Make sure your feature fits DevDocs's [vision](../README.md#vision).
2828 3. Provide a clear and detailed explanation of the feature and why it's important to add it.
2929
30−For general feedback and ideas, please use the [mailing list](https://groups.google.com/d/forum/devdocs).
31−
3230 ## Requesting new documentations
3331
34−Please don't open issues to request new documentations.
32+Please don't open issues to request new documentations.
3533 Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) where everyone can vote.
3634
3735 ## Contributing code and features
3836
3937 1. Search for existing issues; someone may already be working on a similar feature.
40−2. 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.
38+2. 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.
4139 3. Follow the [coding conventions](#coding-conventions).
4240 4. If you're modifying the Ruby code, include tests and ensure they pass.
4341 5. 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
4644
4745 ## Contributing new documentations
4846
49−See the [wiki](https://github.com/Thibaut/devdocs/wiki) to learn how to add new documentations.
47+See the [`docs` folder](https://github.com/freeCodeCamp/devdocs/tree/master/docs) to learn how to add new documentations.
5048
5149 **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.
5250
@@ -57,20 +55,12 @@ In addition to the [guidelines for contributing code](#contributing-code-and-fea · 4 unmodified lines
5755 * Remove as much content and HTML markup as possible, particularly content not associated with any entry (e.g. introduction, changelog, etc.).
5856 * Names must be as short as possible and unique across the documentation.
5957 * 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.
6158
6259 ## Updating existing documentations
6360
6461 Please don't submit a pull request updating the version number of a documentation, unless a change is required in the scraper and you've verified that it works.
6562
66−To 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−
70−Besides new docs and features, here are other ways you can contribute:
71−
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.
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).
63+To ask that an existing documentation be updated, first check the last two [documentation versions reports](https://github.com/freeCodeCamp/devdocs/issues?utf8=%E2%9C%93&q=Documentation+versions+report+is%3Aissue+author%3Adevdocs-bot+sort%3Acreated-desc). Only create an issue if the documentation has been wrongly marked as up-to-date.
7464
7565 ## Coding conventions
7666
@@ -80,4 +70,4 @@ Besides new docs and features, here are other ways you can contribute: · 3 unmodified lines
8070
8171 ## Questions?
8272
83−If you have any questions, please feel free to ask on the [mailing list](https://groups.google.com/d/forum/devdocs).
73+If 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 @@
1+daysUntilClose: 30
2+responseRequiredLabel: needs-info
3+closeComment: >
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 @@
11 .DS_Store
22 .bundle
3−*.pxm
4−*.sketch
3+log
54 tmp
65 public/assets
76 public/fonts
87 public/docs/**/*
9−!public/docs/docs.json
10−!public/docs/**/index.json
8+docs/**/*
9+!docs/*.md
.ruby-version+1−1
@@ -1 +1 @@
1−2.4.2
1+2.6.3
.slugignore+1−0
@@ -0,0 +1 @@
1+test
.travis.yml+25−0
@@ -1 +1,26 @@
11 language: ruby
2+
3+addons:
4+ apt:
5+ packages:
6+ - libcurl4-openssl-dev
7+
8+cache: bundler
9+
10+before_install:
11+ - "echo 'gem: --no-document' > ~/.gemrc"
12+ - gem update --system
13+ - gem install bundler
14+
15+script:
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+
19+deploy:
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 @@
1−Copyright 2013-2017 Thibaut Courouble and other contributors
1+Copyright 2013-2019 Thibaut Courouble and other contributors
22
33 This Source Code Form is subject to the terms of the Mozilla Public
44 License, v. 2.0. If a copy of the MPL was not distributed with this
55 file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
77 Please do not use the name DevDocs to endorse or promote products
8−derived from this software without my permission, except as may be
9−necessary to comply with the notice/attribution requirements.
8+derived from this software without the maintainers' permission, except
9+as may be necessary to comply with the notice/attribution requirements.
1010
11−I also wish that any documentation file generated using this software
11+We also wish that any documentation file generated using this software
1212 be attributed to DevDocs. Let's be fair to all contributors by giving
1313 credit where credit's due. Thanks.
Dockerfile+5−2
@@ -1,9 +1,12 @@
1−FROM ruby:2.4.2
1+FROM ruby:2.6.3
2+
3+ENV LANG=C.UTF-8
4+ENV ENABLE_SERVICE_WORKER=true
25
36 WORKDIR /devdocs
47
58 RUN apt-get update && \
6− apt-get -y install git nodejs && \
9+ apt-get -y install git nodejs libcurl4 && \
710 gem install bundler && \
811 rm -rf /var/lib/apt/lists/*
912
Dockerfile-alpine+5−2
@@ -1,10 +1,13 @@
1−FROM ruby:2.4.2-alpine
1+FROM ruby:2.6.3-alpine
2+
3+ENV LANG=C.UTF-8
4+ENV ENABLE_SERVICE_WORKER=true
25
36 WORKDIR /devdocs
47
58 COPY . /devdocs
69
7−RUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev && \
10+RUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev libcurl && \
811 gem install bundler && \
912 bundle install --system --without test && \
1013 thor docs:download --all && \
Gemfile+12−6
@@ -1,16 +1,20 @@
11 source 'https://rubygems.org'
2−ruby '2.4.2'
2+ruby '2.6.3'
33
44 gem 'rake'
55 gem 'thor'
6−gem 'pry', '~> 0.11.0'
7−gem 'activesupport', '~> 5.1', require: false
6+gem 'pry', '~> 0.12.0'
7+gem 'activesupport', '~> 5.2', require: false
88 gem 'yajl-ruby', require: false
9+gem 'html-pipeline'
10+gem 'typhoeus'
11+gem 'nokogiri'
912
1013 group :app do
1114 gem 'rack'
1215 gem 'sinatra'
1316 gem 'sinatra-contrib'
17+ gem 'rack-ssl-enforcer'
1418 gem 'thin'
1519 gem 'sprockets'
1620 gem 'sprockets-helpers'
@@ -18,10 +22,13 @@ group :app do · 1 unmodified line
1822 gem 'browser'
1923 gem 'sass'
2024 gem 'coffee-script'
25+ gem 'chunky_png'
26+ gem 'sprockets-sass'
2127 end
2228
2329 group :production do
2430 gem 'uglifier'
31+ gem 'newrelic_rpm'
2532 end
2633
2734 group :development do
@@ -29,14 +36,13 @@ group :development do · 1 unmodified line
2936 end
3037
3138 group :docs do
32− gem 'typhoeus'
33− gem 'nokogiri'
34− gem 'html-pipeline'
3539 gem 'image_optim'
3640 gem 'image_optim_pack', platforms: :ruby
3741 gem 'progress_bar', require: false
3842 gem 'unix_utils', require: false
3943 gem 'tty-pager', require: false
44+ gem 'net-sftp', '>= 2.1.3.rc2', require: false
45+ gem 'terminal-table', require: false
4046 end
4147
4248 group :test do
Gemfile.lock+86−67
@@ -1,139 +1,156 @@
11 GEM
22 remote: https://rubygems.org/
33 specs:
4− activesupport (5.1.4)
4+ activesupport (5.2.3)
55 concurrent-ruby (~> 1.0, >= 1.0.2)
6− i18n (~> 0.7)
6+ i18n (>= 0.7, < 2)
77 minitest (~> 5.1)
88 tzinfo (~> 1.1)
9− backports (3.9.1)
10− better_errors (2.3.0)
9+ backports (3.15.0)
10+ better_errors (2.5.1)
1111 coderay (>= 1.0.0)
1212 erubi (>= 1.0.0)
1313 rack (>= 0.9.0)
14− browser (2.5.1)
14+ browser (2.6.1)
15+ chunky_png (1.3.11)
1516 coderay (1.1.2)
1617 coffee-script (2.4.1)
1718 coffee-script-source
1819 execjs
1920 coffee-script-source (1.12.2)
20− concurrent-ruby (1.0.5)
21− daemons (1.2.4)
22− erubi (1.6.1)
23− ethon (0.10.1)
21+ concurrent-ruby (1.1.5)
22+ daemons (1.3.1)
23+ erubi (1.8.0)
24+ ethon (0.12.0)
2425 ffi (>= 1.3.0)
25− eventmachine (1.2.5)
26+ eventmachine (1.2.7)
2627 execjs (2.7.0)
27− exifr (1.3.2)
28− ffi (1.9.18)
29− fspath (3.1.0)
30− highline (1.7.8)
31− html-pipeline (2.7.1)
28+ exifr (1.3.6)
29+ ffi (1.11.1)
30+ fspath (3.1.2)
31+ highline (2.0.2)
32+ html-pipeline (2.11.1)
3233 activesupport (>= 2)
3334 nokogiri (>= 1.4)
34− i18n (0.8.6)
35− image_optim (0.25.0)
35+ i18n (1.6.0)
36+ concurrent-ruby (~> 1.0)
37+ image_optim (0.26.5)
3638 exifr (~> 1.2, >= 1.2.2)
3739 fspath (~> 3.0)
38− image_size (~> 1.5)
40+ image_size (>= 1.5, < 3)
3941 in_threads (~> 1.3)
4042 progress (~> 3.0, >= 3.0.1)
41− image_optim_pack (0.5.0.20171001)
43+ image_optim_pack (0.5.6)
4244 fspath (>= 2.1, < 4)
4345 image_optim (~> 0.19)
44− image_size (1.5.0)
45− in_threads (1.4.0)
46− method_source (0.9.0)
47− mini_portile2 (2.3.0)
48− minitest (5.10.3)
49− multi_json (1.12.2)
50− mustermann (1.0.1)
51− nokogiri (1.8.1)
52− mini_portile2 (~> 2.3.0)
46+ image_size (2.0.2)
47+ in_threads (1.5.3)
48+ method_source (0.9.2)
49+ mini_portile2 (2.4.0)
50+ minitest (5.11.3)
51+ multi_json (1.13.1)
52+ mustermann (1.0.3)
53+ net-sftp (3.0.0.beta1)
54+ net-ssh (>= 5.0.0, < 6.0.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)
5359 options (2.3.2)
54− progress (3.3.1)
55− progress_bar (1.1.0)
56− highline (~> 1.6)
60+ progress (3.5.2)
61+ progress_bar (1.3.0)
62+ highline (>= 1.6, < 3)
5763 options (~> 2.3.0)
58− pry (0.11.1)
64+ pry (0.12.2)
5965 coderay (~> 1.1.0)
6066 method_source (~> 0.9.0)
61− rack (2.0.3)
62− rack-protection (2.0.0)
67+ rack (2.0.7)
68+ rack-protection (2.0.5)
6369 rack
64− rack-test (0.7.0)
70+ rack-ssl-enforcer (0.2.9)
71+ rack-test (1.1.0)
6572 rack (>= 1.0, < 3)
66− rake (12.1.0)
67− rb-fsevent (0.10.2)
68− rb-inotify (0.9.10)
69− ffi (>= 0.5.0, < 2)
73+ rake (12.3.2)
74+ rb-fsevent (0.10.3)
75+ rb-inotify (0.10.0)
76+ ffi (~> 1.0)
7077 rr (1.2.1)
71− sass (3.5.2)
78+ sass (3.7.4)
7279 sass-listen (~> 4.0.0)
7380 sass-listen (4.0.0)
7481 rb-fsevent (~> 0.9, >= 0.9.4)
7582 rb-inotify (~> 0.9, >= 0.9.7)
76− sinatra (2.0.0)
83+ sinatra (2.0.5)
7784 mustermann (~> 1.0)
7885 rack (~> 2.0)
79− rack-protection (= 2.0.0)
86+ rack-protection (= 2.0.5)
8087 tilt (~> 2.0)
81− sinatra-contrib (2.0.0)
82− backports (>= 2.0)
88+ sinatra-contrib (2.0.5)
89+ backports (>= 2.8.2)
8390 multi_json
8491 mustermann (~> 1.0)
85− rack-protection (= 2.0.0)
86− sinatra (= 2.0.0)
92+ rack-protection (= 2.0.5)
93+ sinatra (= 2.0.5)
8794 tilt (>= 1.3, < 3)
88− sprockets (3.7.1)
95+ sprockets (3.7.2)
8996 concurrent-ruby (~> 1.0)
9097 rack (> 1, < 3)
9198 sprockets-helpers (1.2.1)
9299 sprockets (>= 2.2)
100+ sprockets-sass (2.0.0.beta2)
101+ sprockets (>= 2.0, < 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)
93109 thin (1.7.2)
94110 daemons (~> 1.0, >= 1.0.9)
95111 eventmachine (~> 1.0, >= 1.0.4)
96112 rack (>= 1, < 3)
97− thor (0.20.0)
113+ thor (0.20.3)
98114 thread_safe (0.3.6)
99− tilt (2.0.8)
100− tty-pager (0.9.0)
101− tty-screen (~> 0.5.0)
102− tty-which (~> 0.3.0)
103− verse (~> 0.5.0)
104− tty-screen (0.5.0)
105− tty-which (0.3.0)
106− typhoeus (1.3.0)
115+ tilt (2.0.9)
116+ tty-pager (0.12.1)
117+ strings (~> 0.1.4)
118+ tty-screen (~> 0.6)
119+ tty-which (~> 0.4)
120+ tty-screen (0.7.0)
121+ tty-which (0.4.1)
122+ typhoeus (1.3.1)
107123 ethon (>= 0.9.0)
108− tzinfo (1.2.3)
124+ tzinfo (1.2.5)
109125 thread_safe (~> 0.1)
110− uglifier (3.2.0)
126+ uglifier (4.1.20)
111127 execjs (>= 0.3.0, < 3)
112− unicode-display_width (1.1.3)
128+ unicode-display_width (1.6.0)
113129 unicode_utils (1.4.0)
114130 unix_utils (0.0.15)
115− verse (0.5.0)
116− unicode-display_width (~> 1.1.0)
117− unicode_utils (~> 1.4.0)
118− yajl-ruby (1.3.0)
131+ yajl-ruby (1.4.1)
119132
120133 PLATFORMS
121134 ruby
122135
123136 DEPENDENCIES
124− activesupport (~> 5.1)
137+ activesupport (~> 5.2)
125138 better_errors
126139 browser
140+ chunky_png
127141 coffee-script
128142 erubi
129143 html-pipeline
130144 image_optim
131145 image_optim_pack
132146 minitest
147+ net-sftp (>= 2.1.3.rc2)
148+ newrelic_rpm
133149 nokogiri
134150 progress_bar
135− pry (~> 0.11.0)
151+ pry (~> 0.12.0)
136152 rack
153+ rack-ssl-enforcer
137154 rack-test
138155 rake
139156 rr
@@ -142,6 +159,8 @@ DEPENDENCIES · 2 unmodified lines
142159 sinatra-contrib
143160 sprockets
144161 sprockets-helpers
162+ sprockets-sass
163+ terminal-table
145164 thin
146165 thor
147166 tty-pager
@@ -151,7 +170,7 @@ DEPENDENCIES · 3 unmodified lines
151170 yajl-ruby
152171
153172 RUBY VERSION
154− ruby 2.4.2p198
173+ ruby 2.6.3p62
155174
156175 BUNDLED WITH
157− 1.15.4
176+ 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/Thibaut/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 @@
1+If 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. -->
Procfile+1−0
@@ -0,0 +1 @@
1+web: bundle exec rackup config.ru -p $PORT
README.md+51−31
@@ -1,29 +1,27 @@
1−# [DevDocs](http://devdocs.io) [![Build Status](https://travis-ci.org/Thibaut/devdocs.svg?branch=master)](https://travis-ci.org/Thibaut/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)
22
3−DevDocs combines multiple API documentations in a fast, organized, and searchable interface.
3+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.
44
5−* Created by [Thibaut Courouble](https://thibaut.me)
5+DevDocs was created by [Thibaut Courouble](https://thibaut.me) and is operated by [freeCodeCamp](https://www.freecodecamp.org).
66
77 Keep track of development news:
88
9−* Watch the repository on [GitHub](https://github.com/Thibaut/devdocs/subscription)
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)
1011 * Follow [@DevDocs](https://twitter.com/DevDocs) on Twitter
11−* Join the [mailing list](https://groups.google.com/d/forum/devdocs)
1212
13−DevDocs is free and open source. If you like it, please consider supporting my work on [Gratipay](https://gratipay.com/devdocs/). Thanks!
14−
15−**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)
1614
1715 ## Quick Start
1816
19−Unless you wish to contribute to the project, I recommend using the hosted version at [devdocs.io](http://devdocs.io). It's up-to-date and works offline out-of-the-box.
17+Unless 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.
2018
2119 DevDocs is made of two pieces: a Ruby scraper that generates the documentation and metadata, and a JavaScript app powered by a small Sinatra app.
2220
23−DevDocs requires Ruby 2.4.2, 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:
21+DevDocs 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:
2422
2523 ```
26−git clone https://github.com/Thibaut/devdocs.git && cd devdocs
24+git clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs
2725 gem install bundler
2826 bundle install
2927 bundle exec thor docs:download --default
@@ -34,13 +32,13 @@ Finally, point your browser at [localhost:9292](http://localhost:9292) (the firs · 4 unmodified lines
3432
3533 The `thor docs:download` command is used to download pre-generated documentations from DevDocs's servers (e.g. `thor docs:download html css`). You can see the list of available documentations and versions by running `thor docs:list`. To update all downloaded documentations, run `thor docs:download --installed`.
3634
37−**Note:** there is currently no update mechanism other than `git pull origin master` to update the code and `thor docs:download --installed` to download the latest version of the docs. To stay informed about new releases, be sure to [watch](https://github.com/Thibaut/devdocs/subscription) this repository.
35+**Note:** there is currently no update mechanism other than `git pull origin master` to update the code and `thor docs:download --installed` to download the latest version of the docs. To stay informed about new releases, be sure to [watch](https://github.com/freeCodeCamp/devdocs/subscription) this repository.
3836
3937 Alternatively, DevDocs may be started as a Docker container:
4038
4139 ```
4240 # First, build the image
43−git clone https://github.com/Thibaut/devdocs.git && cd devdocs
41+git clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs
4442 docker build -t thibaut/devdocs .
4543
4644 # Finally, start a DevDocs container (access http://localhost:9292)
@@ -61,18 +59,14 @@ The web app is all client-side JavaScript, written in [CoffeeScript](http://coff · 14 unmodified lines
6159
6260 Many of the code's design decisions were driven by the fact that the app uses XHR to load content directly into the main frame. This includes stripping the original documents of most of their HTML markup (e.g. scripts and stylesheets) to avoid polluting the main frame, and prefixing all CSS class names with an underscore to prevent conflicts.
6361
64−Another driving factor is performance and the fact that everything happens in the browser. `applicationCache` (which comes with its own set of constraints) and `localStorage` are used to speed up the boot time, while memory consumption is kept in check by allowing the user to pick his/her own set of documentations. The search algorithm is kept simple because it needs to be fast even searching through 100,000 strings.
62+Another driving factor is performance and the fact that everything happens in the browser. A service worker (which comes with its own set of constraints) and `localStorage` are used to speed up the boot time, while memory consumption is kept in check by allowing the user to pick his/her own set of documentations. The search algorithm is kept simple because it needs to be fast even searching through 100,000 strings.
6563
6664 DevDocs being a developer tool, the browser requirements are high:
6765
68−1. On the desktop:
69− * Recent version of Chrome, Firefox, or Opera
70− * Safari 8+
71− * IE / Edge 10+
72−2. On mobile:
73− * iOS 8+
74− * Android 4.1+
75− * Windows Phone 8+
66+* Recent versions of Firefox, Chrome, or Opera
67+* Safari 11.1+
68+* Edge 17+
69+* iOS 11.3+
7670
7771 This allows the code to take advantage of the latest DOM and HTML5 APIs and make developing DevDocs a lot more fun!
7872
@@ -89,12 +83,13 @@ Modifications made to each document include: · 10 unmodified lines
8983 * replacing all external (not scraped) URLs with their fully qualified counterpart
9084 * replacing all internal (scraped) URLs with their unqualified and relative counterpart
9185 * adding content, such as a title and link to the original document
86+* ensuring correct syntax highlighting using [Prism](http://prismjs.com/)
9287
9388 These 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.
9489
9590 The end result is a set of normalized HTML partials and two JSON files (index + offline data). Because the index files are loaded separately by the [app](#app) following the user's preferences, the scraper also creates a JSON manifest file containing information about the documentations currently available on the system (such as their name, version, update date, etc.).
9691
97−More information about scrapers and filters is available on the [wiki](https://github.com/Thibaut/devdocs/wiki).
92+More information about [scrapers](./docs/scraper-reference.md) and [filters](./docs/filter-reference.md) is available in the `docs` folder.
9893
9994 ## Available Commands
10095
@@ -134,20 +129,45 @@ If multiple versions of Ruby are installed on your system, commands must be run · 33 unmodified lines
134129
135130 ## Contributing
136131
137−Contributions are welcome. Please read the [contributing guidelines](https://github.com/Thibaut/devdocs/blob/master/CONTRIBUTING.md).
138−
139−DevDocs's own documentation is available on the [wiki](https://github.com/Thibaut/devdocs/wiki).
132+Contributions are welcome. Please read the [contributing guidelines](./.github/CONTRIBUTING.md).
133+
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
140160
141161 ## Copyright / License
142162
143−Copyright 2013-2017 Thibaut Courouble and [other contributors](https://github.com/Thibaut/devdocs/graphs/contributors)
163+Copyright 2013-2019 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors)
144164
145−This software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](https://github.com/Thibaut/devdocs/blob/master/COPYRIGHT) and [LICENSE](https://github.com/Thibaut/devdocs/blob/master/LICENSE) files.
165+This software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](./COPYRIGHT) and [LICENSE](./LICENSE) files.
146166
147−Please 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.
167+Please do not use the name DevDocs to endorse or promote products derived from this software without the maintainers' permission, except as may be necessary to comply with the notice/attribution requirements.
148168
149−I 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!
169+We 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!
150170
151171 ## Questions?
152172
153−If you have any questions, please feel free to ask them on the [mailing list](https://groups.google.com/d/forum/devdocs).
173+If 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
33 require 'bundler/setup'
44 require 'thor'
55
6+Bundler.require :default
67 $LOAD_PATH.unshift 'lib'
78
89 task :default do
@@ -13,6 +14,9 @@ end · 4 unmodified lines
1314 namespace :assets do
1415 desc 'Compile all assets'
1516 task :precompile do
17+ load 'tasks/docs.thor'
18+ DocsCLI.new.prepare_deploy
19+
1620 load 'tasks/assets.thor'
1721 AssetsCLI.new.compile
1822 end
assets/images/.gitignore+1−0
@@ -0,0 +1 @@
1+sprites/**/*
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
1313
1414 @el = $('._app')
1515 @localStorage = new LocalStorageStore
16− @appCache = new app.AppCache if app.AppCache.isEnabled()
16+ @serviceWorker = new app.ServiceWorker if app.ServiceWorker.isEnabled()
1717 @settings = new app.Settings
1818 @db = new app.DB()
1919
20+ @settings.initLayout()
21+
2022 @docs = new app.collections.Docs
2123 @disabledDocs = new app.collections.Docs
2224 @entries = new app.collections.Entries
@@ -76,7 +78,7 @@ · 53 unmodified lines
7678 .install()
7779 @previousErrorHandler = onerror
7880 window.onerror = @onWindowError.bind(@)
79− CookieStore.onBlocked = @onCookieBlocked
81+ CookiesStore.onBlocked = @onCookieBlocked
8082 return
8183
8284 bootOne: ->
@@ -138,7 +140,10 @@ · 55 unmodified lines
138140 @docs.sort()
139141 @initDoc(doc)
140142 @saveDocs()
141− _onSuccess()
143+ if app.settings.get('autoInstall')
144+ doc.install(_onSuccess, onError)
145+ else
146+ _onSuccess()
142147 return
143148
144149 doc.load onSuccess, onError, writeCache: true
@@ -147,7 +152,7 @@ · 2 unmodified lines
147152 saveDocs: ->
148153 @settings.setDocs(doc.slug for doc in @docs.all())
149154 @db.migrate()
150− @appCache?.updateInBackground()
155+ @serviceWorker?.updateInBackground()
151156
152157 welcomeBack: ->
153158 visitCount = @settings.get('count')
@@ -157,17 +162,24 @@ · 3 unmodified lines
157162 new app.views.Updates()
158163 @updateChecker = new app.UpdateChecker()
159164
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+
160172 reload: ->
161173 @docs.clearCache()
162174 @disabledDocs.clearCache()
163− if @appCache then @appCache.reload() else window.location = '/'
175+ if @serviceWorker then @serviceWorker.reload() else @reboot()
164176 return
165177
166178 reset: ->
167179 @localStorage.reset()
168180 @settings.reset()
169181 @db?.reset()
170− @appCache?.update()
182+ @serviceWorker?.update()
171183 window.location = '/'
172184 return
173185
@@ -186,9 +198,9 @@ · 12 unmodified lines
186198 return
187199
188200 indexHost: ->
189− # Can't load the index files from the host/CDN when applicationCache is
201+ # Can't load the index files from the host/CDN when service worker is
190202 # 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']
192204
193205 onBootError: (args...) ->
194206 @trigger 'bootError'
@@ -245,7 +257,7 @@ · 50 unmodified lines
245257 matchMedia: !!window.matchMedia
246258 insertAdjacentHTML: !!document.body.insertAdjacentHTML
247259 defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false
248− cssGradients: supportsCssGradients()
260+ cssVariables: !!CSS?.supports?('(--t: 0)')
249261
250262 for key, value of features when not value
251263 Raven.captureMessage "unsupported/#{key}", level: 'info'
@@ -268,9 +280,4 @@ · 16 unmodified lines
268280 isInvalidLocation: ->
269281 @config.env is 'production' and location.host.indexOf(app.config.production_host) isnt 0
270282
271−supportsCssGradients = ->
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−
276283 $.extend app, Events
assets/javascripts/app/appcache.coffee+0−42
@@ -1,42 +0,0 @@
1−class app.AppCache
2− $.extend @prototype, Events
3−
4− @isEnabled: ->
5− try
6− applicationCache and applicationCache.status isnt applicationCache.UNCACHED
7− catch
8−
9− constructor: ->
10− @cache = applicationCache
11− @notifyUpdate = true
12− @onUpdateReady() if @cache.status is @cache.UPDATEREADY
13−
14− $.on @cache, 'progress', @onProgress
15− $.on @cache, 'updateready', @onUpdateReady
16−
17− update: ->
18− @notifyUpdate = true
19− @notifyProgress = true
20− try @cache.update() catch
21− return
22−
23− updateInBackground: ->
24− @notifyUpdate = false
25− @notifyProgress = false
26− try @cache.update() catch
27− return
28−
29− reload: ->
30− $.on @cache, 'updateready noupdate error', -> window.location = '/'
31− @notifyUpdate = false
32− @notifyProgress = true
33− try @cache.update() catch
34− return
35−
36− onProgress: (event) =>
37− @trigger 'progress', event if @notifyProgress
38− return
39−
40− onUpdateReady: =>
41− @trigger 'updateready' if @notifyUpdate
42− return
assets/javascripts/app/config.coffee.erb+3−0
@@ -13,3 +13,6 @@ app.config = · 12 unmodified lines
1313 version: <%= Time.now.to_i %>
1414 release: <%= Time.now.utc.httpdate.to_json %>
1515 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 @@
1+class app.ServiceWorker
2+ $.extend @prototype, Events
3+
4+ @isEnabled: ->
5+ !!navigator.serviceWorker and app.config.service_worker_enabled
6+
7+ constructor: ->
8+ @registration = null
9+ @notifyUpdate = true
10+
11+ navigator.serviceWorker.register(app.config.service_worker_path, {scope: '/'})
12+ .then(
13+ (registration) => @updateRegistration(registration),
14+ (error) -> console.error('Could not register service worker:', error)
15+ )
16+
17+ update: ->
18+ return unless @registration
19+ @notifyUpdate = true
20+ return @registration.update().catch(->)
21+
22+ updateInBackground: ->
23+ return unless @registration
24+ @notifyUpdate = false
25+ return @registration.update().catch(->)
26+
27+ reload: ->
28+ return @updateInBackground().then(() -> app.reboot())
29+
30+ updateRegistration: (registration) ->
31+ @registration = registration
32+ $.on @registration, 'updatefound', @onUpdateFound
33+ return
34+
35+ onUpdateFound: =>
36+ $.off @installingRegistration, 'statechange', @onStateChange() if @installingRegistration
37+ @installingRegistration = @registration.installing
38+ $.on @installingRegistration, 'statechange', @onStateChange
39+ return
40+
41+ onStateChange: =>
42+ if @installingRegistration and @installingRegistration.state == 'installed' and navigator.serviceWorker.controller
43+ @installingRegistration = null
44+ @onUpdateReady()
45+ return
46+
47+ onUpdateReady: ->
48+ @trigger 'updateready' if @notifyUpdate
49+ return
assets/javascripts/app/settings.coffee+34−1
@@ -5,11 +5,13 @@ class app.Settings · 4 unmodified lines
55 'manualUpdate'
66 'fastScroll'
77 'arrowScroll'
8+ 'analyticsConsent'
89 'docs'
910 'dark'
1011 'layout'
1112 'size'
1213 'tips'
14+ 'autoInstall'
1315 ]
1416
1517 INTERNAL_KEYS = [
@@ -19,6 +21,8 @@ class app.Settings · 3 unmodified lines
1921 'news'
2022 ]
2123
24+ LAYOUTS: ['_max-width', '_sidebar-hidden', '_native-scrollbars']
25+
2226 @defaults:
2327 count: 0
2428 hideDisabled: false
@@ -26,9 +30,10 @@ class app.Settings · 1 unmodified line
2630 news: 0
2731 manualUpdate: false
2832 schema: 1
33+ analyticsConsent: false
2934
3035 constructor: ->
31− @store = new CookieStore
36+ @store = new CookiesStore
3237 @cache = {}
3338
3439 get: (key) ->
@@ -38,6 +43,7 @@ class app.Settings · 3 unmodified lines
3843 set: (key, value) ->
3944 @store.set(key, value)
4045 delete @cache[key]
46+ @toggleDark(value) if key == 'dark'
4147 return
4248
4349 del: (key) ->
@@ -63,6 +69,8 @@ class app.Settings · 19 unmodified lines
6369 return
6470
6571 setLayout: (name, enable) ->
72+ @toggleLayout(name, enable)
73+
6674 layout = (@store.get('layout') || '').split(' ')
6775 $.arrayDelete(layout, '')
6876
@@ -104,3 +112,28 @@ class app.Settings · 35 unmodified lines
104112 @store.reset()
105113 @cache = {}
106114 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
149149 when 40
150150 @trigger 'altDown'
151151 false
152+ when 68
153+ @trigger 'altD'
154+ false
152155 when 70
153156 @trigger 'altF', event
154157 when 71
assets/javascripts/app/update_checker.coffee+3−3
@@ -3,13 +3,13 @@ class app.UpdateChecker · 2 unmodified lines
33 @lastCheck = Date.now()
44
55 $.on window, 'focus', @onFocus
6− app.appCache.on 'updateready', @onUpdateReady if app.appCache
6+ app.serviceWorker?.on 'updateready', @onUpdateReady
77
88 setTimeout @checkDocs, 0
99
1010 check: ->
11− if app.appCache
12− app.appCache.update()
11+ if app.serviceWorker
12+ app.serviceWorker.update()
1313 else
1414 ajax
1515 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 @@
1−class @CookieStore
1+class @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+
26 INT = /^\d+$/
37
48 @onBlocked: ->
assets/javascripts/lib/favicon.coffee+64−0
@@ -0,0 +1,64 @@
1+defaultUrl = null
2+currentSlug = null
3+
4+imageCache = {}
5+urlCache = {}
6+
7+withImage = (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 @@
11 ###
2− * Copyright 2013-2017 Thibaut Courouble and other contributors
2+ * Copyright 2013-2019 Thibaut Courouble and other contributors
33 *
44 * This source code is licensed under the terms of the Mozilla
55 * 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
190190
191191 updateCanonicalLink = ->
192192 @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}")
194194
195195 trackers = []
196196
@@ -199,5 +199,21 @@ page.track = (fn) -> · 2 unmodified lines
199199 return
200200
201201 track = ->
202− tracker.call() for tracker in trackers
202+ consentGiven = Cookies.get('analyticsConsent')
203+ consentAsked = Cookies.get('analyticsConsentAsked')
204+
205+ if consentGiven == '1'
206+ tracker.call() for tracker in trackers
207+ else if consentGiven == undefined and consentAsked == undefined
208+ # Only ask for consent once per browser session
209+ Cookies.set('analyticsConsentAsked', '1')
210+
211+ new app.views.Notif 'AnalyticsConsent', autoHide: null
212+ return
213+
214+@resetAnalytics = ->
215+ for cookie in document.cookie.split(/;\s?/)
216+ name = cookie.split('=')[0]
217+ if name[0] == '_'
218+ Cookies.expire(name)
203219 return
assets/javascripts/lib/util.coffee+5−1
@@ -124,7 +124,7 @@ $.empty = (el) -> · 123 unmodified lines
124124 return
125125
126126 # Calls the function while the element is off the DOM to avoid triggering
127−# unecessary reflows and repaints.
127+# unnecessary reflows and repaints.
128128 $.batchUpdate = (el, fn) ->
129129 parent = el.parentNode
130130 sibling = el.nextSibling
@@ -352,6 +352,10 @@ isIE = null · 221 unmodified lines
352352 $.isIE = ->
353353 isIE ?= navigator.userAgent?.indexOf('MSIE') >= 0 || navigator.userAgent?.indexOf('rv:11.0') >= 0
354354
355+isChromeForAndroid = null
356+$.isChromeForAndroid = ->
357+ isChromeForAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 && /Chrome\/([.0-9])+ Mobile/.test(navigator.userAgent)
358+
355359 isAndroid = null
356360 $.isAndroid = ->
357361 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
142142 return
143143
144144 isOutdated: (status) ->
145− status and status.installed and @mtime isnt status.mtime
145+ 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+35−3
@@ -1,5 +1,37 @@
11 [
22 [
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+ ], [
20+ "2018-07-08",
21+ "New documentations: <a href=\"/leaflet/\">Leaflet</a>, <a href=\"/terraform/\">Terraform</a> and <a href=\"/koa/\">Koa</a>"
22+ ], [
23+ "2018-03-26",
24+ "DevDocs is joining the freeCodeCamp community. Read the announcement <a href=\"https://medium.freecodecamp.org/devdocs-is-joining-the-freecodecamp-community-ae185a1c14a6\" target=\"_blank\">here</a>."
25+ ], [
26+ "2018-02-04",
27+ "New documentations: <a href=\"/babel/\">Babel</a>, <a href=\"/jekyll/\">Jekyll</a> and <a href=\"/jsdoc/\">JSDoc</a>"
28+ ], [
29+ "2017-11-26",
30+ "New documentations: <a href=\"/bluebird/\">Bluebird</a>, <a href=\"/eslint/\">ESLint</a> and <a href=\"/homebrew/\">Homebrew</a>"
31+ ], [
32+ "2017-11-18",
33+ "Added print & PDF stylesheet.\nFeedback welcome on <a href=\"https://twitter.com/DevDocs\" target=\"_blank\" rel=\"noopener\">Twitter</a> and <a href=\"https://github.com/freeCodeCamp/devdocs\" target=\"_blank\" rel=\"noopener\">GitHub</a>."
34+ ], [
335 "2017-09-10",
436 "<a href=\"/settings\">Preferences</a> can now be exported and imported."
537 ], [
@@ -110,7 +142,7 @@ · 104 unmodified lines
110142 "New documentations: <a href=\"/q/\">Q</a> and <a href=\"/opentsdb/\">OpenTSDB</a>"
111143 ], [
112144 "2015-07-26",
113− "Added search aliases (e.g. <code class=\"_label\">$</code> is an alias for <code class=\"_label\">jQuery</code>).\n<a href=\"/help#aliases\">Click here</a> to see the full list. Feel free to suggest more on <a href=\"https://github.com/Thibaut/devdocs/issues/new\" target=\"_blank\" rel=\"noopener\">GitHub</a>.",
145+ "Added search aliases (e.g. <code class=\"_label\">$</code> is an alias for <code class=\"_label\">jQuery</code>).\n<a href=\"/help#aliases\">Click here</a> to see the full list. Feel free to suggest more on <a href=\"https://github.com/freeCodeCamp/devdocs/issues/new\" target=\"_blank\" rel=\"noopener\">GitHub</a>.",
114146 "Added <code class=\"_label\">shift + &darr;/&uarr;</code> shortcut for scrolling (same as <code class=\"_label\">alt + &darr;/&uarr;</code>)."
115147 ], [
116148 "2015-07-05",
@@ -151,7 +183,7 @@ · 34 unmodified lines
151183 "New <a href=\"/python2/\">Python 2</a> documentation"
152184 ], [
153185 "2014-11-09",
154− "New design\nFeedback welcome on <a href=\"https://twitter.com/DevDocs\" target=\"_blank\" rel=\"noopener\">Twitter</a> and <a href=\"https://github.com/Thibaut/devdocs\" target=\"_blank\" rel=\"noopener\">GitHub</a>."
186+ "New design\nFeedback welcome on <a href=\"https://twitter.com/DevDocs\" target=\"_blank\" rel=\"noopener\">Twitter</a> and <a href=\"https://github.com/freeCodeCamp/devdocs\" target=\"_blank\" rel=\"noopener\">GitHub</a>."
155187 ], [
156188 "2014-10-19",
157189 "New <a href=\"/svg/\">SVG</a>, <a href=\"/marionette/\">Marionette.js</a>, and <a href=\"/mongoose/\">Mongoose</a> documentations"
@@ -236,7 +268,7 @@ · 78 unmodified lines
236268 "New <a href=\"/ruby/\">Ruby</a> documentation"
237269 ], [
238270 "2013-10-24",
239− "DevDocs is now <a href=\"https://github.com/Thibaut/devdocs\">open source</a>."
271+ "DevDocs is now <a href=\"https://github.com/freeCodeCamp/devdocs\">open source</a>."
240272 ], [
241273 "2013-10-09",
242274 "DevDocs is now available as a <a href=\"https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe\">Chrome web app</a>."
assets/javascripts/templates/error_tmpl.coffee+11−14
@@ -12,8 +12,8 @@ app.templates.notFoundPage = -> · 11 unmodified lines
1212
1313 app.templates.pageLoadError = ->
1414 error """ The page failed to load. """,
15− """ It may be missing from the server (try reloading the app) or you could be offline.<br>
16− If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """,
15+ """ It may be missing from the server (try reloading the app) or you could be offline (try <a href="/offline">installing the documentation for offline usage</a> when online again).<br>
16+ If you're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """,
1717 """ #{back} &middot; <a href="/##{location.pathname}" target="_top" class="_error-link">Reload</a>
1818 &middot; <a href="#" class="_error-link" data-retry>Retry</a> """
1919
@@ -37,10 +37,10 @@ app.templates.offlineError = (reason, exception) -> · 17 unmodified lines
3737 """ Your browser appears to be running in private mode.<br>
3838 This prevents DevDocs from caching documentations for offline access."""
3939 when 'exception'
40− """ An error occured when trying to open the IndexedDB database:<br>
40+ """ An error occurred when trying to open the IndexedDB database:<br>
4141 <code class="_label">#{exception.name}: #{exception.message}</code> """
4242 when 'cant_open'
43− """ An error occured when trying to open the IndexedDB database:<br>
43+ """ An error occurred when trying to open the IndexedDB database:<br>
4444 <code class="_label">#{exception.name}: #{exception.message}</code><br>
4545 This could be because you're browsing in private mode or have disallowed offline storage on the domain. """
4646 when 'version'
@@ -56,21 +56,18 @@ app.templates.unsupportedBrowser = """ · 9 unmodified lines
5656 <h1 class="_fail-title">Your browser is unsupported, sorry.</h1>
5757 <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:
5858 <ul class="_fail-list">
59− <li>Recent versions of Chrome and Firefox
60− <li>Safari 5.1+
61− <li>Opera 12.1+
62− <li>Internet Explorer 10+
63− <li>iOS 6+
64− <li>Android 4.1+
65− <li>Windows Phone 8+
59+ <li>Recent versions of Firefox, Chrome, or Opera
60+ <li>Safari 11.1+
61+ <li>Edge 17+
62+ <li>iOS 11.3+
6663 </ul>
6764 <p class="_fail-text">
68− If you're unable to upgrade, I apologize.
69− I decided to prioritize speed and new features over support for older browsers.
65+ If you're unable to upgrade, we apologize.
66+ We decided to prioritize speed and new features over support for older browsers.
7067 <p class="_fail-text">
7168 Note: if you're already using one of the browsers above, check your settings and add-ons.
7269 The app uses feature detection, not user agent sniffing.
7370 <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>
7572 </div>
7673 """
assets/javascripts/templates/notice_tmpl.coffee+1−1
@@ -2,7 +2,7 @@ notice = (text) -> """<p class="_notice-text">#{text}</p>""" · 1 unmodified line
22
33 app.templates.singleDocNotice = (doc) ->
44 notice """ You're browsing the #{doc.fullName} documentation. To browse all docs, go to
5− <a href="http://#{app.config.production_host}" target="_top">#{app.config.production_host}</a> (or press <code>esc</code>). """
5+ <a href="//#{app.config.production_host}" target="_top">#{app.config.production_host}</a> (or press <code>esc</code>). """
66
77 app.templates.disabledDocNotice = ->
88 notice """ <strong>This documentation is disabled.</strong>
assets/javascripts/templates/notif_tmpl.coffee+9−3
@@ -13,10 +13,10 @@ app.templates.notifUpdateReady = -> · 12 unmodified lines
1313 """<span data-behavior="reboot"><a href="#" data-behavior="reboot">Reload the page</a> to use the new version.</span>"""
1414
1515 app.templates.notifError = ->
16− textNotif """ Oops, an error occured. """,
16+ textNotif """ Oops, an error occurred. """,
1717 """ Try <a href="#" data-behavior="hard-reload">reloading</a>, and if the problem persists,
1818 <a href="#" data-behavior="reset">resetting the app</a>.<br>
19− You can also report this issue on <a href="https://github.com/Thibaut/devdocs/issues/new" target="_blank" rel="noopener">GitHub</a>. """
19+ You can also report this issue on <a href="https://github.com/freeCodeCamp/devdocs/issues/new" target="_blank" rel="noopener">GitHub</a>. """
2020
2121 app.templates.notifQuotaExceeded = ->
2222 textNotif """ The offline database has exceeded its size limitation. """,
@@ -31,7 +31,7 @@ app.templates.notifInvalidLocation = -> · 8 unmodified lines
3131 """ Otherwise things are likely to break. """
3232
3333 app.templates.notifImportInvalid = ->
34− textNotif """ Oops, an error occured. """,
34+ textNotif """ Oops, an error occurred. """,
3535 """ The file you selected is invalid. """
3636
3737 app.templates.notifNews = (news) ->
@@ -68,3 +68,9 @@ app.templates.notifShare = -> · 30 unmodified lines
6868 app.templates.notifUpdateDocs = ->
6969 textNotif """ Documentation updates available. """,
7070 """ <a href="/offline">Install them</a> as soon as possible to avoid broken pages. """
71+
72+app.templates.notifAnalyticsConsent = ->
73+ textNotif """ Tracking cookies """,
74+ """ We would like to gather usage data about how DevDocs is used through Google Analytics and Gauges. We only collect anonymous traffic information.
75+ Please confirm if you accept our tracking cookies. You can always change your decision in the settings.
76+ <br><span class="_notif-right"><a href="#" data-behavior="accept-analytics">Accept</a> or <a href="#" data-behavior="decline-analytics">Decline</a></span> """
assets/javascripts/templates/pages/about_tmpl.coffee+146−72
@@ -11,28 +11,22 @@ app.templates.aboutPage = -> """ · 10 unmodified lines
1111 </nav>
1212
1313 <h1 class="_lined-heading">DevDocs: API Documentation Browser</h1>
14− <p>DevDocs combines multiple API documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.
15− <ul>
16− <li>Created and maintained by <a href="https://thibaut.me">Thibaut Courouble</a>
17− <li>Free and <a href="https://github.com/Thibaut/devdocs">open source</a>
18− <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=Thibaut&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20" tabindex="-1"></iframe>
19− </ul>
14+ <p>DevDocs combines multiple developer documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.
15+ <p>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>. It was created by <a href="https://thibaut.me">Thibaut Courouble</a> and is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a>.
2016 <p>To keep up-to-date with the latest news:
2117 <ul>
2218 <li>Follow <a href="https://twitter.com/DevDocs">@DevDocs</a> on Twitter
23− <li>Watch the repository on <a href="https://github.com/Thibaut/devdocs/subscription">GitHub</a>
24− <li>Join the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>
19+ <li>Watch the repository on <a href="https://github.com/freeCodeCamp/devdocs/subscription">GitHub</a> <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20" tabindex="-1"></iframe>
20+ <li>Join the <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a> chat room
2521 </ul>
26− <p class="_note _note-green">If you like DevDocs, please spread the word and consider supporting my work on
27− <a href="https://gratipay.com/devdocs/">Gratipay</a>. Thanks!<br>
2822
2923 <h2 class="_block-heading" id="copyright">Copyright and License</h2>
3024 <p class="_note">
31− <strong>Copyright 2013&ndash;2017 Thibaut Courouble and <a href="https://github.com/Thibaut/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>
3226 This software is licensed under the terms of the Mozilla Public License v2.0.<br>
33− You may obtain a copy of the source code at <a href="https://github.com/Thibaut/devdocs">github.com/Thibaut/devdocs</a>.<br>
34− For more information, see the <a href="https://github.com/Thibaut/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a>
35− and <a href="https://github.com/Thibaut/devdocs/blob/master/LICENSE">LICENSE</a> files.
27+ You may obtain a copy of the source code at <a href="https://github.com/freeCodeCamp/devdocs">github.com/freeCodeCamp/devdocs</a>.<br>
28+ For more information, see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a>
29+ and <a href="https://github.com/freeCodeCamp/devdocs/blob/master/LICENSE">LICENSE</a> files.
3630
3731 <h2 class="_block-heading" id="plugins">Plugins and Extensions</h2>
3832 <ul>
@@ -49,20 +43,19 @@ app.templates.aboutPage = -> """ · 10 unmodified lines
4943 <dl>
5044 <dt>Where can I suggest new docs and features?
5145 <dd>You can suggest and vote for new docs on the <a href="https://trello.com/b/6BmTulfx/devdocs-documentation">Trello board</a>.<br>
52− If you have a specific feature request, add it to the <a href="https://github.com/Thibaut/devdocs/issues">issue tracker</a>.<br>
53− Otherwise use the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>.
46+ If you have a specific feature request, add it to the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>.<br>
47+ Otherwise, come talk to us in the <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a> chat room.
5448 <dt>Where can I report bugs?
55− <dd>In the <a href="https://github.com/Thibaut/devdocs/issues">issue tracker</a>. Thanks!
49+ <dd>In the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>. Thanks!
5650 </dl>
57− <p>For anything else, feel free to email me at <a href="mailto:thibaut@devdocs.io">thibaut@devdocs.io</a>.
5851
5952 <h2 class="_block-heading" id="credits">Credits</h2>
6053
6154 <p><strong>Special thanks to:</strong>
6255 <ul>
63− <li><a href="https://out.devdocs.io/s/maxcdn">MaxCDN</a>, <a href="https://sentry.io/">Sentry</a> and <a href="http://get.gaug.es/?utm_source=devdocs&utm_medium=referral&utm_campaign=sponsorships" title="Real Time Web Analytics">Gauges</a> for offering a free account to DevDocs
56+ <li><a href="https://out.devdocs.io/s/maxcdn">MaxCDN</a>, <a href="https://sentry.io/">Sentry</a> and <a href="https://get.gaug.es/?utm_source=devdocs&utm_medium=referral&utm_campaign=sponsorships" title="Real Time Web Analytics">Gauges</a> for offering a free account to DevDocs
6457 <li><a href="https://out.devdocs.io/s/maxcdn">MaxCDN</a>, <a href="https://out.devdocs.io/s/shopify">Shopify</a>, <a href="https://out.devdocs.io/s/jetbrains">JetBrains</a> and <a href="https://out.devdocs.io/s/code-school">Code School</a> for sponsoring DevDocs in the past
65− <li><a href="https://www.heroku.com">Heroku</a> and <a href="http://newrelic.com">New Relic</a> for providing awesome free service
58+ <li><a href="https://www.heroku.com">Heroku</a> and <a href="https://newrelic.com/">New Relic</a> for providing awesome free service
6659 <li><a href="https://www.jeremykratz.com/">Jeremy Kratz</a> for the C/C++ logo
6760 </ul>
6861
@@ -78,43 +71,59 @@ app.templates.aboutPage = -> """ · 9 unmodified lines
7871
7972 <h2 class="_block-heading" id="privacy">Privacy Policy</h2>
8073 <ul>
81− <li><a href="http://devdocs.io">devdocs.io</a> ("App") is operated by Thibaut Courouble ("We").
82− <li>We do not collect personal information.
83− <li>We use Google Analytics, Gauges and Sentry to collect anonymous traffic information and improve the app.
74+ <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a> ("We").
75+ <li>We do not collect personal information through the app.
76+ <li>We use Google Analytics and Gauges to collect anonymous traffic information if you have given consent to this. You can change your decision in the <a href="/settings">settings</a>.
77+ <li>We use Sentry to collect crash data and improve the app.
8478 <li>The app uses cookies to store user preferences.
8579 <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.
86− <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>.
8781 </ul>
8882 """
8983
9084 credits = [
9185 [ 'Angular<br>Angular.js',
92− '2010-2017 Google, Inc.',
86+ '2010-2018 Google, Inc.',
9387 'CC BY',
9488 'https://creativecommons.org/licenses/by/4.0/'
9589 ], [
9690 'Ansible',
97− '2012-2017 Michael DeHaan<br>&copy; 2017 Red Hat, Inc.',
91+ '2012-2018 Michael DeHaan<br>&copy; 2018–2019 Red Hat, Inc.',
9892 'GPLv3',
9993 'https://raw.githubusercontent.com/ansible/ansible/devel/COPYING'
10094 ], [
10195 'Apache HTTP Server<br>Apache Pig',
102− '2017 The Apache Software Foundation<br>Apache and the Apache feather logo are trademarks of The Apache Software Foundation.',
96+ '2018 The Apache Software Foundation<br>Apache and the Apache feather logo are trademarks of The Apache Software Foundation.',
10397 'Apache',
10498 'https://www.apache.org/licenses/LICENSE-2.0'
10599 ], [
106100 'Async',
107− '2010-2017 Caolan McMahon',
101+ '2010-2018 Caolan McMahon',
108102 'MIT',
109103 'https://raw.githubusercontent.com/caolan/async/master/LICENSE'
104+ ], [
105+ 'Babel',
106+ '2018 Sebastian McKenzie',
107+ 'MIT',
108+ 'https://raw.githubusercontent.com/babel/website/master/LICENSE'
110109 ], [
111110 'Backbone.js',
112111 '2010-2016 Jeremy Ashkenas, DocumentCloud',
113112 'MIT',
114113 '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'
119+ ], [
120+ 'Bluebird',
121+ '2013-2017 Petka Antonov',
122+ 'MIT',
123+ 'https://raw.githubusercontent.com/petkaantonov/bluebird/master/LICENSE'
115124 ], [
116125 'Bootstrap',
117− '2011-2017 Twitter, Inc.<br>2011-2017 The Bootstrap Authors',
126+ '2011-2019 Twitter, Inc.<br>2011-2019 The Bootstrap Authors',
118127 'CC BY',
119128 'https://creativecommons.org/licenses/by/3.0/'
120129 ], [
@@ -124,7 +133,7 @@ credits = [ · 3 unmodified lines
124133 'https://raw.githubusercontent.com/bottlepy/bottle/master/LICENSE'
125134 ], [
126135 'Bower',
127− '2017 Bower contributors',
136+ '2018 Bower contributors',
128137 'MIT',
129138 'https://github.com/bower/bower.github.io/blob/1057905c18d899106f91372e6cca7ef54a91d60f/package.json#L20'
130139 ], [
@@ -134,9 +143,9 @@ credits = [ · 3 unmodified lines
134143 'http://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA'
135144 ], [
136145 'CakePHP',
137− '2005-2017 The Cake Software Foundation, Inc.',
146+ '2005-2018 The Cake Software Foundation, Inc.',
138147 'MIT',
139− 'https://raw.githubusercontent.com/cakephp/cakephp/master/LICENSE.txt'
148+ 'https://raw.githubusercontent.com/cakephp/cakephp/master/LICENSE'
140149 ], [
141150 'Chai',
142151 '2016 Chai.js Assertion Library',
@@ -154,12 +163,12 @@ credits = [ · 11 unmodified lines
154163 'https://github.com/clojure/clojure/blob/master/epl-v10.html'
155164 ], [
156165 'CMake',
157− '2000-2017 Kitware, Inc. and Contributors',
166+ '2000-2019 Kitware, Inc. and Contributors',
158167 'BSD',
159168 'https://cmake.org/licensing/'
160169 ], [
161170 'Codeception',
162− '2011-2017 Michael Bodnarchuk and contributors',
171+ '2011 Michael Bodnarchuk and contributors',
163172 'MIT',
164173 'https://raw.githubusercontent.com/Codeception/Codeception/master/LICENSE'
165174 ], [
@@ -169,17 +178,17 @@ credits = [ · 3 unmodified lines
169178 'https://raw.githubusercontent.com/Codeception/CodeceptJS/master/LICENSE'
170179 ], [
171180 'CodeIgniter',
172− '2014-2017 British Columbia Institute of Technology',
181+ '2014-2018 British Columbia Institute of Technology',
173182 'MIT',
174183 'https://raw.githubusercontent.com/bcit-ci/CodeIgniter/develop/license.txt'
175184 ], [
176185 'CoffeeScript',
177− '2009-2017 Jeremy Ashkenas',
186+ '2009-2018 Jeremy Ashkenas',
178187 'MIT',
179− 'https://raw.githubusercontent.com/jashkenas/coffee-script/master/LICENSE'
188+ 'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE'
180189 ], [
181190 'Cordova',
182− '2012-2017 The Apache Software Foundation',
191+ '2012-2018 The Apache Software Foundation',
183192 'Apache',
184193 'https://raw.githubusercontent.com/apache/cordova-docs/master/LICENSE'
185194 ], [
@@ -194,14 +203,19 @@ credits = [ · 8 unmodified lines
194203 'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE'
195204 ], [
196205 'D',
197− '1999-2017 The D Language Foundation',
206+ '1999-2018 The D Language Foundation',
198207 'Boost',
199208 'https://raw.githubusercontent.com/dlang/phobos/master/LICENSE_1_0.txt'
200209 ], [
201210 'D3.js',
202− '2010-2017 Michael Bostock',
211+ '2010-2018 Michael Bostock',
203212 'BSD',
204213 'https://raw.githubusercontent.com/d3/d3/master/LICENSE'
214+ ], [
215+ 'Dart',
216+ '2012 the Dart project authors',
217+ 'CC BY-SA',
218+ 'https://creativecommons.org/licenses/by-sa/4.0/'
205219 ], [
206220 'Django',
207221 'Django Software Foundation and individual contributors',
@@ -209,7 +223,7 @@ credits = [ · 1 unmodified line
209223 'https://raw.githubusercontent.com/django/django/master/LICENSE'
210224 ], [
211225 'Docker',
212− '2017 Docker, Inc.<br>Docker and the Docker logo are trademarks of Docker, Inc.',
226+ '2019 Docker, Inc.<br>Docker and the Docker logo are trademarks of Docker, Inc.',
213227 'Apache',
214228 'https://raw.githubusercontent.com/docker/docker.github.io/master/LICENSE'
215229 ], [
@@ -224,12 +238,12 @@ credits = [ · 8 unmodified lines
224238 'https://api.drupal.org/api/drupal/LICENSE.txt'
225239 ], [
226240 'Electron',
227− '2013-2017 GitHub Inc.',
241+ '2013-2018 GitHub Inc.',
228242 'MIT',
229243 'https://raw.githubusercontent.com/electron/electron/master/LICENSE'
230244 ], [
231245 'Elixir',
232− '2012-2017 Plataformatec',
246+ '2012 Plataformatec',
233247 'Apache',
234248 'https://raw.githubusercontent.com/elixir-lang/elixir/master/LICENSE'
235249 ], [
@@ -242,6 +256,11 @@ credits = [ · 6 unmodified lines
242256 '2010-2017 Ericsson AB',
243257 'Apache',
244258 'https://raw.githubusercontent.com/erlang/otp/maint/LICENSE.txt'
259+ ], [
260+ 'ESLint',
261+ 'JS Foundation and other contributors',
262+ 'MIT',
263+ 'https://raw.githubusercontent.com/eslint/eslint/master/LICENSE'
245264 ], [
246265 'Express',
247266 '2017 StrongLoop, IBM, and other expressjs.com contributors.',
@@ -264,7 +283,7 @@ credits = [ · 16 unmodified lines
264283 'https://www.gnu.org/licenses/fdl-1.3.en.html'
265284 ], [
266285 'Git',
267− '2005-2017 Linus Torvalds and others',
286+ '2005-2018 Linus Torvalds and others',
268287 'GPLv2',
269288 'https://raw.githubusercontent.com/git/git/master/COPYING'
270289 ], [
@@ -274,14 +293,24 @@ credits = [ · 3 unmodified lines
274293 'https://creativecommons.org/licenses/by/3.0/'
275294 ], [
276295 'Godot',
277− '2014-2017 Juan Linietsky, Ariel Manzur, Godot Engine contributors',
296+ '2014-2018 Juan Linietsky, Ariel Manzur, Godot Engine contributors',
278297 'MIT',
279298 'https://raw.githubusercontent.com/godotengine/godot/master/LICENSE.txt'
299+ ], [
300+ 'Graphite',
301+ '2008-2012 Chris Davis<br>&copy; 2011-2016 The Graphite Project',
302+ 'Apache',
303+ 'https://raw.githubusercontent.com/graphite-project/graphite-web/master/LICENSE'
280304 ], [
281305 'Grunt',
282306 'GruntJS Team',
283307 'MIT',
284308 'https://github.com/gruntjs/grunt-docs/blob/master/package.json#L10'
309+ ], [
310+ 'Handlebars',
311+ '2011-2017 Yehuda Katz',
312+ 'MIT',
313+ 'https://raw.githubusercontent.com/wycats/handlebars.js/master/LICENSE'
285314 ], [
286315 'Haskell',
287316 'The University of Glasgow',
@@ -289,9 +318,14 @@ credits = [ · 1 unmodified line
289318 'https://www.haskell.org/ghc/license'
290319 ], [
291320 'Haxe',
292− '2005-2016 Haxe Foundation',
321+ '2005-2018 Haxe Foundation',
293322 'MIT',
294− 'http://haxe.org/foundation/open-source.html'
323+ 'https://haxe.org/foundation/open-source.html'
324+ ], [
325+ 'Homebrew',
326+ '2009-present Homebrew contributors',
327+ 'BSD',
328+ 'https://raw.githubusercontent.com/Homebrew/brew/master/LICENSE.txt'
295329 ], [
296330 'Immutable.js',
297331 '2014-2016 Facebook, Inc.',
@@ -307,6 +341,11 @@ credits = [ · 9 unmodified lines
307341 '2008-2017 Pivotal Labs',
308342 'MIT',
309343 'https://raw.githubusercontent.com/jasmine/jasmine/master/MIT.LICENSE'
344+ ], [
345+ 'Jekyll',
346+ '2008-2018 Tom Preston-Werner and Jekyll contributors',
347+ 'MIT',
348+ 'https://raw.githubusercontent.com/jekyll/jekyll/master/LICENSE'
310349 ], [
311350 'Jest',
312351 '2014-present Facebook Inc.',
@@ -329,17 +368,27 @@ credits = [ · 16 unmodified lines
329368 'https://raw.githubusercontent.com/jquery/api.jqueryui.com/master/LICENSE.txt'
330369 ], [
331370 'Julia',
332− '2009-2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors',
371+ '2009-2018 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors',
333372 'MIT',
334373 'https://raw.githubusercontent.com/JuliaLang/julia/master/LICENSE.md'
374+ ], [
375+ 'JSDoc',
376+ '2011-2017 the contributors to the JSDoc 3 documentation project',
377+ 'CC BY-SA',
378+ 'https://raw.githubusercontent.com/jsdoc3/jsdoc3.github.com/master/LICENSE'
335379 ], [
336380 'Knockout.js',
337381 'Steven Sanderson, the Knockout.js team, and other contributors',
338382 'MIT',
339383 'https://raw.githubusercontent.com/knockout/knockout/master/LICENSE'
384+ ], [
385+ 'Koa',
386+ '2018 Koa contributors',
387+ 'MIT',
388+ 'https://raw.githubusercontent.com/koajs/koa/master/LICENSE'
340389 ], [
341390 'Kotlin',
342− '2010-2017 JetBrains s.r.o.',
391+ '2010-2019 JetBrains s.r.o.',
343392 'Apache',
344393 'https://raw.githubusercontent.com/JetBrains/kotlin/master/license/LICENSE.txt'
345394 ], [
@@ -347,6 +396,11 @@ credits = [ · 1 unmodified line
347396 'Taylor Otwell',
348397 'MIT',
349398 'https://raw.githubusercontent.com/laravel/framework/master/LICENSE.txt'
399+ ], [
400+ 'Leaflet',
401+ '2010-2019 Vladimir Agafonkin<br>&copy; 2010-2011, CloudMade<br>Maps &copy; OpenStreetMap contributors.',
402+ 'BSD',
403+ 'https://raw.githubusercontent.com/Leaflet/Leaflet/master/LICENSE'
350404 ], [
351405 'Less',
352406 '2009-2016 The Core Less Team',
@@ -359,9 +413,9 @@ credits = [ · 6 unmodified lines
359413 'https://raw.githubusercontent.com/Shopify/liquid/master/LICENSE'
360414 ], [
361415 'Lo-Dash',
362− '2012-2016 The Dojo Foundation',
416+ 'JS Foundation and other contributors',
363417 'MIT',
364− 'https://raw.githubusercontent.com/lodash/lodash/master/LICENSE.txt'
418+ 'https://raw.githubusercontent.com/lodash/lodash/master/LICENSE'
365419 ], [
366420 'Lua',
367421 '1994–2017 Lua.org, PUC-Rio',
@@ -384,7 +438,7 @@ credits = [ · 16 unmodified lines
384438 'https://daringfireball.net/projects/markdown/license'
385439 ], [
386440 'Matplotlib',
387− '2012-2017 Matplotlib Development Team. All rights reserved.',
441+ '2012-2018 Matplotlib Development Team. All rights reserved.',
388442 'Custom',
389443 'https://raw.githubusercontent.com/matplotlib/matplotlib/master/LICENSE/LICENSE'
390444 ], [
@@ -399,7 +453,7 @@ credits = [ · 8 unmodified lines
399453 'https://github.com/seattlerb/minitest/blob/master/README.rdoc#license'
400454 ], [
401455 'Mocha',
402− '2016 JS Foundation and contributors',
456+ '2011-2018 JS Foundation and contributors',
403457 'CC BY',
404458 'https://creativecommons.org/licenses/by/4.0/'
405459 ], [
@@ -419,12 +473,12 @@ credits = [ · 13 unmodified lines
419473 'https://github.com/LearnBoost/mongoose/blob/master/README.md#license'
420474 ], [
421475 'nginx',
422− '2002-2017 Igor Sysoev<br>&copy; 2011-2017 Nginx, Inc.',
476+ '2002-2019 Igor Sysoev<br>&copy; 2011-2019 Nginx, Inc.',
423477 'BSD',
424478 'http://nginx.org/LICENSE'
425479 ], [
426480 'nginx / Lua Module',
427− '2009-2016 Xiaozhe Wang (chaoslawful)<br>&copy; 2009-2017 Yichun "agentzh" Zhang (章亦春), OpenResty Inc.',
481+ '2009-2017 Xiaozhe Wang (chaoslawful)<br>&copy; 2009-2018 Yichun "agentzh" Zhang (章亦春), OpenResty Inc.',
428482 'BSD',
429483 'https://github.com/openresty/lua-nginx-module#copyright-and-license'
430484 ], [
@@ -464,7 +518,7 @@ credits = [ · 33 unmodified lines
464518 'https://raw.githubusercontent.com/OpenTSDB/opentsdb.net/gh-pages/COPYING.LESSER'
465519 ], [
466520 'Padrino',
467− '2010-2016 Padrino',
521+ '2010-2019 Padrino',
468522 'MIT',
469523 'https://raw.githubusercontent.com/padrino/padrino-framework/master/padrino/LICENSE.txt'
470524 ], [
@@ -494,7 +548,7 @@ credits = [ · 23 unmodified lines
494548 'https://raw.githubusercontent.com/phoenixframework/phoenix/master/LICENSE.md'
495549 ], [
496550 'PHP',
497− '1997-2017 The PHP Documentation Group',
551+ '1997-2018 The PHP Documentation Group',
498552 'CC BY',
499553 'https://secure.php.net/manual/en/copyright.php'
500554 ], [
@@ -504,12 +558,22 @@ credits = [ · 3 unmodified lines
504558 'https://creativecommons.org/licenses/by/3.0/'
505559 ], [
506560 'PostgreSQL',
507− '1996-2017 The PostgreSQL Global Development Group<br>&copy; 1994 The Regents of the University of California',
561+ '1996-2018 The PostgreSQL Global Development Group<br>&copy; 1994 The Regents of the University of California',
508562 'PostgreSQL',
509563 'https://www.postgresql.org/about/licence/'
564+ ], [
565+ 'Puppeteer',
566+ '2017 Google Inc',
567+ 'Apache',
568+ 'https://raw.githubusercontent.com/GoogleChrome/puppeteer/master/LICENSE'
569+ ], [
570+ 'Pygame',
571+ 'Pygame Developers',
572+ 'LGPLv2.1',
573+ 'https://raw.githubusercontent.com/pygame/pygame/master/LICENSE'
510574 ], [
511575 'Python',
512− '2001-2017 Python Software Foundation<br>Python is a trademark of the Python Software Foundation.',
576+ '2001-2018 Python Software Foundation<br>Python is a trademark of the Python Software Foundation.',
513577 'PSFL',
514578 'https://docs.python.org/3/license.html'
515579 ], [
@@ -517,6 +581,11 @@ credits = [ · 1 unmodified line
517581 '2009-2017 Kristopher Michael Kowal',
518582 'MIT',
519583 'https://raw.githubusercontent.com/kriskowal/q/master/LICENSE'
584+ ], [
585+ 'Qt',
586+ '2012-2018 The Qt Company Ltd',
587+ 'GFDL',
588+ 'https://doc.qt.io/qt-5/licensing.html'
520589 ], [
521590 'Ramda',
522591 '2013-2016 Scott Sauyet and Michael Hurley',
@@ -524,12 +593,12 @@ credits = [ · 1 unmodified line
524593 'https://raw.githubusercontent.com/ramda/ramda/master/LICENSE.txt'
525594 ], [
526595 'React, React Native, Flow, Relay',
527− '2013-2017 Facebook Inc.',
528− 'CC BY',
529− 'https://raw.githubusercontent.com/facebook/react/master/LICENSE-docs'
596+ '2013-present Facebook Inc.',
597+ 'MIT',
598+ 'https://raw.githubusercontent.com/facebook/react/master/LICENSE'
530599 ], [
531600 'Redis',
532− '2009-2017 Salvatore Sanfilippo',
601+ '2009-2018 Salvatore Sanfilippo',
533602 'CC BY-SA',
534603 'https://creativecommons.org/licenses/by-sa/4.0/'
535604 ], [
@@ -574,12 +643,12 @@ credits = [ · 38 unmodified lines
574643 'http://scikit-image.org/docs/dev/license.html'
575644 ], [
576645 'scikit-learn',
577− '2007-2017 The scikit-learn developers',
646+ '2007-2018 The scikit-learn developers',
578647 'BSD',
579648 'https://raw.githubusercontent.com/scikit-learn/scikit-learn/master/COPYING'
580649 ], [
581650 'Sinon',
582− '2010-2017 Christian Johansen',
651+ '2010-2018 Christian Johansen',
583652 'BSD',
584653 'https://raw.githubusercontent.com/sinonjs/sinon/master/LICENSE'
585654 ], [
@@ -609,14 +678,19 @@ credits = [ · 23 unmodified lines
609678 'http://tcl.tk/software/tcltk/license.html'
610679 ], [
611680 'TensorFlow',
612− '2017 The TensorFlow Authors',
681+ '2018 The TensorFlow Authors',
613682 'CC BY',
614683 'https://creativecommons.org/licenses/by/3.0/'
684+ ], [
685+ 'Terraform',
686+ '2018 HashiCorp',
687+ 'MPL',
688+ 'https://raw.githubusercontent.com/hashicorp/terraform-website/master/LICENSE.md'
615689 ], [
616690 'Twig',
617− '2009-2017 The Twig Team',
691+ '2009-2018 The Twig Team',
618692 'BSD',
619− 'https://twig.sensiolabs.org/license'
693+ 'https://twig.symfony.com/license'
620694 ], [
621695 'TypeScript',
622696 'Microsoft and other contributors',
@@ -624,17 +698,17 @@ credits = [ · 1 unmodified line
624698 'https://raw.githubusercontent.com/Microsoft/TypeScript-Handbook/master/LICENSE'
625699 ], [
626700 'Underscore.js',
627− '2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors',
701+ '2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors',
628702 'MIT',
629703 'https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE'
630704 ], [
631705 'Vagrant',
632− '2010-2017 Mitchell Hashimoto',
706+ '2010-2018 Mitchell Hashimoto',
633707 'MPL',
634708 'https://raw.githubusercontent.com/mitchellh/vagrant/master/website/LICENSE.md'
635709 ], [
636710 'Vue.js',
637− '2013-2017 Evan You, Vue.js contributors',
711+ '2013-2018 Evan You, Vue.js contributors',
638712 'MIT',
639713 'https://raw.githubusercontent.com/vuejs/vue/master/LICENSE'
640714 ], [
@@ -649,7 +723,7 @@ credits = [ · 8 unmodified lines
649723 'https://creativecommons.org/licenses/by/4.0/'
650724 ], [
651725 'Yarn',
652− '2016-2017 Yarn Contributors',
726+ '2016-present Yarn Contributors',
653727 'BSD',
654728 'https://raw.githubusercontent.com/yarnpkg/yarn/master/LICENSE'
655729 ], [
assets/javascripts/templates/pages/help_tmpl.coffee+6−5
@@ -20,7 +20,7 @@ app.templates.helpPage = -> · 19 unmodified lines
2020 </ul>
2121 </nav>
2222
23− <h1 class="_lined-heading">User Guide</h2>
23+ <h1 class="_lined-heading">User Guide</h1>
2424
2525 <h2 class="_block-heading" id="managing-documentations">Managing Documentations</h2>
2626 <p>
@@ -57,9 +57,7 @@ app.templates.helpPage = -> · 30 unmodified lines
5757 <ul>
5858 <li>On Chrome, the setup is done automatically. Simply press <code class="_label">tab</code> when devdocs.io is autocompleted
5959 in the omnibox (to set a custom keyword, click <em>Manage search engines\u2026</em> in Chrome's settings).
60− <li>On Firefox, open the search engine list (icon in the search bar) and click <em>Add "DevDocs Search"</em>.
61− DevDocs is now available in the search bar. You can also search from the location bar by following
62− <a href="https://support.mozilla.org/en-US/kb/how-search-from-address-bar">these instructions</a>.
60+ <li>On Firefox, right-click the DevDocs search field and select <em>Add a Keyword for this Search…</em>. Then, type the added keyword followed by a query in the address bar to search DevDocs.
6361 </dl>
6462 <p>
6563 <i>Note: the above search features only work for documentations that are enabled.</i>
@@ -133,6 +131,9 @@ app.templates.helpPage = -> · 67 unmodified lines
133131 <dt class="_shortcuts-dt">
134132 <code class="_shortcut-code">alt + s</code>
135133 <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
136137 </dl>
137138 <p class="_note _note-green">
138139 <strong>Tip:</strong> If the cursor is no longer in the search field, press <code class="_label">/</code> or
@@ -153,5 +154,5 @@ app.templates.helpPage = -> · 14 unmodified lines
153154 #{("<tr><td class=\"_code\">#{key}<td class=\"_code\">#{value}" for key, value of aliases_two).join('')}
154155 </table>
155156 </div>
156− <p>Feel free to suggest new aliases on <a href="https://github.com/Thibaut/devdocs/issues/new">GitHub</a>.
157+ <p>Feel free to suggest new aliases on <a href="https://github.com/freeCodeCamp/devdocs/issues/new">GitHub</a>.
157158 """
assets/javascripts/templates/pages/news_tmpl.coffee.erb+1−1
@@ -4,7 +4,7 @@ app.templates.newsPage = -> · 3 unmodified lines
44 """ <h1 class="_lined-heading">Changelog</h1>
55 <p class="_note">
66 For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.<br>
7− For development updates, follow the project on <a href="https://github.com/Thibaut/devdocs">GitHub</a>.
7+ For development updates, follow the project on <a href="https://github.com/freeCodeCamp/devdocs">GitHub</a>.
88 <div class="_news">#{app.templates.newsList app.news}</div> """
99
1010 app.templates.newsList = (news, options = {}) ->
assets/javascripts/templates/pages/offline_tmpl.coffee+10−5
@@ -25,14 +25,14 @@ app.templates.offlinePage = (docs) -> """ · 24 unmodified lines
2525 <h2 class="_block-heading">Questions & Answers</h2>
2626 <dl>
2727 <dt>How does this work?
28− <dd>Each page is cached as a key-value pair in <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> (downloaded from a single file).<br>
29− The app also uses <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache">AppCache</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a> to cache the assets and index files.
28+ <dd>Each page is cached as a key-value pair in <a href="https://devdocs.io/dom/indexeddb_api">IndexedDB</a> (downloaded from a single file).<br>
29+ The app also uses <a href="https://devdocs.io/dom/service_worker_api/using_service_workers">Service Workers</a> and <a href="https://devdocs.io/dom/web_storage_api">localStorage</a> to cache the assets and index files.
3030 <dt>Can I close the tab/browser?
3131 <dd>#{canICloseTheTab()}
3232 <dt>What if I don't update a documentation?
3333 <dd>You'll see outdated content and some pages will be missing or broken, because the rest of the app (including data for the search and sidebar) uses a different caching mechanism that's updated automatically.
3434 <dt>I found a bug, where do I report it?
35− <dd>In the <a href="https://github.com/Thibaut/devdocs/issues">issue tracker</a>. Thanks!
35+ <dd>In the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>. Thanks!
3636 <dt>How do I uninstall/reset the app?
3737 <dd>Click <a href="#" data-behavior="reset">here</a>.
3838 <dt>Why aren't all documentations listed above?
@@ -41,10 +41,15 @@ app.templates.offlinePage = (docs) -> """ · 2 unmodified lines
4141 """
4242
4343 canICloseTheTab = ->
44− if app.AppCache.isEnabled()
44+ if app.ServiceWorker.isEnabled()
4545 """ 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). """
4646 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>
4853 The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """
4954
5055 app.templates.offlineDoc = (doc, status) ->
assets/javascripts/templates/pages/root_tmpl.coffee.erb+11−13
@@ -8,18 +8,17 @@ app.templates.intro = """ · 7 unmodified lines
88 <p>Thanks for downloading DevDocs. Here are a few things you should know:
99 <ol class="_intro-list">
1010 <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="http://devdocs.io">devdocs.io</a>.
11+ we&nbsp;recommend using the hosted version at <a href="https://devdocs.io">devdocs.io</a>.
1212 <li>Run <code>thor docs:list</code> to see all available documentations.
1313 <li>Run <code>thor docs:download &lt;name&gt;</code> to download documentations.
1414 <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/Thibaut/devdocs/subscription">watch the repository</a> on GitHub.
16− <li>The <a href="https://github.com/Thibaut/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>.
18− <li>Contributions are welcome. See the <a href="https://github.com/Thibaut/devdocs/blob/master/CONTRIBUTING.md">guidelines</a>.
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 and
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>.
1919 <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/Thibaut/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a> and
21− <a href="https://github.com/Thibaut/devdocs/blob/master/LICENSE">LICENSE</a> files.
22− <li>If you like the app, please consider supporting the project on <a href="https://gratipay.com/devdocs/">Gratipay</a>. Thanks!
20+ see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a> and
21+ <a href="https://github.com/freeCodeCamp/devdocs/blob/master/LICENSE">LICENSE</a> files.
2322 </ol>
2423 <p>Happy coding!
2524 </div></div>
@@ -39,9 +38,8 @@ app.templates.intro = """ · 13 unmodified lines
3938 <li>You can search using your browser's address bar &mdash; <a href="/help#browser_search">learn how</a>.
4039 <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>.
4140 <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.
42− <li>DevDocs is free and <a href="https://github.com/Thibaut/devdocs">open source</a>.
43− <iframe class="_github-btn" src="//ghbtns.com/github-btn.html?user=Thibaut&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe>
44− <li>If you like the app, please consider supporting the project on <a href="https://gratipay.com/devdocs/">Gratipay</a>. Thanks!
41+ <li>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>.
42+ <object data="https://img.shields.io/github/stars/freeCodeCamp/devdocs.svg?style=social" type="image/svg+xml" aria-hidden="true"></object>
4543 <li>And if you're new to coding, check out <a href="https://www.freecodecamp.org/">freeCodeCamp's open source curriculum</a>.
4644 </ol>
4745 <p>Happy coding!
@@ -59,7 +57,7 @@ app.templates.mobileIntro = """ · 11 unmodified lines
5957 <li>The search supports fuzzy matching.
6058 <li>To search a specific documentation, type its name (or an abbr.), then Space.
6159 <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.
62− <li>DevDocs is <a href="https://github.com/Thibaut/devdocs">open source</a>.
60+ <li>DevDocs is <a href="https://github.com/freeCodeCamp/devdocs">open source</a>.
6361 </ol>
6462 <p>Happy coding!
6563 <a class="_intro-hide" data-hide-intro>Stop showing this message</a>
@@ -71,6 +69,6 @@ app.templates.androidWarning = """ · 5 unmodified lines
7169 <h2 class="_intro-title">Hi there</h2>
7270 <p>DevDocs is running inside an Android WebView. Some features may not work properly.
7371 <p>If you downloaded an app called DevDocs on the Play Store, please uninstall it — it's made by someone who is using (and profiting from) the name DevDocs without permission.
74− <p>To install DevDocs on your phone, visit <a href="http://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.
7573 </div>
7674 """
assets/javascripts/templates/pages/settings_tmpl.coffee+8−0
@@ -15,6 +15,14 @@ app.templates.settingsPage = (settings) -> """ · 14 unmodified lines
1515 <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar
1616 <small>Tip: drag the edge of the sidebar to resize it.</small>
1717 </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>
1826 </div>
1927 </div>
2028
assets/javascripts/tracking.js+27−23
@@ -1,28 +1,32 @@
11 try {
2− if (app.config.env == 'production') {
3− (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
4− (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
5− m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
6− })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
7− ga('create', 'UA-5544833-12', 'devdocs.io');
8− page.track(function() {
9− ga('send', 'pageview', {
10− page: location.pathname + location.search + location.hash,
11− dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version
2+ if (app.config.env === 'production') {
3+ if (Cookies.get('analyticsConsent') === '1') {
4+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
5+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
6+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
7+ })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
8+ ga('create', 'UA-5544833-12', 'devdocs.io');
9+ page.track(function() {
10+ ga('send', 'pageview', {
11+ page: location.pathname + location.search + location.hash,
12+ dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version
13+ });
1214 });
13− });
1415
15− page.track(function() {
16− if (window._gauges)
17− _gauges.push(['track']);
18− else
19− (function() {
20− var _gauges=_gauges||[];!function(){var a=document.createElement("script");
21− a.type="text/javascript",a.async=!0,a.id="gauges-tracker",
22− a.setAttribute("data-site-id","51c15f82613f5d7819000067"),
23− a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];
24− b.parentNode.insertBefore(a,b)}();
25− })();
26− });
16+ page.track(function() {
17+ if (window._gauges)
18+ _gauges.push(['track']);
19+ else
20+ (function() {
21+ var _gauges=_gauges||[];!function(){var a=document.createElement("script");
22+ a.type="text/javascript",a.async=!0,a.id="gauges-tracker",
23+ a.setAttribute("data-site-id","51c15f82613f5d7819000067"),
24+ a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];
25+ b.parentNode.insertBefore(a,b)}();
26+ })();
27+ });
28+ } else {
29+ resetAnalytics();
30+ }
2731 }
2832 } catch(e) { }
assets/javascripts/vendor/fastclick.js+2−2
@@ -413,7 +413,7 @@ · 412 unmodified lines
413413 // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
414414 // with the same identifier as the touch event that previously triggered the click that triggered the alert.
415415 // 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.
417417 // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,
418418 // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,
419419 // random integers, it's safe to to continue if the identifier is 0 here.
@@ -805,7 +805,7 @@ · 385 unmodified lines
805805 }
806806 }
807807
808− // IE11: prefixed -ms-touch-action is no longer supported and it's recomended to use non-prefixed version
808+ // IE11: prefixed -ms-touch-action is no longer supported and it's recommended to use non-prefixed version
809809 // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx
810810 if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {
811811 return true;
assets/javascripts/vendor/prism.js+879−417
@@ -1,4 +1,5 @@
1−/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+c+cpp+coffeescript+ruby+d+elixir+erlang+go+java+json+kotlin+lua+nginx+nim+perl+php+python+jsx+crystal+rust+scss+sql+typescript */
1+/* PrismJS 1.15.0
2+https://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 */
23 var _self = (typeof window !== 'undefined')
34 ? window // if in browser
45 : (
@@ -16,11 +17,12 @@ var _self = (typeof window !== 'undefined') · 11 unmodified lines
1617 var Prism = (function(){
1718
1819 // Private helper vars
19−var lang = /\blang(?:uage)?-(\w+)\b/i;
20+var lang = /\blang(?:uage)?-([\w-]+)\b/i;
2021 var uniqueId = 0;
2122
2223 var _ = _self.Prism = {
2324 manual: _self.Prism && _self.Prism.manual,
25+ disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
2426 util: {
2527 encode: function (tokens) {
2628 if (tokens instanceof Token) {
@@ -44,23 +46,38 @@ var _ = _self.Prism = { · 17 unmodified lines
4446 },
4547
4648 // Deep clone a language definition (e.g. to extend it)
47− clone: function (o) {
49+ clone: function (o, visited) {
4850 var type = _.util.type(o);
51+ visited = visited || {};
4952
5053 switch (type) {
5154 case 'Object':
55+ if (visited[_.util.objId(o)]) {
56+ return visited[_.util.objId(o)];
57+ }
5258 var clone = {};
59+ visited[_.util.objId(o)] = clone;
5360
5461 for (var key in o) {
5562 if (o.hasOwnProperty(key)) {
56− clone[key] = _.util.clone(o[key]);
63+ clone[key] = _.util.clone(o[key], visited);
5764 }
5865 }
5966
6067 return clone;
6168
6269 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;
6481 }
6582
6683 return o;
@@ -155,6 +172,10 @@ var _ = _self.Prism = { · 88 unmodified lines
155172 plugins: {},
156173
157174 highlightAll: function(async, callback) {
175+ _.highlightAllUnder(document, async, callback);
176+ },
177+
178+ highlightAllUnder: function(container, async, callback) {
158179 var env = {
159180 callback: callback,
160181 selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
@@ -162,7 +183,7 @@ var _ = _self.Prism = { · 1 unmodified line
162183
163184 _.hooks.run("before-highlightall", env);
164185
165− var elements = env.elements || document.querySelectorAll(env.selector);
186+ var elements = env.elements || container.querySelectorAll(env.selector);
166187
167188 for (var i=0, element; element = elements[i++];) {
168189 _.highlightElement(element, async === true, env.callback);
@@ -185,11 +206,13 @@ var _ = _self.Prism = { · 16 unmodified lines
185206 // Set language on the element, if not present
186207 element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
187208
188− // Set language on the parent, for styling
189− parent = element.parentNode;
209+ if (element.parentNode) {
210+ // Set language on the parent, for styling
211+ parent = element.parentNode;
190212
191− if (/pre/i.test(parent.nodeName)) {
192− parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
213+ if (/pre/i.test(parent.nodeName)) {
214+ parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
215+ }
193216 }
194217
195218 var code = element.textContent;
@@ -251,8 +274,15 @@ var _ = _self.Prism = { · 55 unmodified lines
251274 },
252275
253276 highlight: function (text, grammar, language) {
254− var tokens = _.tokenize(text, grammar);
255− return Token.stringify(_.util.encode(tokens), language);
277+ var env = {
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);
256286 },
257287
258288 matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
@@ -300,15 +330,9 @@ var _ = _self.Prism = { · 41 unmodified lines
300330 continue;
301331 }
302332
303− pattern.lastIndex = 0;
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) {
333+ if (greedy && i != strarr.length - 1) {
310334 pattern.lastIndex = pos;
311− match = pattern.exec(text);
335+ var match = pattern.exec(text);
312336 if (!match) {
313337 break;
314338 }
@@ -327,11 +351,8 @@ var _ = _self.Prism = { · 12 unmodified lines
327351 }
328352 }
329353
330− /*
331− * If strarr[i] is a Token, then the match starts inside another Token, which is invalid
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) {
354+ // If strarr[i] is a Token, then the match starts inside another Token, which is invalid
355+ if (strarr[i] instanceof Token) {
335356 continue;
336357 }
337358
@@ -339,6 +360,11 @@ var _ = _self.Prism = { · 1 unmodified line
339360 delNum = k - i;
340361 str = text.slice(pos, p);
341362 match.index -= pos;
363+ } else {
364+ pattern.lastIndex = 0;
365+
366+ var match = pattern.exec(str),
367+ delNum = 1;
342368 }
343369
344370 if (!match) {
@@ -350,7 +376,7 @@ var _ = _self.Prism = { · 5 unmodified lines
350376 }
351377
352378 if(lookbehind) {
353− lookbehindLength = match[1].length;
379+ lookbehindLength = match[1] ? match[1].length : 0;
354380 }
355381
356382 var from = match.index + lookbehindLength,
@@ -460,10 +486,6 @@ Token.stringify = function(o, language, parent) { · 103 unmodified lines
460486 parent: parent
461487 };
462488
463− if (env.type == 'comment') {
464− env.attributes['spellcheck'] = 'true';
465− }
466−
467489 if (o.alias) {
468490 var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
469491 Array.prototype.push.apply(env.classes, aliases);
@@ -484,18 +506,21 @@ Token.stringify = function(o, language, parent) { · 14 unmodified lines
484506 // // in Node.js
485507 // return _self.Prism;
486508 // }
487−// // In worker
488−// _self.addEventListener('message', function(evt) {
489−// var message = JSON.parse(evt.data),
490−// lang = message.language,
491−// code = message.code,
492−// immediateClose = message.immediateClose;
493−
494−// _self.postMessage(_.highlight(code, _.languages[lang], lang));
495−// if (immediateClose) {
496−// _self.close();
497−// }
498−// }, false);
509+
510+// if (!_.disableWorkerMessageHandler) {
511+// // In worker
512+// _self.addEventListener('message', function (evt) {
513+// var message = JSON.parse(evt.data),
514+// lang = message.language,
515+// code = message.code,
516+// immediateClose = message.immediateClose;
517+
518+// _self.postMessage(_.highlight(code, _.languages[lang], lang));
519+// if (immediateClose) {
520+// _self.close();
521+// }
522+// }, false);
523+// }
499524
500525 // return _self.Prism;
501526 // }
@@ -539,7 +564,8 @@ Prism.languages.markup = { · 37 unmodified lines
539564 'doctype': /<!DOCTYPE[\s\S]+?>/i,
540565 'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,
541566 'tag': {
542− pattern: /<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\s\S])*\1|[^\s'">=]+))?)*\s*\/?>/i,
567+ pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,
568+ greedy: true,
543569 inside: {
544570 'tag': {
545571 pattern: /^<\/?[^\s>\/]+/i,
@@ -549,9 +575,15 @@ Prism.languages.markup = { · 3 unmodified lines
549575 }
550576 },
551577 'attr-value': {
552− pattern: /=(?:('|")[\s\S]*?(\1)|[^\s>]+)/i,
578+ pattern: /=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i,
553579 inside: {
554− 'punctuation': /[=>"']/
580+ 'punctuation': [
581+ /^=/,
582+ {
583+ pattern: /(^|[^\\])["']/,
584+ lookbehind: true
585+ }
586+ ]
555587 }
556588 },
557589 'punctuation': /\/?>/,
@@ -586,25 +618,25 @@ Prism.languages.svg = Prism.languages.markup; · 28 unmodified lines
586618 Prism.languages.css = {
587619 'comment': /\/\*[\s\S]*?\*\//,
588620 'atrule': {
589− pattern: /@[\w-]+?.*?(;|(?=\s*\{))/i,
621+ pattern: /@[\w-]+?.*?(?:;|(?=\s*\{))/i,
590622 inside: {
591623 'rule': /@[\w-]+/
592624 // See rest below
593625 }
594626 },
595− 'url': /url\((?:(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,
596− 'selector': /[^\{\}\s][^\{\};]*?(?=\s*\{)/,
627+ 'url': /url\((?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,
628+ 'selector': /[^{}\s][^{};]*?(?=\s*\{)/,
597629 'string': {
598− pattern: /("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
630+ pattern: /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
599631 greedy: true
600632 },
601− 'property': /(\b|\B)[\w-]+(?=\s*:)/i,
633+ 'property': /[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,
602634 'important': /\B!important\b/i,
603635 'function': /[-a-z0-9]+(?=\()/i,
604636 'punctuation': /[(){};:]/
605637 };
606638
607−Prism.languages.css['atrule'].inside.rest = Prism.util.clone(Prism.languages.css);
639+Prism.languages.css['atrule'].inside.rest = Prism.languages.css;
608640
609641 if (Prism.languages.markup) {
610642 Prism.languages.insertBefore('markup', 'tag', {
@@ -612,13 +644,14 @@ if (Prism.languages.markup) { · 1 unmodified line
612644 pattern: /(<style[\s\S]*?>)[\s\S]*?(?=<\/style>)/i,
613645 lookbehind: true,
614646 inside: Prism.languages.css,
615− alias: 'language-css'
647+ alias: 'language-css',
648+ greedy: true
616649 }
617650 });
618651
619652 Prism.languages.insertBefore('inside', 'attr-value', {
620653 'style-attr': {
621− pattern: /\s*style=("|').*?\1/i,
654+ pattern: /\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,
622655 inside: {
623656 'attr-name': {
624657 pattern: /^\s*style/i,
@@ -642,63 +675,71 @@ Prism.languages.clike = { · 17 unmodified lines
642675 },
643676 {
644677 pattern: /(^|[^\\:])\/\/.*/,
645− lookbehind: true
678+ lookbehind: true,
679+ greedy: true
646680 }
647681 ],
648682 'string': {
649− pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
683+ pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
650684 greedy: true
651685 },
652686 'class-name': {
653− pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,
687+ pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,
654688 lookbehind: true,
655689 inside: {
656− punctuation: /(\.|\\)/
690+ punctuation: /[.\\]/
657691 }
658692 },
659− 'keyword': /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
660− 'boolean': /\b(true|false)\b/,
693+ 'keyword': /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
694+ 'boolean': /\b(?:true|false)\b/,
661695 'function': /[a-z0-9_]+(?=\()/i,
662− '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,
663697 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,
664698 'punctuation': /[{}[\];(),.:]/
665699 };
666700
667701 Prism.languages.javascript = Prism.languages.extend('clike', {
668− '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/,
669− 'number': /\b-?(0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\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/,
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+)?/,
670704 // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
671− 'function': /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,
705+ 'function': /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\()/i,
672706 'operator': /-[-=]?|\+[+=]?|!=?=?|<<?=?|>>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/
673707 });
674708
675709 Prism.languages.insertBefore('javascript', 'keyword', {
676710 'regex': {
677− 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,.;})\]]))/,
678712 lookbehind: true,
679713 greedy: true
680− }
714+ },
715+ // This must be declared before keyword because we use "function" inside the look-forward
716+ 'function-variable': {
717+ pattern: /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=\s*(?:function\b|(?:\([^()]*\)|[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/i,
718+ alias: 'function'
719+ },
720+ 'constant': /\b[A-Z][A-Z\d_]*\b/
681721 });
682722
683723 Prism.languages.insertBefore('javascript', 'string', {
684724 'template-string': {
685− pattern: /`(?:\\\\|\\?[^\\])*?`/,
725+ pattern: /`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,
686726 greedy: true,
687727 inside: {
688728 'interpolation': {
689− pattern: /\$\{[^}]+\}/,
729+ pattern: /\${[^}]+}/,
690730 inside: {
691731 'interpolation-punctuation': {
692− pattern: /^\$\{|\}$/,
732+ pattern: /^\${|}$/,
693733 alias: 'punctuation'
694734 },
695− rest: Prism.languages.javascript
735+ rest: null // See below
696736 }
697737 },
698738 'string': /[\s\S]+/
699739 }
700740 }
701741 });
742+Prism.languages.javascript['template-string'].inside['interpolation'].inside.rest = Prism.languages.javascript;
702743
703744 if (Prism.languages.markup) {
704745 Prism.languages.insertBefore('markup', 'tag', {
@@ -706,7 +747,8 @@ if (Prism.languages.markup) { · 1 unmodified line
706747 pattern: /(<script[\s\S]*?>)[\s\S]*?(?=<\/script>)/i,
707748 lookbehind: true,
708749 inside: Prism.languages.javascript,
709− alias: 'language-javascript'
750+ alias: 'language-javascript',
751+ greedy: true
710752 }
711753 });
712754 }
@@ -714,51 +756,145 @@ if (Prism.languages.markup) { · 1 unmodified line
714756 Prism.languages.js = Prism.languages.javascript;
715757
716758 Prism.languages.c = Prism.languages.extend('clike', {
717− '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/,
718− 'operator': /\-[>-]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|?\||[~^%?*\/]/,
719− 'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i
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/,
760+ 'operator': /-[>-]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/]/,
761+ 'number': /(?:\b0x[\da-f]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?)[ful]*/i
720762 });
721763
722764 Prism.languages.insertBefore('c', 'string', {
723765 'macro': {
724766 // allow for multiline macro definitions
725767 // spaces after the # character compile fine with gcc
726− pattern: /(^\s*)#\s*[a-z]+([^\r\n\\]|\\.|\\(?:\r\n?|\n))*/im,
768+ pattern: /(^\s*)#\s*[a-z]+(?:[^\r\n\\]|\\(?:\r\n|[\s\S]))*/im,
727769 lookbehind: true,
728770 alias: 'property',
729771 inside: {
730772 // highlight the path of the include statement as a string
731773 'string': {
732− pattern: /(#\s*include\s*)(<.+?>|("|')(\\?.)+?\3)/,
774+ pattern: /(#\s*include\s*)(?:<.+?>|("|')(?:\\?.)+?\2)/,
733775 lookbehind: true
734776 },
735777 // highlight macro directives as keywords
736778 'directive': {
737− pattern: /(#\s*)\b(define|defined|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,
779+ pattern: /(#\s*)\b(?:define|defined|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,
738780 lookbehind: true,
739781 alias: 'keyword'
740782 }
741783 }
742784 },
743785 // highlight predefined macros as constants
744− 'constant': /\b(__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/
786+ 'constant': /\b(?:__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/
745787 });
746788
747789 delete Prism.languages.c['class-name'];
748790 delete Prism.languages.c['boolean'];
749791
792+(function(Prism) {
793+ var insideString = {
794+ variable: [
795+ // Arithmetic Environment
796+ {
797+ pattern: /\$?\(\([\s\S]+?\)\)/,
798+ inside: {
799+ // If there is a $ sign at the beginning highlight $(( and )) as variable
800+ variable: [{
801+ pattern: /(^\$\(\([\s\S]+)\)\)/,
802+ lookbehind: true
803+ },
804+ /^\$\(\(/
805+ ],
806+ number: /\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee]-?\d+)?/,
807+ // Operators according to https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic
808+ operator: /--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,
809+ // If there is no $ sign at the beginning highlight (( and )) as punctuation
810+ punctuation: /\(\(?|\)\)?|,|;/
811+ }
812+ },
813+ // Command Substitution
814+ {
815+ pattern: /\$\([^)]+\)|`[^`]+`/,
816+ greedy: true,
817+ inside: {
818+ variable: /^\$\(|^`|\)$|`$/
819+ }
820+ },
821+ /\$(?:[\w#?*!@]+|\{[^}]+\})/i
822+ ]
823+ };
824+
825+ Prism.languages.bash = {
826+ 'shebang': {
827+ pattern: /^#!\s*\/bin\/bash|^#!\s*\/bin\/sh/,
828+ alias: 'important'
829+ },
830+ 'comment': {
831+ pattern: /(^|[^"{\\])#.*/,
832+ lookbehind: true
833+ },
834+ 'string': [
835+ //Support for Here-Documents https://en.wikipedia.org/wiki/Here_document
836+ {
837+ pattern: /((?:^|[^<])<<\s*)["']?(\w+?)["']?\s*\r?\n(?:[\s\S])*?\r?\n\2/,
838+ lookbehind: true,
839+ greedy: true,
840+ inside: insideString
841+ },
842+ {
843+ pattern: /(["'])(?:\\[\s\S]|\$\([^)]+\)|`[^`]+`|(?!\1)[^\\])*\1/,
844+ greedy: true,
845+ inside: insideString
846+ }
847+ ],
848+ 'variable': insideString.variable,
849+ // Originally based on http://ss64.com/bash/
850+ 'function': {
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;|&])/,
852+ lookbehind: true
853+ },
854+ 'keyword': {
855+ pattern: /(^|[\s;|&])(?:let|:|\.|if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)(?=$|[\s;|&])/,
856+ lookbehind: true
857+ },
858+ 'boolean': {
859+ pattern: /(^|[\s;|&])(?:true|false)(?=$|[\s;|&])/,
860+ lookbehind: true
861+ },
862+ 'operator': /&&?|\|\|?|==?|!=?|<<<?|>>|<=?|>=?|=~/,
863+ 'punctuation': /\$?\(\(?|\)\)?|\.\.|[{}[\];]/
864+ };
865+
866+ var inside = insideString.variable[1].inside;
867+ inside.string = Prism.languages.bash.string;
868+ inside['function'] = Prism.languages.bash['function'];
869+ inside.keyword = Prism.languages.bash.keyword;
870+ inside['boolean'] = Prism.languages.bash['boolean'];
871+ inside.operator = Prism.languages.bash.operator;
872+ inside.punctuation = Prism.languages.bash.punctuation;
873+
874+ Prism.languages.shell = Prism.languages.bash;
875+})(Prism);
876+
750877 Prism.languages.cpp = Prism.languages.extend('c', {
751− '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/,
752− 'boolean': /\b(true|false)\b/,
753− 'operator': /[-+]{1,2}|!=?|<{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/
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/,
879+ 'boolean': /\b(?:true|false)\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/
754881 });
755882
756883 Prism.languages.insertBefore('cpp', 'keyword', {
757884 'class-name': {
758− pattern: /(class\s+)[a-z0-9_]+/i,
885+ pattern: /(class\s+)\w+/i,
759886 lookbehind: true
760887 }
761888 });
889+
890+Prism.languages.insertBefore('cpp', 'string', {
891+ 'raw-string': {
892+ pattern: /R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,
893+ alias: 'string',
894+ greedy: true
895+ }
896+});
897+
762898 (function(Prism) {
763899
764900 // Ignore comments starting with { to privilege string interpolation highlighting
@@ -774,20 +910,20 @@ Prism.languages.coffeescript = Prism.languages.extend('javascript', { · 9 unmodified lines
774910
775911 // Strings are multiline
776912 {
777− pattern: /'(?:\\?[^\\])*?'/,
913+ pattern: /'(?:\\[\s\S]|[^\\'])*'/,
778914 greedy: true
779915 },
780916
781917 {
782918 // Strings are multiline
783− pattern: /"(?:\\?[^\\])*?"/,
919+ pattern: /"(?:\\[\s\S]|[^\\"])*"/,
784920 greedy: true,
785921 inside: {
786922 'interpolation': interpolation
787923 }
788924 }
789925 ],
790− 'keyword': /\b(and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,
926+ 'keyword': /\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,
791927 'class-member': {
792928 pattern: /@(?!\d)\w+/,
793929 alias: 'variable'
@@ -813,7 +949,7 @@ Prism.languages.insertBefore('coffeescript', 'comment', { · 19 unmodified lines
813949
814950 Prism.languages.insertBefore('coffeescript', 'string', {
815951 'inline-javascript': {
816− pattern: /`(?:\\?[\s\S])*?`/,
952+ pattern: /`(?:\\[\s\S]|[^\\`])*`/,
817953 inside: {
818954 'delimiter': {
819955 pattern: /^`|`$/,
@@ -859,10 +995,13 @@ delete Prism.languages.coffeescript['template-string']; · 39 unmodified lines
859995 (function(Prism) {
860996 Prism.languages.ruby = Prism.languages.extend('clike', {
861997 'comment': [
862− /#(?!\{[^\r\n]*?\}).*/,
863− /^=begin(?:\r?\n|\r)(?:.*(?:\r?\n|\r))*?=end/m
998+ /#.*/,
999+ {
1000+ pattern: /^=begin(?:\r?\n|\r)(?:.*(?:\r?\n|\r))*?=end/m,
1001+ greedy: true
1002+ }
8641003 ],
865− '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/
8661005 });
8671006
8681007 var interpolation = {
@@ -872,14 +1011,14 @@ delete Prism.languages.coffeescript['template-string']; · 3 unmodified lines
8721011 pattern: /^#\{|\}$/,
8731012 alias: 'tag'
8741013 },
875− rest: Prism.util.clone(Prism.languages.ruby)
1014+ rest: Prism.languages.ruby
8761015 }
8771016 };
8781017
8791018 Prism.languages.insertBefore('ruby', 'keyword', {
8801019 'regex': [
8811020 {
882− pattern: /%r([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1[gim]{0,3}/,
1021+ pattern: /%r([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[gim]{0,3}/,
8831022 greedy: true,
8841023 inside: {
8851024 'interpolation': interpolation
@@ -920,18 +1059,21 @@ delete Prism.languages.coffeescript['template-string']; · 34 unmodified lines
9201059 greedy: true
9211060 }
9221061 ],
923− 'variable': /[@$]+[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/,
924− 'symbol': /:[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/
1062+ 'variable': /[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,
1063+ 'symbol': {
1064+ pattern: /(^|[^:]):[a-zA-Z_]\w*(?:[?!]|\b)/,
1065+ lookbehind: true
1066+ }
9251067 });
9261068
9271069 Prism.languages.insertBefore('ruby', 'number', {
928− 'builtin': /\b(Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|File|Fixnum|Float|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,
929− 'constant': /\b[A-Z][a-zA-Z_0-9]*(?:[?!]|\b)/
1070+ 'builtin': /\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|Fixnum|Float|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,
1071+ 'constant': /\b[A-Z]\w*(?:[?!]|\b)/
9301072 });
9311073
9321074 Prism.languages.ruby.string = [
9331075 {
934− pattern: /%[qQiIwWxs]?([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1/,
1076+ pattern: /%[qQiIwWxs]?([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,
9351077 greedy: true,
9361078 inside: {
9371079 'interpolation': interpolation
@@ -967,7 +1109,7 @@ delete Prism.languages.coffeescript['template-string']; · 29 unmodified lines
9671109 }
9681110 },
9691111 {
970− pattern: /("|')(#\{[^}]+\}|\\(?:\r?\n|\r)|\\?.)*?\1/,
1112+ pattern: /("|')(?:#\{[^}]+\}|\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
9711113 greedy: true,
9721114 inside: {
9731115 'interpolation': interpolation
@@ -978,7 +1120,7 @@ delete Prism.languages.coffeescript['template-string']; · 4 unmodified lines
9781120 Prism.languages.d = Prism.languages.extend('clike', {
9791121 'string': [
9801122 // r"", x""
981− /\b[rx]"(\\.|[^\\"])*"[cwd]?/,
1123+ /\b[rx]"(?:\\[\s\S]|[^\\"])*"[cwd]?/,
9821124 // q"[]", q"()", q"<>", q"{}"
9831125 /\bq"(?:\[[\s\S]*?\]|\([\s\S]*?\)|<[\s\S]*?>|\{[\s\S]*?\})"/,
9841126 // q"IDENT
@@ -990,7 +1132,7 @@ Prism.languages.d = Prism.languages.extend('clike', { · 5 unmodified lines
9901132 // Characters
9911133 /'(?:\\'|\\?[^']+)'/,
9921134
993− /(["`])(\\.|(?!\1)[^\\])*\1[cwd]?/
1135+ /(["`])(?:\\[\s\S]|(?!\1)[^\\])*\1[cwd]?/
9941136 ],
9951137
9961138 'number': [
@@ -1023,7 +1165,7 @@ Prism.languages.d.comment = [ · 26 unmodified lines
10231165 Prism.languages.insertBefore('d', 'comment', {
10241166 'token-string': {
10251167 // Allow one level of nesting
1026− pattern: /\bq\{(?:|\{[^}]*\}|[^}])*\}/,
1168+ pattern: /\bq\{(?:\{[^}]*\}|[^}])*\}/,
10271169 alias: 'string'
10281170 }
10291171 });
@@ -1039,20 +1181,86 @@ Prism.languages.insertBefore('d', 'function', { · 9 unmodified lines
10391181 alias: 'variable'
10401182 }
10411183 });
1184+Prism.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+
1202+Prism.languages.insertBefore('dart','function',{
1203+ 'metadata': {
1204+ pattern: /@\w+/,
1205+ alias: 'symbol'
1206+ }
1207+});
1208+// Django/Jinja2 syntax definition for Prism.js <http://prismjs.com> syntax highlighter.
1209+// Mostly it works OK but can paint code incorrectly on complex html/template tag combinations.
1210+
1211+var _django_template = {
1212+ 'property': {
1213+ pattern: /(?:{{|{%)[\s\S]*?(?:%}|}})/g,
1214+ greedy: true,
1215+ inside: {
1216+ 'string': {
1217+ pattern: /("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,
1218+ greedy: true
1219+ },
1220+ 'keyword': /\b(?:\||load|verbatim|widthratio|ssi|firstof|for|url|ifchanged|csrf_token|lorem|ifnotequal|autoescape|now|templatetag|debug|cycle|ifequal|regroup|comment|filter|endfilter|if|spaceless|with|extends|block|include|else|empty|endif|endfor|as|endblock|endautoescape|endverbatim|trans|endtrans|[Tt]rue|[Ff]alse|[Nn]one|in|is|static|macro|endmacro|call|endcall|set|endset|raw|endraw)\b/,
1221+ 'operator' : /[-+=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,
1222+ 'function': /\b(?:_|abs|add|addslashes|attr|batch|callable|capfirst|capitalize|center|count|cut|d|date|default|default_if_none|defined|dictsort|dictsortreversed|divisibleby|e|equalto|escape|escaped|escapejs|even|filesizeformat|first|float|floatformat|force_escape|forceescape|format|get_digit|groupby|indent|int|iriencode|iterable|join|last|length|length_is|linebreaks|linebreaksbr|linenumbers|list|ljust|lower|make_list|map|mapping|number|odd|phone2numeric|pluralize|pprint|random|reject|rejectattr|removetags|replace|reverse|rjust|round|safe|safeseq|sameas|select|selectattr|sequence|slice|slugify|sort|string|stringformat|striptags|sum|time|timesince|timeuntil|title|trim|truncate|truncatechars|truncatechars_html|truncatewords|truncatewords_html|undefined|unordered_list|upper|urlencode|urlize|urlizetrunc|wordcount|wordwrap|xmlattr|yesno)\b/,
1223+ 'important': /\b-?\d+(?:\.\d+)?\b/,
1224+ 'variable': /\b\w+?\b/,
1225+ 'punctuation' : /[[\];(),.:]/
1226+ }
1227+ }
1228+};
1229+
1230+Prism.languages.django = Prism.languages.extend('markup', {'comment': /(?:<!--|{#)[\s\S]*?(?:#}|-->)/});
1231+// Updated html tag pattern to allow template tags inside html tags
1232+Prism.languages.django.tag.pattern = /<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^>=]+))?)*\s*\/?>/i;
1233+Prism.languages.insertBefore('django', 'entity', _django_template);
1234+Prism.languages.insertBefore('inside', 'tag', _django_template, Prism.languages.django.tag);
1235+
1236+if (Prism.languages.javascript) {
1237+ // Combine js code and template tags painting inside <script> blocks
1238+ Prism.languages.insertBefore('inside', 'string', _django_template, Prism.languages.django.script);
1239+ Prism.languages.django.script.inside.string.inside = _django_template;
1240+}
1241+if (Prism.languages.css) {
1242+ // Combine css code and template tags painting inside <style> blocks
1243+ Prism.languages.insertBefore('inside', 'atrule', {'tag': _django_template.property}, Prism.languages.django.style);
1244+ Prism.languages.django.style.inside.string.inside = _django_template;
1245+}
1246+
1247+// Add an Jinja2 alias
1248+Prism.languages.jinja2 = Prism.languages.django;
1249+
10421250 Prism.languages.elixir = {
1043− // Negative look-ahead is needed for string interpolation
1044− // Negative look-behind is needed to avoid highlighting markdown headers in
1045− // multi-line doc strings
10461251 'comment': {
1047− pattern: /(^|[^#])#(?![{#]).*/m,
1252+ pattern: /#.*/m,
10481253 lookbehind: true
10491254 },
1050− // ~r"""foo""", ~r'''foo''', ~r/foo/, ~r|foo|, ~r"foo", ~r'foo', ~r(foo), ~r[foo], ~r{foo}, ~r<foo>
1051− 'regex': /~[rR](?:("""|'''|[\/|"'])(?:\\.|(?!\1)[^\\])+\1|\((?:\\\)|[^)])+\)|\[(?:\\\]|[^\]])+\]|\{(?:\\\}|[^}])+\}|<(?:\\>|[^>])+>)[uismxfr]*/,
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>
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+ },
10521260 'string': [
10531261 {
1054− // ~s"""foo""", ~s'''foo''', ~s/foo/, ~s|foo|, ~s"foo", ~s'foo', ~s(foo), ~s[foo], ~s{foo}, ~s<foo>
1055− pattern: /~[cCsSwW](?:("""|'''|[\/|"'])(?:\\.|(?!\1)[^\\])+\1|\((?:\\\)|[^)])+\)|\[(?:\\\]|[^\]])+\]|\{(?:\\\}|#\{[^}]+\}|[^}])+\}|<(?:\\>|[^>])+>)[csa]?/,
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>
1263+ pattern: /~[cCsSwW](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|#\{[^}]+\}|[^\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[csa]?/,
10561264 greedy: true,
10571265 inside: {
10581266 // See interpolation below
@@ -1067,7 +1275,7 @@ Prism.languages.elixir = { · 8 unmodified lines
10671275 },
10681276 {
10691277 // Multi-line strings are allowed
1070− pattern: /("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/,
1278+ pattern: /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
10711279 greedy: true,
10721280 inside: {
10731281 // See interpolation below
@@ -1095,7 +1303,7 @@ Prism.languages.elixir = { · 21 unmodified lines
10951303 alias: 'variable'
10961304 },
10971305 'attribute': {
1098− pattern: /@[\S]+/,
1306+ pattern: /@\w+/,
10991307 alias: 'variable'
11001308 },
11011309 'number': /\b(?:0[box][a-f\d_]+|\d[\d_]*)(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?\b/i,
@@ -1126,25 +1334,114 @@ Prism.languages.elixir.string.forEach(function(o) { · 24 unmodified lines
11261334 pattern: /^#\{|\}$/,
11271335 alias: 'punctuation'
11281336 },
1129− rest: Prism.util.clone(Prism.languages.elixir)
1337+ rest: Prism.languages.elixir
11301338 }
11311339 }
11321340 };
11331341 });
11341342
11351343
1344+Prism.languages['markup-templating'] = {};
1345+
1346+Object.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+});
11361433 Prism.languages.erlang = {
11371434 'comment': /%.+/,
11381435 'string': {
1139− pattern: /"(?:\\?.)*?"/,
1436+ pattern: /"(?:\\.|[^\\"\r\n])*"/,
11401437 greedy: true
11411438 },
11421439 'quoted-function': {
1143− pattern: /'(?:\\.|[^'\\])+'(?=\()/,
1440+ pattern: /'(?:\\.|[^\\'\r\n])+'(?=\()/,
11441441 alias: 'function'
11451442 },
11461443 'quoted-atom': {
1147− pattern: /'(?:\\.|[^'\\])+'/,
1444+ pattern: /'(?:\\.|[^\\'\r\n])+'/,
11481445 alias: 'atom'
11491446 },
11501447 'boolean': /\b(?:true|false)\b/,
@@ -1152,7 +1449,7 @@ Prism.languages.erlang = { · 1 unmodified line
11521449 'number': [
11531450 /\$\\?./,
11541451 /\d+#[a-z0-9]+/i,
1155− /(?:\b|-)\d*\.?\d+([Ee][+-]?\d+)?\b/
1452+ /(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i
11561453 ],
11571454 'function': /\b[a-z][\w@]*(?=\()/,
11581455 'variable': {
@@ -1178,21 +1475,21 @@ Prism.languages.erlang = { · 19 unmodified lines
11781475
11791476 };
11801477 Prism.languages.go = Prism.languages.extend('clike', {
1181− 'keyword': /\b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,
1182− '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/,
1183− 'boolean': /\b(_|iota|nil|true|false)\b/,
1478+ 'keyword': /\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\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/,
1480+ 'boolean': /\b(?:_|iota|nil|true|false)\b/,
11841481 'operator': /[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,
1185− '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,
11861483 'string': {
1187− pattern: /("|'|`)(\\?.|\r|\n)*?\1/,
1484+ pattern: /(["'`])(\\[\s\S]|(?!\1)[^\\])*\1/,
11881485 greedy: true
11891486 }
11901487 });
11911488 delete Prism.languages.go['class-name'];
11921489
11931490 Prism.languages.java = Prism.languages.extend('clike', {
1194− '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/,
1195− 'number': /\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,
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/,
1492+ 'number': /\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp-]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?[df]?/i,
11961493 'operator': {
11971494 pattern: /(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<<?=?|>>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,
11981495 lookbehind: true
@@ -1207,14 +1504,28 @@ Prism.languages.insertBefore('java','function', { · 8 unmodified lines
12071504 }
12081505 });
12091506
1507+Prism.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+
12101518 Prism.languages.json = {
1211− 'property': /"(?:\\.|[^\\"])*"(?=\s*:)/ig,
1212− 'string': /"(?!:)(?:\\.|[^\\"])*"(?!:)/g,
1213− 'number': /\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?)\b/g,
1214− 'punctuation': /[{}[\]);,]/g,
1519+ 'property': /"(?:\\.|[^\\"\r\n])*"(?=\s*:)/i,
1520+ 'string': {
1521+ pattern: /"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,
1522+ greedy: true
1523+ },
1524+ 'number': /-?\d+\.?\d*([Ee][+-]?\d+)?/,
1525+ 'punctuation': /[{}[\],]/,
12151526 'operator': /:/g,
1216− 'boolean': /\b(true|false)\b/gi,
1217− 'null': /\bnull\b/gi
1527+ 'boolean': /\b(?:true|false)\b/i,
1528+ 'null': /\bnull\b/i
12181529 };
12191530
12201531 Prism.languages.jsonp = Prism.languages.json;
@@ -1223,7 +1534,7 @@ Prism.languages.jsonp = Prism.languages.json; · 2 unmodified lines
12231534 Prism.languages.kotlin = Prism.languages.extend('clike', {
12241535 'keyword': {
12251536 // The lookbehind prevents wrong highlighting of e.g. kotlin.properties.get
1226− 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/,
12271538 lookbehind: true
12281539 },
12291540 'function': [
@@ -1233,7 +1544,7 @@ Prism.languages.jsonp = Prism.languages.json; · 3 unmodified lines
12331544 lookbehind: true
12341545 }
12351546 ],
1236− '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/,
12371548 'operator': /\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/
12381549 });
12391550
@@ -1241,7 +1552,7 @@ Prism.languages.jsonp = Prism.languages.json; · 1 unmodified line
12411552
12421553 Prism.languages.insertBefore('kotlin', 'string', {
12431554 'raw-string': {
1244− pattern: /(["'])\1\1[\s\S]*?\1{3}/,
1555+ pattern: /("""|''')[\s\S]*?\1/,
12451556 alias: 'string'
12461557 // See interpolation below
12471558 }
@@ -1267,7 +1578,7 @@ Prism.languages.jsonp = Prism.languages.json; · 19 unmodified lines
12671578 pattern: /^\$\{|\}$/,
12681579 alias: 'variable'
12691580 },
1270− rest: Prism.util.clone(Prism.languages.kotlin)
1581+ rest: Prism.languages.kotlin
12711582 }
12721583 },
12731584 {
@@ -1281,6 +1592,7 @@ Prism.languages.jsonp = Prism.languages.json; · 7 unmodified lines
12811592 };
12821593
12831594 }(Prism));
1595+
12841596 Prism.languages.lua = {
12851597 'comment': /^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,
12861598 // \z may be used to skip the following space
@@ -1301,16 +1613,68 @@ Prism.languages.lua = { · 14 unmodified lines
13011613 ],
13021614 'punctuation': /[\[\](){},;]|\.+|:+/
13031615 };
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+
13041668 Prism.languages.nginx = Prism.languages.extend('clike', {
1305− 'comment': {
1306− pattern: /(^|[^"{\\])#.*/,
1307− lookbehind: true
1308− },
1309− 'keyword': /\b(?:CONTENT_|DOCUMENT_|GATEWAY_|HTTP_|HTTPS|if_not_empty|PATH_|QUERY_|REDIRECT_|REMOTE_|REQUEST_|SCGI|SCRIPT_|SERVER_|http|server|events|location|include|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,
1669+ 'comment': {
1670+ pattern: /(^|[^"{\\])#.*/,
1671+ lookbehind: true
1672+ },
1673+ '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
13101674 });
13111675
13121676 Prism.languages.insertBefore('nginx', 'keyword', {
1313− 'variable': /\$[a-z_]+/i
1677+ 'variable': /\$[a-z_]+/i
13141678 });
13151679 Prism.languages.nim = {
13161680 'comment': /#.*/,
@@ -1361,13 +1725,13 @@ Prism.languages.perl = { · 44 unmodified lines
13611725 'string': [
13621726 // q/.../
13631727 {
1364− pattern: /\b(?:q|qq|qx|qw)\s*([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1/,
1728+ pattern: /\b(?:q|qq|qx|qw)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,
13651729 greedy: true
13661730 },
13671731
13681732 // q a...a
13691733 {
1370− pattern: /\b(?:q|qq|qx|qw)\s+([a-zA-Z0-9])(?:[^\\]|\\[\s\S])*?\1/,
1734+ pattern: /\b(?:q|qq|qx|qw)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1/,
13711735 greedy: true
13721736 },
13731737
@@ -1397,7 +1761,7 @@ Prism.languages.perl = { · 23 unmodified lines
13971761
13981762 // "...", `...`
13991763 {
1400− pattern: /("|`)(?:[^\\]|\\[\s\S])*?\1/,
1764+ pattern: /("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,
14011765 greedy: true
14021766 },
14031767
@@ -1411,13 +1775,13 @@ Prism.languages.perl = { · 7 unmodified lines
14111775 'regex': [
14121776 // m/.../
14131777 {
1414− pattern: /\b(?:m|qr)\s*([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1[msixpodualngc]*/,
1778+ pattern: /\b(?:m|qr)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,
14151779 greedy: true
14161780 },
14171781
14181782 // m a...a
14191783 {
1420− pattern: /\b(?:m|qr)\s+([a-zA-Z0-9])(?:[^\\]|\\.)*?\1[msixpodualngc]*/,
1784+ pattern: /\b(?:m|qr)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,
14211785 greedy: true
14221786 },
14231787
@@ -1449,14 +1813,14 @@ Prism.languages.perl = { · 25 unmodified lines
14491813 // FIXME We don't handle change of separator like s(...)[...]
14501814 // s/.../.../
14511815 {
1452− pattern: /(^|[^-]\b)(?:s|tr|y)\s*([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\2(?:[^\\]|\\[\s\S])*?\2[msixpodualngcer]*/,
1816+ pattern: /(^|[^-]\b)(?:s|tr|y)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,
14531817 lookbehind: true,
14541818 greedy: true
14551819 },
14561820
14571821 // s a...a...a
14581822 {
1459− pattern: /(^|[^-]\b)(?:s|tr|y)\s+([a-zA-Z0-9])(?:[^\\]|\\[\s\S])*?\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]*/,
14601824 lookbehind: true,
14611825 greedy: true
14621826 },
@@ -1508,7 +1872,7 @@ Prism.languages.perl = { · 45 unmodified lines
15081872 // ${...}
15091873 /[&*$@%]#?(?=\{)/,
15101874 // $foo
1511− /[&*$@%]#?((::)*'?(?!\d)[\w$]+)+(::)*/i,
1875+ /[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+)+(?:::)*/i,
15121876 // $1
15131877 /[&*$@%]\d+/,
15141878 // $_, @_, %!
@@ -1522,7 +1886,7 @@ Prism.languages.perl = { · 7 unmodified lines
15221886 },
15231887 'vstring': {
15241888 // v1.2, 1.2.3
1525− pattern: /v\d+(\.\d+)*|\d+(\.\d+){2,}/,
1889+ pattern: /v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,
15261890 alias: 'string'
15271891 },
15281892 'function': {
@@ -1531,9 +1895,9 @@ Prism.languages.perl = { · 2 unmodified lines
15311895 keyword: /sub/
15321896 }
15331897 },
1534− '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/,
1535− 'number': /\b-?(0x[\dA-Fa-f](_?[\dA-Fa-f])*|0b[01](_?[01])*|(\d(_?\d)*)?\.?\d(_?\d)*([Ee][+-]?\d+)?)\b/,
1536− 'operator': /-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\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/,
1899+ 'number': /\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,
1900+ 'operator': /-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,
15371901 'punctuation': /[{}[\];(),:]/
15381902 };
15391903
@@ -1549,136 +1913,239 @@ Prism.languages.perl = { · 9 unmodified lines
15491913 * Adds the following new token classes:
15501914 * constant, delimiter, variable, function, package
15511915 */
1552−
1553−Prism.languages.php = Prism.languages.extend('clike', {
1554− '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,
1555− 'constant': /\b[A-Z0-9_]{2,}\b/,
1556− 'comment': {
1557− pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,
1558− lookbehind: true
1559− }
1560−});
1561−
1562−// Shell-like comments are matched after strings, because they are less
1563−// common than strings containing hashes...
1564−Prism.languages.insertBefore('php', 'class-name', {
1565− 'shell-comment': {
1566− pattern: /(^|[^\\])#.*/,
1567− lookbehind: true,
1568− alias: 'comment'
1569− }
1570−});
1571−
1572−Prism.languages.insertBefore('php', 'keyword', {
1573− 'delimiter': {
1574− pattern: /\?>|<\?(?:php|=)?/i,
1575− alias: 'important'
1576− },
1577− 'variable': /\$\w+\b/i,
1578− 'package': {
1579− pattern: /(\\|namespace\s+|use\s+)[\w\\]+/,
1580− lookbehind: true,
1581− inside: {
1582− punctuation: /\\/
1583− }
1584− }
1585−});
1586−
1587−// Must be defined after the function pattern
1588−Prism.languages.insertBefore('php', 'operator', {
1589− 'property': {
1590− pattern: /(->)[\w]+/,
1591− lookbehind: true
1592− }
1593−});
1594−
1595−// Add HTML support if the markup language exists
1596−if (Prism.languages.markup) {
1597−
1598− // Tokenize all inline PHP blocks that are wrapped in <?php ?>
1599− // This allows for easy PHP + markup highlighting
1600− Prism.hooks.add('before-highlight', function(env) {
1601− if (env.language !== 'php' || !/(?:<\?php|<\?)/ig.test(env.code)) {
1602− return;
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
16031923 }
1604−
1605− env.tokenStack = [];
1606−
1607− env.backupCode = env.code;
1608− env.code = env.code.replace(/(?:<\?php|<\?)[\s\S]*?(?:\?>|$)/ig, function(match) {
1609− var i = env.tokenStack.length;
1610− // Check for existing strings
1611− while (env.backupCode.indexOf('___PHP' + i + '___') !== -1)
1612− ++i;
1613−
1614− // Create a sparse array
1615− env.tokenStack[i] = match;
1616−
1617− return '___PHP' + i + '___';
1618− });
1619−
1620− // Switch the grammar to markup
1621− env.grammar = Prism.languages.markup;
16221924 });
16231925
1624− // Restore env.code for other plugins (e.g. line-numbers)
1625− Prism.hooks.add('before-insert', function(env) {
1626− if (env.language === 'php' && env.backupCode) {
1627− env.code = env.backupCode;
1628− delete env.backupCode;
1926+ Prism.languages.insertBefore('php', 'string', {
1927+ 'shell-comment': {
1928+ pattern: /(^|[^\\])#.*/,
1929+ lookbehind: true,
1930+ alias: 'comment'
16291931 }
16301932 });
16311933
1632− // Re-insert the tokens after highlighting
1633− Prism.hooks.add('after-highlight', function(env) {
1634− if (env.language !== 'php' || !env.tokenStack) {
1635− return;
1934+ Prism.languages.insertBefore('php', 'keyword', {
1935+ 'delimiter': {
1936+ pattern: /\?>|<\?(?:php|=)?/i,
1937+ alias: 'important'
1938+ },
1939+ 'variable': /\$+(?:\w+\b|(?={))/i,
1940+ 'package': {
1941+ pattern: /(\\|namespace\s+|use\s+)[\w\\]+/,
1942+ lookbehind: true,
1943+ inside: {
1944+ punctuation: /\\/
1945+ }
16361946 }
1947+ });
16371948
1638− // Switch the grammar back
1639− env.grammar = Prism.languages.php;
1640−
1641− for (var i = 0, keys = Object.keys(env.tokenStack); i < keys.length; ++i) {
1642− var k = keys[i];
1643− var t = env.tokenStack[k];
1644−
1645− // The replace prevents $$, $&, $`, $', $n, $nn from being interpreted as special patterns
1646− env.highlightedCode = env.highlightedCode.replace('___PHP' + k + '___',
1647− "<span class=\"token php language-php\">" +
1648− Prism.highlight(t, env.grammar, 'php').replace(/\$/g, '$$$$') +
1649− "</span>");
1949+ // Must be defined after the function pattern
1950+ Prism.languages.insertBefore('php', 'operator', {
1951+ 'property': {
1952+ pattern: /(->)[\w]+/,
1953+ lookbehind: true
16501954 }
1651−
1652− env.element.innerHTML = env.highlightedCode;
16531955 });
1654−}
1655−;
1656−Prism.languages.python= {
1657− 'triple-quoted-string': {
1658− pattern: /"""[\s\S]+?"""|'''[\s\S]+?'''/,
1659− alias: 'string'
1660− },
1661− 'comment': {
1956+
1957+ Prism.languages.insertBefore('php', 'string', {
1958+ 'nowdoc-string': {
1959+ pattern: /<<<'([^']+)'(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;/,
1960+ greedy: true,
1961+ alias: 'string',
1962+ inside: {
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'];
2003+
2004+ var string_interpolation = {
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;
2013+
2014+ Prism.hooks.add('before-tokenize', function(env) {
2015+ if (!/(?:<\?php|<\?)/ig.test(env.code)) {
2016+ return;
2017+ }
2018+
2019+ var phpPattern = /(?:<\?php|<\?)[\s\S]*?(?:\?>|$)/ig;
2020+ Prism.languages['markup-templating'].buildPlaceholders(env, 'php', phpPattern);
2021+ });
2022+
2023+ Prism.hooks.add('after-tokenize', function(env) {
2024+ Prism.languages['markup-templating'].tokenizePlaceholders(env, 'php');
2025+ });
2026+
2027+}(Prism));
2028+Prism.languages.sql= {
2029+ 'comment': {
2030+ pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,
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+};
2046+Prism.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
2056+ }
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+});
2080+
2081+Prism.languages.insertBefore('scss', 'atrule', {
2082+ 'keyword': [
2083+ /@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,
2084+ {
2085+ pattern: /( +)(?:from|through)(?= )/,
2086+ lookbehind: true
2087+ }
2088+ ]
2089+});
2090+
2091+Prism.languages.scss.property = {
2092+ pattern: /(?:[\w-]|\$[-\w]+|#\{\$[-\w]+\})+(?=\s*:)/i,
2093+ inside: {
2094+ 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2095+ }
2096+};
2097+
2098+Prism.languages.insertBefore('scss', 'important', {
2099+ // var and interpolated vars
2100+ 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2101+});
2102+
2103+Prism.languages.insertBefore('scss', 'function', {
2104+ 'placeholder': {
2105+ pattern: /%[-\w]+/,
2106+ alias: 'selector'
2107+ },
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+});
2119+
2120+Prism.languages.scss['atrule'].inside.rest = Prism.languages.scss;
2121+Prism.languages.python = {
2122+ 'comment': {
16622123 pattern: /(^|[^\\])#.*/,
16632124 lookbehind: true
16642125 },
2126+ 'triple-quoted-string': {
2127+ pattern: /("""|''')[\s\S]+?\1/,
2128+ greedy: true,
2129+ alias: 'string'
2130+ },
16652131 'string': {
1666− pattern: /("|')(?:\\\\|\\?[^\\\r\n])*?\1/,
2132+ pattern: /("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,
16672133 greedy: true
16682134 },
1669− 'function' : {
1670− pattern: /((?:^|\s)def[ \t]+)[a-zA-Z_][a-zA-Z0-9_]*(?=\()/g,
2135+ 'function': {
2136+ pattern: /((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,
16712137 lookbehind: true
16722138 },
16732139 'class-name': {
1674− pattern: /(\bclass\s+)[a-z0-9_]+/i,
2140+ pattern: /(\bclass\s+)\w+/i,
16752141 lookbehind: true
16762142 },
1677− 'keyword' : /\b(?:as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|pass|print|raise|return|try|while|with|yield)\b/,
1678− 'boolean' : /\b(?:True|False)\b/,
1679− 'number' : /\b-?(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,
1680− 'operator' : /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,
1681− 'punctuation' : /[{}[\];(),.:]/
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/,
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/,
2145+ 'boolean': /\b(?:True|False|None)\b/,
2146+ 'number': /(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,
2147+ 'operator': /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,
2148+ 'punctuation': /[{}[\];(),.:]/
16822149 };
16832150
16842151 (function(Prism) {
@@ -1686,93 +2153,134 @@ Prism.languages.python= { · 1 unmodified line
16862153 var javascript = Prism.util.clone(Prism.languages.javascript);
16872154
16882155 Prism.languages.jsx = Prism.languages.extend('markup', javascript);
1689−Prism.languages.jsx.tag.pattern= /<\/?[\w\.:-]+\s*(?:\s+(?:[\w\.:-]+(?:=(?:("|')(\\?[\s\S])*?\1|[^\s'">=]+|(\{[\s\S]*?\})))?|\{\.{3}\w+\}))*\s*\/?>/i;
2156+Prism.languages.jsx.tag.pattern= /<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s{'">=]+|\{(?:\{(?:\{[^}]*\}|[^{}])*\}|[^{}])+\}))?|\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}))*\s*\/?)?>/i;
16902157
1691−Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:('|")[\s\S]*?(\1)|[^\s>]+)/i;
2158+Prism.languages.jsx.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/i;
2159+Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i;
16922160
16932161 Prism.languages.insertBefore('inside', 'attr-name', {
16942162 'spread': {
1695− pattern: /\{\.{3}\w+\}/,
2163+ pattern: /\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}/,
16962164 inside: {
1697− 'punctuation': /\{|\}|\./,
2165+ 'punctuation': /\.{3}|[{}.]/,
16982166 'attr-value': /\w+/
16992167 }
17002168 }
17012169 }, Prism.languages.jsx.tag);
17022170
1703−var jsxExpression = Prism.util.clone(Prism.languages.jsx);
1704−
1705−delete jsxExpression.punctuation
1706−
1707−jsxExpression = Prism.languages.insertBefore('jsx', 'operator', {
1708− 'punctuation': /=(?={)|[{}[\];(),.:]/
1709−}, { jsx: jsxExpression });
1710−
17112171 Prism.languages.insertBefore('inside', 'attr-value',{
17122172 'script': {
1713− // Allow for one level of nesting
1714− pattern: /=(\{(?:\{[^}]*\}|[^}])+\})/i,
1715− inside: jsxExpression,
2173+ // Allow for two levels of nesting
2174+ pattern: /=(\{(?:\{(?:\{[^}]*\}|[^}])*\}|[^}])+\})/i,
2175+ inside: {
2176+ 'script-punctuation': {
2177+ pattern: /^=(?={)/,
2178+ alias: 'punctuation'
2179+ },
2180+ rest: Prism.languages.jsx
2181+ },
17162182 'alias': 'language-javascript'
17172183 }
17182184 }, Prism.languages.jsx.tag);
17192185
1720−}(Prism));
2186+// The following will handle plain text inside tags
2187+var 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+};
17212199
1722−(function(Prism) {
1723− Prism.languages.crystal = Prism.languages.extend('ruby', {
1724− keyword: [
1725− /\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/,
1726− {
1727− pattern: /(\.\s*)(?:is_a|responds_to)\?/,
1728− lookbehind: true
1729− }
1730− ],
2200+var walkTokens = function (tokens) {
2201+ var openedTags = [];
2202+ for (var i = 0; i < tokens.length; i++) {
2203+ var token = tokens[i];
2204+ var notTagNorBrace = false;
2205+
2206+ if (typeof token !== 'string') {
2207+ if (token.type === 'tag' && token.content[0] && token.content[0].type === 'tag') {
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 === '{') {
17312228
1732− 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/,
1733− });
2229+ // Here we might have entered a JSX context inside a tag
2230+ openedTags[openedTags.length - 1].openedBraces++;
17342231
1735− 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 === '}') {
17362233
1737− Prism.languages.insertBefore('crystal', 'string', {
1738− attribute: {
1739− pattern: /@\[.+?\]/,
1740− alias: 'attr-name',
1741− inside: {
1742− delimiter: {
1743− pattern: /^@\[|\]$/,
1744− alias: 'tag'
1745− },
1746− rest: rest
1747− }
1748− },
2234+ // Here we might have left a JSX context inside a tag
2235+ openedTags[openedTags.length - 1].openedBraces--;
17492236
1750− expansion: [
1751− {
1752− pattern: /\{\{.+?\}\}/,
1753− inside: {
1754− delimiter: {
1755− pattern: /^\{\{|\}\}$/,
1756− alias: 'tag'
1757− },
1758− rest: rest
2237+ } else {
2238+ notTagNorBrace = true
17592239 }
1760− },
1761− {
1762− pattern: /\{%.+?%\}/,
1763− inside: {
1764− delimiter: {
1765− pattern: /^\{%|%\}$/,
1766− alias: 'tag'
1767− },
1768− rest: rest
2240+ }
2241+ if (notTagNorBrace || typeof token === 'string') {
2242+ if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces === 0) {
2243+ // Here we are inside a tag, and not inside a JSX context.
2244+ // That's plain text: drop any tokens matched.
2245+ var plainText = stringifyToken(token);
2246+
2247+ // And merge text with adjacent text
2248+ 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);
17692259 }
17702260 }
1771− ]
1772− });
2261+
2262+ if (token.content && typeof token.content !== 'string') {
2263+ walkTokens(token.content);
2264+ }
2265+ }
2266+};
2267+
2268+Prism.hooks.add('after-tokenize', function (env) {
2269+ if (env.language !== 'jsx' && env.language !== 'tsx') {
2270+ return;
2271+ }
2272+ walkTokens(env.tokens);
2273+});
17732274
17742275 }(Prism));
17752276
2277+Prism.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+
2283+Prism.languages.ts = Prism.languages.typescript;
17762284 /* TODO
17772285 Add support for Markdown notation inside doc comments
17782286 Add support for nested block comments...
@@ -1793,14 +2301,22 @@ Prism.languages.rust = { · 14 unmodified lines
17932301 ],
17942302 'string': [
17952303 {
1796− pattern: /b?r(#*)"(?:\\?.)*?"\1/,
2304+ pattern: /b?r(#*)"(?:\\.|(?!"\1)[^\\\r\n])*"\1/,
17972305 greedy: true
17982306 },
17992307 {
1800− pattern: /b?("|')(?:\\?.)*?\1/,
2308+ pattern: /b?"(?:\\.|[^\\\r\n"])*"/,
18012309 greedy: true
18022310 }
18032311 ],
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+ },
18042320 '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/,
18052321
18062322 'attribute': {
@@ -1810,125 +2326,71 @@ Prism.languages.rust = { · 3 unmodified lines
18102326 },
18112327
18122328 'function': [
1813− /[a-z0-9_]+(?=\s*\()/i,
2329+ /\w+(?=\s*\()/,
18142330 // Macros can use parens or brackets
1815− /[a-z0-9_]+!(?=\s*\(|\[)/i
2331+ /\w+!(?=\s*\(|\[)/
18162332 ],
18172333 'macro-rules': {
1818− pattern: /[a-z0-9_]+!/i,
2334+ pattern: /\w+!/,
18192335 alias: 'function'
18202336 },
18212337
18222338 // Hex, oct, bin, dec numbers with visual separators and type suffix
1823− '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/,
18242340
18252341 // Closure params should not be confused with bitwise OR |
18262342 'closure-params': {
18272343 pattern: /\|[^|]*\|(?=\s*[{-])/,
18282344 inside: {
1829− 'punctuation': /[\|:,]/,
2345+ 'punctuation': /[|:,]/,
18302346 'operator': /[&*]/
18312347 }
18322348 },
18332349 'punctuation': /[{}[\];(),:]|\.+|->/,
1834− 'operator': /[-+*\/%!^=]=?|@|&[&=]?|\|[|=]?|<<?=?|>>?=?/
2350+ 'operator': /[-+*\/%!^]=?|=[=>]?|@|&[&=]?|\|[|=]?|<<?=?|>>?=?/
18352351 };
1836−Prism.languages.scss = Prism.languages.extend('css', {
1837− 'comment': {
1838− pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,
1839− lookbehind: true
2352+Prism.languages.yaml = {
2353+ 'scalar': {
2354+ pattern: /([\-:]\s*(?:![^\s]+)?[ \t]*[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\2[^\r\n]+)*)/,
2355+ lookbehind: true,
2356+ alias: 'string'
18402357 },
1841− 'atrule': {
1842− pattern: /@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,
1843− inside: {
1844− 'rule': /@[\w-]+/
1845− // See rest below
1846− }
2358+ 'comment': /#.*/,
2359+ 'key': {
2360+ pattern: /(\s*(?:^|[:\-,[{\r\n?])[ \t]*(?:![^\s]+)?[ \t]*)[^\r\n{[\]},#\s]+?(?=\s*:\s)/,
2361+ lookbehind: true,
2362+ alias: 'atrule'
18472363 },
1848− // url, compassified
1849− 'url': /(?:[-a-z]+-)*url(?=\()/i,
1850− // CSS selector regex is not appropriate for Sass
1851− // since there can be lot more things (var, @ directive, nesting..)
1852− // a selector must start at the end of a property or after a brace (end of other rules or nesting)
1853− // it can contain some characters that aren't used for defining rules or end of selector, & (parent selector), or interpolated variable
1854− // 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
1855− // can "pass" as a selector- e.g: proper#{$erty})
1856− // this one was hard to do, so please be careful if you edit this one :)
1857− 'selector': {
1858− // Initial look-ahead is used to prevent matching of blank selectors
1859− pattern: /(?=\S)[^@;\{\}\(\)]?([^@;\{\}\(\)]|&|#\{\$[-_\w]+\})+(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/m,
1860− inside: {
1861− 'parent': {
1862− pattern: /&/,
1863− alias: 'important'
1864− },
1865− 'placeholder': /%[-_\w]+/,
1866− 'variable': /\$[-_\w]+|#\{\$[-_\w]+\}/
1867− }
1868− }
1869−});
1870−
1871−Prism.languages.insertBefore('scss', 'atrule', {
1872− 'keyword': [
1873− /@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,
1874− {
1875− pattern: /( +)(?:from|through)(?= )/,
1876− lookbehind: true
1877− }
1878− ]
1879−});
1880−
1881−Prism.languages.scss.property = {
1882− pattern: /(?:[\w-]|\$[-_\w]+|#\{\$[-_\w]+\})+(?=\s*:)/i,
1883− inside: {
1884− 'variable': /\$[-_\w]+|#\{\$[-_\w]+\}/
1885− }
1886−};
1887−
1888−Prism.languages.insertBefore('scss', 'important', {
1889− // var and interpolated vars
1890− 'variable': /\$[-_\w]+|#\{\$[-_\w]+\}/
1891−});
1892−
1893−Prism.languages.insertBefore('scss', 'function', {
1894− 'placeholder': {
1895− pattern: /%[-_\w]+/,
1896− alias: 'selector'
2364+ 'directive': {
2365+ pattern: /(^[ \t]*)%.+/m,
2366+ lookbehind: true,
2367+ alias: 'important'
18972368 },
1898− 'statement': {
1899− pattern: /\B!(?:default|optional)\b/i,
1900− alias: 'keyword'
2369+ 'datetime': {
2370+ pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)(?:\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?)?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?)(?=[ \t]*(?:$|,|]|}))/m,
2371+ lookbehind: true,
2372+ alias: 'number'
19012373 },
1902− 'boolean': /\b(?:true|false)\b/,
1903− 'null': /\bnull\b/,
1904− 'operator': {
1905− pattern: /(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,
1906− lookbehind: true
1907− }
1908−});
1909−
1910−Prism.languages.scss['atrule'].inside.rest = Prism.util.clone(Prism.languages.scss);
1911−Prism.languages.sql= {
1912− 'comment': {
1913− pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,
1914− lookbehind: true
2374+ 'boolean': {
2375+ pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)(?:true|false)[ \t]*(?=$|,|]|})/im,
2376+ lookbehind: true,
2377+ alias: 'important'
19152378 },
1916− 'string' : {
1917− pattern: /(^|[^@\\])("|')(?:\\?[\s\S])*?\2/,
1918− greedy: true,
2379+ 'null': {
2380+ pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)(?:null|~)[ \t]*(?=$|,|]|})/im,
2381+ lookbehind: true,
2382+ alias: 'important'
2383+ },
2384+ 'string': {
2385+ pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)("|')(?:(?!\2)[^\\\r\n]|\\.)*\2(?=[ \t]*(?:$|,|]|}))/m,
2386+ lookbehind: true,
2387+ greedy: true
2388+ },
2389+ 'number': {
2390+ pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+\.?\d*|\.?\d+)(?:e[+-]?\d+)?|\.inf|\.nan)[ \t]*(?=$|,|]|})/im,
19192391 lookbehind: true
19202392 },
1921− 'variable': /@[\w.$]+|@("|'|`)(?:\\?[\s\S])+?\1/,
1922− '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?
1923− '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,
1924− 'boolean': /\b(?:TRUE|FALSE|NULL)\b/i,
1925− 'number': /\b-?(?:0x)?\d*\.?[\da-f]+\b/,
1926− 'operator': /[-+*\/=%^~]|&&?|\|?\||!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,
1927− 'punctuation': /[;[\]()`,.]/
2393+ 'tag': /![^\s]+/,
2394+ 'important': /[&*][\w]+/,
2395+ 'punctuation': /---|[:[\]{}\-,|>?]|\.\.\./
19282396 };
1929−Prism.languages.typescript = Prism.languages.extend('javascript', {
1930− // From JavaScript Prism keyword list and TypeScript language spec: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#221-reserved-words
1931− '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|enum|symbol|namespace|abstract|require|type)\b/
1932−});
1933−
1934−Prism.languages.ts = Prism.languages.typescript;
assets/javascripts/vendor/raven.js+2544−2655
@@ -1,4 +1,4 @@
1−/*! Raven.js 3.18.0 (b771bb2) | github.com/getsentry/raven-js */
1+/*! Raven.js 3.20.1 (42adaf5) | github.com/getsentry/raven-js */
22
33 /*
44 * Includes TraceKit
@@ -10,322 +10,270 @@ · 5 unmodified lines
1010 *
1111 */
1212
13−(function(f) {
14− if (typeof exports === 'object' && typeof module !== 'undefined') {
15− module.exports = f();
16− } else if (typeof define === 'function' && define.amd) {
17− define([], f);
18− } else {
19− var g;
20− if (typeof window !== 'undefined') {
21− g = window;
22− } else if (typeof global !== 'undefined') {
23− g = global;
24− } else if (typeof self !== 'undefined') {
25− g = self;
26− } else {
27− g = this;
28− }
29− g.Raven = f();
30− }
31−})(function() {
32− var define, module, exports;
33− return (function e(t, n, r) {
34− function s(o, u) {
35− if (!n[o]) {
36− if (!t[o]) {
37− var a = typeof require == 'function' && require;
38− if (!u && a) return a(o, !0);
39− if (i) return i(o, !0);
40− var f = new Error("Cannot find module '" + o + "'");
41− throw ((f.code = 'MODULE_NOT_FOUND'), f);
42− }
43− var l = (n[o] = {exports: {}});
44− t[o][0].call(
45− l.exports,
46− function(e) {
47− var n = t[o][1][e];
48− return s(n ? n : e);
49− },
50− l,
51− l.exports,
52− e,
53− t,
54− n,
55− r
56− );
57− }
58− return n[o].exports;
59− }
60− var i = typeof require == 'function' && require;
61− for (var o = 0; o < r.length; o++) s(r[o]);
62− return s;
63− })(
64− {
65− 1: [
66− function(_dereq_, module, exports) {
67− function RavenConfigError(message) {
68− this.name = 'RavenConfigError';
69− this.message = message;
70− }
71− RavenConfigError.prototype = new Error();
72− RavenConfigError.prototype.constructor = RavenConfigError;
73−
74− module.exports = RavenConfigError;
75− },
76− {}
77− ],
78− 2: [
79− function(_dereq_, module, exports) {
80− var wrapMethod = function(console, level, callback) {
81− var originalConsoleLevel = console[level];
82− var originalConsole = console;
83−
84− if (!(level in console)) {
85− return;
86− }
13+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
14+function RavenConfigError(message) {
15+ this.name = 'RavenConfigError';
16+ this.message = message;
17+}
18+RavenConfigError.prototype = new Error();
19+RavenConfigError.prototype.constructor = RavenConfigError;
8720
88− var sentryLevel = level === 'warn' ? 'warning' : level;
89−
90− console[level] = function() {
91− var args = [].slice.call(arguments);
92−
93− var msg = '' + args.join(' ');
94− var data = {
95− level: sentryLevel,
96− logger: 'console',
97− extra: {arguments: args}
98− };
99−
100− if (level === 'assert') {
101− if (args[0] === false) {
102− // Default browsers message
103− msg =
104− 'Assertion failed: ' + (args.slice(1).join(' ') || 'console.assert');
105− data.extra.arguments = args.slice(1);
106− callback && callback(msg, data);
107− }
108− } else {
109− callback && callback(msg, data);
110− }
111−
112− // this fails for some browsers. :(
113− if (originalConsoleLevel) {
114− // IE9 doesn't allow calling apply on console functions directly
115− // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193
116− Function.prototype.apply.call(
117− originalConsoleLevel,
118− originalConsole,
119− args
120− );
121− }
122− };
123− };
124−
125− module.exports = {
126− wrapMethod: wrapMethod
127− };
128− },
129− {}
130− ],
131− 3: [
132− function(_dereq_, module, exports) {
133− (function(global) {
134− /*global XDomainRequest:false, __DEV__:false*/
21+module.exports = RavenConfigError;
13522
136− var TraceKit = _dereq_(6);
137− var stringify = _dereq_(7);
138− var RavenConfigError = _dereq_(1);
139− var utils = _dereq_(5);
23+},{}],2:[function(_dereq_,module,exports){
24+var wrapMethod = function(console, level, callback) {
25+ var originalConsoleLevel = console[level];
26+ var originalConsole = console;
14027
141− var isError = utils.isError,
142− isObject = utils.isObject;
28+ if (!(level in console)) {
29+ return;
30+ }
14331
144− var wrapConsoleMethod = _dereq_(2).wrapMethod;
32+ var sentryLevel = level === 'warn' ? 'warning' : level;
14533
146− var dsnKeys = 'source protocol user pass host port path'.split(' '),
147− dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;
34+ console[level] = function() {
35+ var args = [].slice.call(arguments);
14836
149− function now() {
150− return +new Date();
151− }
37+ var msg = '' + args.join(' ');
38+ var data = {level: sentryLevel, logger: 'console', extra: {arguments: args}};
15239
153− // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
154− var _window =
155− typeof window !== 'undefined'
156− ? window
157− : typeof global !== 'undefined'
158− ? global
159− : typeof self !== 'undefined' ? self : {};
160− var _document = _window.document;
161− var _navigator = _window.navigator;
162−
163− function keepOriginalCallback(original, callback) {
164− return isFunction(callback)
165− ? function(data) {
166− return callback(data, original);
167− }
168− : callback;
169− }
40+ if (level === 'assert') {
41+ if (args[0] === false) {
42+ // Default browsers message
43+ msg = 'Assertion failed: ' + (args.slice(1).join(' ') || 'console.assert');
44+ data.extra.arguments = args.slice(1);
45+ callback && callback(msg, data);
46+ }
47+ } else {
48+ callback && callback(msg, data);
49+ }
17050
171− // First, check for JSON support
172− // If there is no JSON, we no-op the core features of Raven
173− // since JSON is required to encode the payload
174− function Raven() {
175− this._hasJSON = !!(typeof JSON === 'object' && JSON.stringify);
176− // Raven can run in contexts where there's no document (react-native)
177− this._hasDocument = !isUndefined(_document);
178− this._hasNavigator = !isUndefined(_navigator);
179− this._lastCapturedException = null;
180− this._lastData = null;
181− this._lastEventId = null;
182− this._globalServer = null;
183− this._globalKey = null;
184− this._globalProject = null;
185− this._globalContext = {};
186− this._globalOptions = {
187− logger: 'javascript',
188− ignoreErrors: [],
189− ignoreUrls: [],
190− whitelistUrls: [],
191− includePaths: [],
192− collectWindowErrors: true,
193− maxMessageLength: 0,
194−
195− // By default, truncates URL values to 250 chars
196− maxUrlLength: 250,
197− stackTraceLimit: 50,
198− autoBreadcrumbs: true,
199− instrument: true,
200− sampleRate: 1
201− };
202− this._ignoreOnError = 0;
203− this._isRavenInstalled = false;
204− this._originalErrorStackTraceLimit = Error.stackTraceLimit;
205− // capture references to window.console *and* all its methods first
206− // before the console plugin has a chance to monkey patch
207− this._originalConsole = _window.console || {};
208− this._originalConsoleMethods = {};
209− this._plugins = [];
210− this._startTime = now();
211− this._wrappedBuiltIns = [];
212− this._breadcrumbs = [];
213− this._lastCapturedEvent = null;
214− this._keypressTimeout;
215− this._location = _window.location;
216− this._lastHref = this._location && this._location.href;
217− this._resetBackoff();
218−
219− // eslint-disable-next-line guard-for-in
220− for (var method in this._originalConsole) {
221− this._originalConsoleMethods[method] = this._originalConsole[method];
222− }
223− }
51+ // this fails for some browsers. :(
52+ if (originalConsoleLevel) {
53+ // IE9 doesn't allow calling apply on console functions directly
54+ // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193
55+ Function.prototype.apply.call(originalConsoleLevel, originalConsole, args);
56+ }
57+ };
58+};
59+
60+module.exports = {
61+ wrapMethod: wrapMethod
62+};
63+
64+},{}],3:[function(_dereq_,module,exports){
65+(function (global){
66+/*global XDomainRequest:false */
67+
68+var TraceKit = _dereq_(6);
69+var stringify = _dereq_(7);
70+var RavenConfigError = _dereq_(1);
71+
72+var utils = _dereq_(5);
73+var isError = utils.isError;
74+var isObject = utils.isObject;
75+var isErrorEvent = utils.isErrorEvent;
76+var isUndefined = utils.isUndefined;
77+var isFunction = utils.isFunction;
78+var isString = utils.isString;
79+var isArray = utils.isArray;
80+var isEmptyObject = utils.isEmptyObject;
81+var each = utils.each;
82+var objectMerge = utils.objectMerge;
83+var truncate = utils.truncate;
84+var objectFrozen = utils.objectFrozen;
85+var hasKey = utils.hasKey;
86+var joinRegExp = utils.joinRegExp;
87+var urlencode = utils.urlencode;
88+var uuid4 = utils.uuid4;
89+var htmlTreeAsString = utils.htmlTreeAsString;
90+var isSameException = utils.isSameException;
91+var isSameStacktrace = utils.isSameStacktrace;
92+var parseUrl = utils.parseUrl;
93+var fill = utils.fill;
94+
95+var wrapConsoleMethod = _dereq_(2).wrapMethod;
96+
97+var dsnKeys = 'source protocol user pass host port path'.split(' '),
98+ dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;
99+
100+function now() {
101+ return +new Date();
102+}
103+
104+// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
105+var _window =
106+ typeof window !== 'undefined'
107+ ? window
108+ : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
109+var _document = _window.document;
110+var _navigator = _window.navigator;
111+
112+function keepOriginalCallback(original, callback) {
113+ return isFunction(callback)
114+ ? function(data) {
115+ return callback(data, original);
116+ }
117+ : callback;
118+}
119+
120+// First, check for JSON support
121+// If there is no JSON, we no-op the core features of Raven
122+// since JSON is required to encode the payload
123+function Raven() {
124+ this._hasJSON = !!(typeof JSON === 'object' && JSON.stringify);
125+ // Raven can run in contexts where there's no document (react-native)
126+ this._hasDocument = !isUndefined(_document);
127+ this._hasNavigator = !isUndefined(_navigator);
128+ this._lastCapturedException = null;
129+ this._lastData = null;
130+ this._lastEventId = null;
131+ this._globalServer = null;
132+ this._globalKey = null;
133+ this._globalProject = null;
134+ this._globalContext = {};
135+ this._globalOptions = {
136+ logger: 'javascript',
137+ ignoreErrors: [],
138+ ignoreUrls: [],
139+ whitelistUrls: [],
140+ includePaths: [],
141+ collectWindowErrors: true,
142+ maxMessageLength: 0,
143+
144+ // By default, truncates URL values to 250 chars
145+ maxUrlLength: 250,
146+ stackTraceLimit: 50,
147+ autoBreadcrumbs: true,
148+ instrument: true,
149+ sampleRate: 1
150+ };
151+ this._ignoreOnError = 0;
152+ this._isRavenInstalled = false;
153+ this._originalErrorStackTraceLimit = Error.stackTraceLimit;
154+ // capture references to window.console *and* all its methods first
155+ // before the console plugin has a chance to monkey patch
156+ this._originalConsole = _window.console || {};
157+ this._originalConsoleMethods = {};
158+ this._plugins = [];
159+ this._startTime = now();
160+ this._wrappedBuiltIns = [];
161+ this._breadcrumbs = [];
162+ this._lastCapturedEvent = null;
163+ this._keypressTimeout;
164+ this._location = _window.location;
165+ this._lastHref = this._location && this._location.href;
166+ this._resetBackoff();
167+
168+ // eslint-disable-next-line guard-for-in
169+ for (var method in this._originalConsole) {
170+ this._originalConsoleMethods[method] = this._originalConsole[method];
171+ }
172+}
224173
225− /*
174+/*
226175 * The core Raven singleton
227176 *
228177 * @this {Raven}
229178 */
230179
231− Raven.prototype = {
232− // Hardcode version string so that raven source can be loaded directly via
233− // webpack (using a build step causes webpack #1617). Grunt verifies that
234− // this value matches package.json during build.
235− // See: https://github.com/getsentry/raven-js/issues/465
236− VERSION: '3.18.0',
180+Raven.prototype = {
181+ // Hardcode version string so that raven source can be loaded directly via
182+ // webpack (using a build step causes webpack #1617). Grunt verifies that
183+ // this value matches package.json during build.
184+ // See: https://github.com/getsentry/raven-js/issues/465
185+ VERSION: '3.20.1',
237186
238− debug: false,
187+ debug: false,
239188
240− TraceKit: TraceKit, // alias to TraceKit
189+ TraceKit: TraceKit, // alias to TraceKit
241190
242− /*
191+ /*
243192 * Configure Raven with a DSN and extra options
244193 *
245194 * @param {string} dsn The public Sentry DSN
246195 * @param {object} options Set of global options [optional]
247196 * @return {Raven}
248197 */
249− config: function(dsn, options) {
250− var self = this;
251−
252− if (self._globalServer) {
253− this._logDebug('error', 'Error: Raven has already been configured');
254− return self;
255− }
256− if (!dsn) return self;
257−
258− var globalOptions = self._globalOptions;
259−
260− // merge in options
261− if (options) {
262− each(options, function(key, value) {
263− // tags and extra are special and need to be put into context
264− if (key === 'tags' || key === 'extra' || key === 'user') {
265− self._globalContext[key] = value;
266− } else {
267− globalOptions[key] = value;
268− }
269− });
270− }
198+ config: function(dsn, options) {
199+ var self = this;
271200
272− self.setDSN(dsn);
273−
274− // "Script error." is hard coded into browsers for errors that it can't read.
275− // this is the result of a script being pulled in from an external domain and CORS.
276− globalOptions.ignoreErrors.push(/^Script error\.?$/);
277− globalOptions.ignoreErrors.push(
278− /^Javascript error: Script error\.? on line 0$/
279− );
280−
281− // join regexp rules into one big rule
282− globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors);
283− globalOptions.ignoreUrls = globalOptions.ignoreUrls.length
284− ? joinRegExp(globalOptions.ignoreUrls)
285− : false;
286− globalOptions.whitelistUrls = globalOptions.whitelistUrls.length
287− ? joinRegExp(globalOptions.whitelistUrls)
288− : false;
289− globalOptions.includePaths = joinRegExp(globalOptions.includePaths);
290− globalOptions.maxBreadcrumbs = Math.max(
291− 0,
292− Math.min(globalOptions.maxBreadcrumbs || 100, 100)
293− ); // default and hard limit is 100
294−
295− var autoBreadcrumbDefaults = {
296− xhr: true,
297− console: true,
298− dom: true,
299− location: true
300− };
201+ if (self._globalServer) {
202+ this._logDebug('error', 'Error: Raven has already been configured');
203+ return self;
204+ }
205+ if (!dsn) return self;
206+
207+ var globalOptions = self._globalOptions;
208+
209+ // merge in options
210+ if (options) {
211+ each(options, function(key, value) {
212+ // tags and extra are special and need to be put into context
213+ if (key === 'tags' || key === 'extra' || key === 'user') {
214+ self._globalContext[key] = value;
215+ } else {
216+ globalOptions[key] = value;
217+ }
218+ });
219+ }
301220
302− var autoBreadcrumbs = globalOptions.autoBreadcrumbs;
303− if ({}.toString.call(autoBreadcrumbs) === '[object Object]') {
304− autoBreadcrumbs = objectMerge(autoBreadcrumbDefaults, autoBreadcrumbs);
305− } else if (autoBreadcrumbs !== false) {
306− autoBreadcrumbs = autoBreadcrumbDefaults;
307− }
308− globalOptions.autoBreadcrumbs = autoBreadcrumbs;
221+ self.setDSN(dsn);
222+
223+ // "Script error." is hard coded into browsers for errors that it can't read.
224+ // this is the result of a script being pulled in from an external domain and CORS.
225+ globalOptions.ignoreErrors.push(/^Script error\.?$/);
226+ globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/);
227+
228+ // join regexp rules into one big rule
229+ globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors);
230+ globalOptions.ignoreUrls = globalOptions.ignoreUrls.length
231+ ? joinRegExp(globalOptions.ignoreUrls)
232+ : false;
233+ globalOptions.whitelistUrls = globalOptions.whitelistUrls.length
234+ ? joinRegExp(globalOptions.whitelistUrls)
235+ : false;
236+ globalOptions.includePaths = joinRegExp(globalOptions.includePaths);
237+ globalOptions.maxBreadcrumbs = Math.max(
238+ 0,
239+ Math.min(globalOptions.maxBreadcrumbs || 100, 100)
240+ ); // default and hard limit is 100
241+
242+ var autoBreadcrumbDefaults = {
243+ xhr: true,
244+ console: true,
245+ dom: true,
246+ location: true,
247+ sentry: true
248+ };
249+
250+ var autoBreadcrumbs = globalOptions.autoBreadcrumbs;
251+ if ({}.toString.call(autoBreadcrumbs) === '[object Object]') {
252+ autoBreadcrumbs = objectMerge(autoBreadcrumbDefaults, autoBreadcrumbs);
253+ } else if (autoBreadcrumbs !== false) {
254+ autoBreadcrumbs = autoBreadcrumbDefaults;
255+ }
256+ globalOptions.autoBreadcrumbs = autoBreadcrumbs;
309257
310− var instrumentDefaults = {
311− tryCatch: true
312− };
258+ var instrumentDefaults = {
259+ tryCatch: true
260+ };
313261
314− var instrument = globalOptions.instrument;
315− if ({}.toString.call(instrument) === '[object Object]') {
316− instrument = objectMerge(instrumentDefaults, instrument);
317− } else if (instrument !== false) {
318− instrument = instrumentDefaults;
319− }
320− globalOptions.instrument = instrument;
262+ var instrument = globalOptions.instrument;
263+ if ({}.toString.call(instrument) === '[object Object]') {
264+ instrument = objectMerge(instrumentDefaults, instrument);
265+ } else if (instrument !== false) {
266+ instrument = instrumentDefaults;
267+ }
268+ globalOptions.instrument = instrument;
321269
322− TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors;
270+ TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors;
323271
324− // return for chaining
325− return self;
326− },
272+ // return for chaining
273+ return self;
274+ },
327275
328− /*
276+ /*
329277 * Installs a global window.onerror error handler
330278 * to capture and report uncaught exceptions.
331279 * At this point, install() is required to be called due
@@ -333,63 +281,58 @@ · 1 unmodified line
333281 *
334282 * @return {Raven}
335283 */
336− install: function() {
337− var self = this;
338− if (self.isSetup() && !self._isRavenInstalled) {
339− TraceKit.report.subscribe(function() {
340− self._handleOnErrorStackInfo.apply(self, arguments);
341− });
342− if (
343− self._globalOptions.instrument &&
344− self._globalOptions.instrument.tryCatch
345− ) {
346− self._instrumentTryCatch();
347− }
284+ install: function() {
285+ var self = this;
286+ if (self.isSetup() && !self._isRavenInstalled) {
287+ TraceKit.report.subscribe(function() {
288+ self._handleOnErrorStackInfo.apply(self, arguments);
289+ });
348290
349− if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs();
291+ self._patchFunctionToString();
350292
351− // Install all of the plugins
352− self._drainPlugins();
293+ if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch) {
294+ self._instrumentTryCatch();
295+ }
353296
354− self._isRavenInstalled = true;
355− }
297+ if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs();
356298
357− Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
358− return this;
359− },
299+ // Install all of the plugins
300+ self._drainPlugins();
360301
361− /*
302+ self._isRavenInstalled = true;
303+ }
304+
305+ Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
306+ return this;
307+ },
308+
309+ /*
362310 * Set the DSN (can be called multiple time unlike config)
363311 *
364312 * @param {string} dsn The public Sentry DSN
365313 */
366− setDSN: function(dsn) {
367− var self = this,
368− uri = self._parseDSN(dsn),
369− lastSlash = uri.path.lastIndexOf('/'),
370− path = uri.path.substr(1, lastSlash);
371−
372− self._dsn = dsn;
373− self._globalKey = uri.user;
374− self._globalSecret = uri.pass && uri.pass.substr(1);
375− self._globalProject = uri.path.substr(lastSlash + 1);
376−
377− self._globalServer = self._getGlobalServer(uri);
378−
379− self._globalEndpoint =
380− self._globalServer +
381− '/' +
382− path +
383− 'api/' +
384− self._globalProject +
385− '/store/';
386−
387− // Reset backoff state since we may be pointing at a
388− // new project/server
389− this._resetBackoff();
390− },
391−
392− /*
314+ setDSN: function(dsn) {
315+ var self = this,
316+ uri = self._parseDSN(dsn),
317+ lastSlash = uri.path.lastIndexOf('/'),
318+ path = uri.path.substr(1, lastSlash);
319+
320+ self._dsn = dsn;
321+ self._globalKey = uri.user;
322+ self._globalSecret = uri.pass && uri.pass.substr(1);
323+ self._globalProject = uri.path.substr(lastSlash + 1);
324+
325+ self._globalServer = self._getGlobalServer(uri);
326+
327+ self._globalEndpoint =
328+ self._globalServer + '/' + path + 'api/' + self._globalProject + '/store/';
329+
330+ // Reset backoff state since we may be pointing at a
331+ // new project/server
332+ this._resetBackoff();
333+ },
334+
335+ /*
393336 * Wrap code within a context so Raven can capture errors
394337 * reliably across domains that is executed immediately.
395338 *
@@ -397,17 +340,17 @@ · 1 unmodified line
397340 * @param {function} func The callback to be immediately executed within the context
398341 * @param {array} args An array of arguments to be called with the callback [optional]
399342 */
400− context: function(options, func, args) {
401− if (isFunction(options)) {
402− args = func || [];
403− func = options;
404− options = undefined;
405− }
343+ context: function(options, func, args) {
344+ if (isFunction(options)) {
345+ args = func || [];
346+ func = options;
347+ options = undefined;
348+ }
406349
407− return this.wrap(options, func).apply(this, args);
408− },
350+ return this.wrap(options, func).apply(this, args);
351+ },
409352
410− /*
353+ /*
411354 * Wrap code within a context and returns back a new function to be executed
412355 *
413356 * @param {object} options A specific set of options for this context [optional]
@@ -415,381 +358,397 @@ · 1 unmodified line
415358 * @param {function} func A function to call before the try/catch wrapper [optional, private]
416359 * @return {function} The newly wrapped functions with a context
417360 */
418− wrap: function(options, func, _before) {
419− var self = this;
420− // 1 argument has been passed, and it's not a function
421− // so just return it
422− if (isUndefined(func) && !isFunction(options)) {
423− return options;
424− }
425−
426− // options is optional
427− if (isFunction(options)) {
428− func = options;
429− options = undefined;
430− }
361+ wrap: function(options, func, _before) {
362+ var self = this;
363+ // 1 argument has been passed, and it's not a function
364+ // so just return it
365+ if (isUndefined(func) && !isFunction(options)) {
366+ return options;
367+ }
431368
432− // At this point, we've passed along 2 arguments, and the second one
433− // is not a function either, so we'll just return the second argument.
434− if (!isFunction(func)) {
435− return func;
436− }
369+ // options is optional
370+ if (isFunction(options)) {
371+ func = options;
372+ options = undefined;
373+ }
437374
438− // We don't wanna wrap it twice!
439− try {
440− if (func.__raven__) {
441− return func;
442− }
375+ // At this point, we've passed along 2 arguments, and the second one
376+ // is not a function either, so we'll just return the second argument.
377+ if (!isFunction(func)) {
378+ return func;
379+ }
443380
444− // If this has already been wrapped in the past, return that
445− if (func.__raven_wrapper__) {
446− return func.__raven_wrapper__;
447− }
448− } catch (e) {
449− // Just accessing custom props in some Selenium environments
450− // can cause a "Permission denied" exception (see raven-js#495).
451− // Bail on wrapping and return the function as-is (defers to window.onerror).
452− return func;
453− }
381+ // We don't wanna wrap it twice!
382+ try {
383+ if (func.__raven__) {
384+ return func;
385+ }
454386
455− function wrapped() {
456− var args = [],
457− i = arguments.length,
458− deep = !options || (options && options.deep !== false);
387+ // If this has already been wrapped in the past, return that
388+ if (func.__raven_wrapper__) {
389+ return func.__raven_wrapper__;
390+ }
391+ } catch (e) {
392+ // Just accessing custom props in some Selenium environments
393+ // can cause a "Permission denied" exception (see raven-js#495).
394+ // Bail on wrapping and return the function as-is (defers to window.onerror).
395+ return func;
396+ }
459397
460− if (_before && isFunction(_before)) {
461− _before.apply(this, arguments);
462− }
398+ function wrapped() {
399+ var args = [],
400+ i = arguments.length,
401+ deep = !options || (options && options.deep !== false);
463402
464− // Recursively wrap all of a function's arguments that are
465− // functions themselves.
466− while (i--)
467− args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i];
403+ if (_before && isFunction(_before)) {
404+ _before.apply(this, arguments);
405+ }
468406
469− try {
470− // Attempt to invoke user-land function
471− // NOTE: If you are a Sentry user, and you are seeing this stack frame, it
472− // means Raven caught an error invoking your application code. This is
473− // expected behavior and NOT indicative of a bug with Raven.js.
474− return func.apply(this, args);
475− } catch (e) {
476− self._ignoreNextOnError();
477− self.captureException(e, options);
478− throw e;
479− }
480− }
407+ // Recursively wrap all of a function's arguments that are
408+ // functions themselves.
409+ while (i--) args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i];
410+
411+ try {
412+ // Attempt to invoke user-land function
413+ // NOTE: If you are a Sentry user, and you are seeing this stack frame, it
414+ // means Raven caught an error invoking your application code. This is
415+ // expected behavior and NOT indicative of a bug with Raven.js.
416+ return func.apply(this, args);
417+ } catch (e) {
418+ self._ignoreNextOnError();
419+ self.captureException(e, options);
420+ throw e;
421+ }
422+ }
481423
482− // copy over properties of the old function
483− for (var property in func) {
484− if (hasKey(func, property)) {
485− wrapped[property] = func[property];
486− }
487− }
488− wrapped.prototype = func.prototype;
424+ // copy over properties of the old function
425+ for (var property in func) {
426+ if (hasKey(func, property)) {
427+ wrapped[property] = func[property];
428+ }
429+ }
430+ wrapped.prototype = func.prototype;
489431
490− func.__raven_wrapper__ = wrapped;
491− // Signal that this function has been wrapped already
492− // for both debugging and to prevent it to being wrapped twice
493− wrapped.__raven__ = true;
494− wrapped.__inner__ = func;
432+ func.__raven_wrapper__ = wrapped;
433+ // Signal that this function has been wrapped/filled already
434+ // for both debugging and to prevent it to being wrapped/filled twice
435+ wrapped.__raven__ = true;
436+ wrapped.__orig__ = func;
495437
496− return wrapped;
497− },
438+ return wrapped;
439+ },
498440
499− /*
441+ /*
500442 * Uninstalls the global error handler.
501443 *
502444 * @return {Raven}
503445 */
504− uninstall: function() {
505− TraceKit.report.uninstall();
446+ uninstall: function() {
447+ TraceKit.report.uninstall();
506448
507− this._restoreBuiltIns();
449+ this._unpatchFunctionToString();
450+ this._restoreBuiltIns();
508451
509− Error.stackTraceLimit = this._originalErrorStackTraceLimit;
510− this._isRavenInstalled = false;
452+ Error.stackTraceLimit = this._originalErrorStackTraceLimit;
453+ this._isRavenInstalled = false;
511454
512− return this;
513− },
455+ return this;
456+ },
514457
515− /*
458+ /*
516459 * Manually capture an exception and send it over to Sentry
517460 *
518461 * @param {error} ex An exception to be logged
519462 * @param {object} options A specific set of options for this error [optional]
520463 * @return {Raven}
521464 */
522− captureException: function(ex, options) {
523− // If not an Error is passed through, recall as a message instead
524− if (!isError(ex)) {
525− return this.captureMessage(
526− ex,
527− objectMerge(
528− {
529− trimHeadFrames: 1,
530− stacktrace: true // if we fall back to captureMessage, default to attempting a new trace
531− },
532− options
533− )
534− );
535− }
536−
537− // Store the raw exception object for potential debugging and introspection
538− this._lastCapturedException = ex;
465+ captureException: function(ex, options) {
466+ // Cases for sending ex as a message, rather than an exception
467+ var isNotError = !isError(ex);
468+ var isNotErrorEvent = !isErrorEvent(ex);
469+ var isErrorEventWithoutError = isErrorEvent(ex) && !ex.error;
470+
471+ if ((isNotError && isNotErrorEvent) || isErrorEventWithoutError) {
472+ return this.captureMessage(
473+ ex,
474+ objectMerge(
475+ {
476+ trimHeadFrames: 1,
477+ stacktrace: true // if we fall back to captureMessage, default to attempting a new trace
478+ },
479+ options
480+ )
481+ );
482+ }
539483
540− // TraceKit.report will re-raise any exception passed to it,
541− // which means you have to wrap it in try/catch. Instead, we
542− // can wrap it here and only re-raise if TraceKit.report
543− // raises an exception different from the one we asked to
544− // report on.
545− try {
546− var stack = TraceKit.computeStackTrace(ex);
547− this._handleStackInfo(stack, options);
548− } catch (ex1) {
549− if (ex !== ex1) {
550− throw ex1;
551− }
552− }
484+ // Get actual Error from ErrorEvent
485+ if (isErrorEvent(ex)) ex = ex.error;
486+
487+ // Store the raw exception object for potential debugging and introspection
488+ this._lastCapturedException = ex;
489+
490+ // TraceKit.report will re-raise any exception passed to it,
491+ // which means you have to wrap it in try/catch. Instead, we
492+ // can wrap it here and only re-raise if TraceKit.report
493+ // raises an exception different from the one we asked to
494+ // report on.
495+ try {
496+ var stack = TraceKit.computeStackTrace(ex);
497+ this._handleStackInfo(stack, options);
498+ } catch (ex1) {
499+ if (ex !== ex1) {
500+ throw ex1;
501+ }
502+ }
553503
554− return this;
555− },
504+ return this;
505+ },
556506
557− /*
507+ /*
558508 * Manually send a message to Sentry
559509 *
560510 * @param {string} msg A plain message to be captured in Sentry
561511 * @param {object} options A specific set of options for this message [optional]
562512 * @return {Raven}
563513 */
564− captureMessage: function(msg, options) {
565− // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an
566− // early call; we'll error on the side of logging anything called before configuration since it's
567− // probably something you should see:
568− if (
569− !!this._globalOptions.ignoreErrors.test &&
570− this._globalOptions.ignoreErrors.test(msg)
571− ) {
572− return;
573− }
514+ captureMessage: function(msg, options) {
515+ // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an
516+ // early call; we'll error on the side of logging anything called before configuration since it's
517+ // probably something you should see:
518+ if (
519+ !!this._globalOptions.ignoreErrors.test &&
520+ this._globalOptions.ignoreErrors.test(msg)
521+ ) {
522+ return;
523+ }
574524
575− options = options || {};
576−
577− var data = objectMerge(
578− {
579− message: msg + '' // Make sure it's actually a string
580− },
581− options
582− );
583−
584− if (this._globalOptions.stacktrace || (options && options.stacktrace)) {
585− var ex;
586− // Generate a "synthetic" stack trace from this point.
587− // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative
588− // of a bug with Raven.js. Sentry generates synthetic traces either by configuration,
589− // or if it catches a thrown object without a "stack" property.
590− try {
591− throw new Error(msg);
592− } catch (ex1) {
593− ex = ex1;
594− }
525+ options = options || {};
526+
527+ var data = objectMerge(
528+ {
529+ message: msg + '' // Make sure it's actually a string
530+ },
531+ options
532+ );
533+
534+ var ex;
535+ // Generate a "synthetic" stack trace from this point.
536+ // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative
537+ // of a bug with Raven.js. Sentry generates synthetic traces either by configuration,
538+ // or if it catches a thrown object without a "stack" property.
539+ try {
540+ throw new Error(msg);
541+ } catch (ex1) {
542+ ex = ex1;
543+ }
595544
596− // null exception name so `Error` isn't prefixed to msg
597− ex.name = null;
598−
599− options = objectMerge(
600− {
601− // fingerprint on msg, not stack trace (legacy behavior, could be
602− // revisited)
603− fingerprint: msg,
604− // since we know this is a synthetic trace, the top N-most frames
605− // MUST be from Raven.js, so mark them as in_app later by setting
606− // trimHeadFrames
607− trimHeadFrames: (options.trimHeadFrames || 0) + 1
608− },
609− options
610− );
611−
612− var stack = TraceKit.computeStackTrace(ex);
613− var frames = this._prepareFrames(stack, options);
614− data.stacktrace = {
615− // Sentry expects frames oldest to newest
616− frames: frames.reverse()
617− };
618− }
545+ // null exception name so `Error` isn't prefixed to msg
546+ ex.name = null;
547+ var stack = TraceKit.computeStackTrace(ex);
548+
549+ // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1]
550+ var initialCall = isArray(stack.stack) && stack.stack[1];
551+ var fileurl = (initialCall && initialCall.url) || '';
552+
553+ if (
554+ !!this._globalOptions.ignoreUrls.test &&
555+ this._globalOptions.ignoreUrls.test(fileurl)
556+ ) {
557+ return;
558+ }
619559
620− // Fire away!
621− this._send(data);
560+ if (
561+ !!this._globalOptions.whitelistUrls.test &&
562+ !this._globalOptions.whitelistUrls.test(fileurl)
563+ ) {
564+ return;
565+ }
622566
623− return this;
624− },
567+ if (this._globalOptions.stacktrace || (options && options.stacktrace)) {
568+ options = objectMerge(
569+ {
570+ // fingerprint on msg, not stack trace (legacy behavior, could be
571+ // revisited)
572+ fingerprint: msg,
573+ // since we know this is a synthetic trace, the top N-most frames
574+ // MUST be from Raven.js, so mark them as in_app later by setting
575+ // trimHeadFrames
576+ trimHeadFrames: (options.trimHeadFrames || 0) + 1
577+ },
578+ options
579+ );
580+
581+ var frames = this._prepareFrames(stack, options);
582+ data.stacktrace = {
583+ // Sentry expects frames oldest to newest
584+ frames: frames.reverse()
585+ };
586+ }
625587
626− captureBreadcrumb: function(obj) {
627− var crumb = objectMerge(
628− {
629− timestamp: now() / 1000
630− },
631− obj
632− );
588+ // Fire away!
589+ this._send(data);
633590
634− if (isFunction(this._globalOptions.breadcrumbCallback)) {
635− var result = this._globalOptions.breadcrumbCallback(crumb);
591+ return this;
592+ },
636593
637− if (isObject(result) && !isEmptyObject(result)) {
638− crumb = result;
639− } else if (result === false) {
640− return this;
641− }
642− }
594+ captureBreadcrumb: function(obj) {
595+ var crumb = objectMerge(
596+ {
597+ timestamp: now() / 1000
598+ },
599+ obj
600+ );
643601
644− this._breadcrumbs.push(crumb);
645− if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) {
646− this._breadcrumbs.shift();
647− }
648− return this;
649− },
602+ if (isFunction(this._globalOptions.breadcrumbCallback)) {
603+ var result = this._globalOptions.breadcrumbCallback(crumb);
604+
605+ if (isObject(result) && !isEmptyObject(result)) {
606+ crumb = result;
607+ } else if (result === false) {
608+ return this;
609+ }
610+ }
650611
651− addPlugin: function(plugin /*arg1, arg2, ... argN*/) {
652− var pluginArgs = [].slice.call(arguments, 1);
612+ this._breadcrumbs.push(crumb);
613+ if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) {
614+ this._breadcrumbs.shift();
615+ }
616+ return this;
617+ },
653618
654− this._plugins.push([plugin, pluginArgs]);
655− if (this._isRavenInstalled) {
656− this._drainPlugins();
657− }
619+ addPlugin: function(plugin /*arg1, arg2, ... argN*/) {
620+ var pluginArgs = [].slice.call(arguments, 1);
658621
659− return this;
660− },
622+ this._plugins.push([plugin, pluginArgs]);
623+ if (this._isRavenInstalled) {
624+ this._drainPlugins();
625+ }
626+
627+ return this;
628+ },
661629
662− /*
630+ /*
663631 * Set/clear a user to be sent along with the payload.
664632 *
665633 * @param {object} user An object representing user data [optional]
666634 * @return {Raven}
667635 */
668− setUserContext: function(user) {
669− // Intentionally do not merge here since that's an unexpected behavior.
670− this._globalContext.user = user;
636+ setUserContext: function(user) {
637+ // Intentionally do not merge here since that's an unexpected behavior.
638+ this._globalContext.user = user;
671639
672− return this;
673− },
640+ return this;
641+ },
674642
675− /*
643+ /*
676644 * Merge extra attributes to be sent along with the payload.
677645 *
678646 * @param {object} extra An object representing extra data [optional]
679647 * @return {Raven}
680648 */
681− setExtraContext: function(extra) {
682− this._mergeContext('extra', extra);
649+ setExtraContext: function(extra) {
650+ this._mergeContext('extra', extra);
683651
684− return this;
685− },
652+ return this;
653+ },
686654
687− /*
655+ /*
688656 * Merge tags to be sent along with the payload.
689657 *
690658 * @param {object} tags An object representing tags [optional]
691659 * @return {Raven}
692660 */
693− setTagsContext: function(tags) {
694− this._mergeContext('tags', tags);
661+ setTagsContext: function(tags) {
662+ this._mergeContext('tags', tags);
695663
696− return this;
697− },
664+ return this;
665+ },
698666
699− /*
667+ /*
700668 * Clear all of the context.
701669 *
702670 * @return {Raven}
703671 */
704− clearContext: function() {
705− this._globalContext = {};
672+ clearContext: function() {
673+ this._globalContext = {};
706674
707− return this;
708− },
675+ return this;
676+ },
709677
710− /*
678+ /*
711679 * Get a copy of the current context. This cannot be mutated.
712680 *
713681 * @return {object} copy of context
714682 */
715− getContext: function() {
716− // lol javascript
717− return JSON.parse(stringify(this._globalContext));
718− },
683+ getContext: function() {
684+ // lol javascript
685+ return JSON.parse(stringify(this._globalContext));
686+ },
719687
720− /*
688+ /*
721689 * Set environment of application
722690 *
723691 * @param {string} environment Typically something like 'production'.
724692 * @return {Raven}
725693 */
726− setEnvironment: function(environment) {
727− this._globalOptions.environment = environment;
694+ setEnvironment: function(environment) {
695+ this._globalOptions.environment = environment;
728696
729− return this;
730− },
697+ return this;
698+ },
731699
732− /*
700+ /*
733701 * Set release version of application
734702 *
735703 * @param {string} release Typically something like a git SHA to identify version
736704 * @return {Raven}
737705 */
738− setRelease: function(release) {
739− this._globalOptions.release = release;
706+ setRelease: function(release) {
707+ this._globalOptions.release = release;
740708
741− return this;
742− },
709+ return this;
710+ },
743711
744− /*
712+ /*
745713 * Set the dataCallback option
746714 *
747715 * @param {function} callback The callback to run which allows the
748716 * data blob to be mutated before sending
749717 * @return {Raven}
750718 */
751− setDataCallback: function(callback) {
752− var original = this._globalOptions.dataCallback;
753− this._globalOptions.dataCallback = keepOriginalCallback(
754− original,
755− callback
756− );
757− return this;
758− },
759−
760− /*
719+ setDataCallback: function(callback) {
720+ var original = this._globalOptions.dataCallback;
721+ this._globalOptions.dataCallback = keepOriginalCallback(original, callback);
722+ return this;
723+ },
724+
725+ /*
761726 * Set the breadcrumbCallback option
762727 *
763728 * @param {function} callback The callback to run which allows filtering
764729 * or mutating breadcrumbs
765730 * @return {Raven}
766731 */
767− setBreadcrumbCallback: function(callback) {
768− var original = this._globalOptions.breadcrumbCallback;
769− this._globalOptions.breadcrumbCallback = keepOriginalCallback(
770− original,
771− callback
772− );
773− return this;
774− },
775−
776− /*
732+ setBreadcrumbCallback: function(callback) {
733+ var original = this._globalOptions.breadcrumbCallback;
734+ this._globalOptions.breadcrumbCallback = keepOriginalCallback(original, callback);
735+ return this;
736+ },
737+
738+ /*
777739 * Set the shouldSendCallback option
778740 *
779741 * @param {function} callback The callback to run which allows
780742 * introspecting the blob before sending
781743 * @return {Raven}
782744 */
783− setShouldSendCallback: function(callback) {
784− var original = this._globalOptions.shouldSendCallback;
785− this._globalOptions.shouldSendCallback = keepOriginalCallback(
786− original,
787− callback
788− );
789− return this;
790− },
791−
792− /**
745+ setShouldSendCallback: function(callback) {
746+ var original = this._globalOptions.shouldSendCallback;
747+ this._globalOptions.shouldSendCallback = keepOriginalCallback(original, callback);
748+ return this;
749+ },
750+
751+ /**
793752 * Override the default HTTP transport mechanism that transmits data
794753 * to the Sentry server.
795754 *
@@ -798,438 +757,443 @@ · 2 unmodified lines
798757 *
799758 * @return {Raven}
800759 */
801− setTransport: function(transport) {
802− this._globalOptions.transport = transport;
760+ setTransport: function(transport) {
761+ this._globalOptions.transport = transport;
803762
804− return this;
805− },
763+ return this;
764+ },
806765
807− /*
766+ /*
808767 * Get the latest raw exception that was captured by Raven.
809768 *
810769 * @return {error}
811770 */
812− lastException: function() {
813− return this._lastCapturedException;
814− },
771+ lastException: function() {
772+ return this._lastCapturedException;
773+ },
815774
816− /*
775+ /*
817776 * Get the last event id
818777 *
819778 * @return {string}
820779 */
821− lastEventId: function() {
822− return this._lastEventId;
823− },
780+ lastEventId: function() {
781+ return this._lastEventId;
782+ },
824783
825− /*
784+ /*
826785 * Determine if Raven is setup and ready to go.
827786 *
828787 * @return {boolean}
829788 */
830− isSetup: function() {
831− if (!this._hasJSON) return false; // needs JSON support
832− if (!this._globalServer) {
833− if (!this.ravenNotConfiguredError) {
834− this.ravenNotConfiguredError = true;
835− this._logDebug('error', 'Error: Raven has not been configured.');
836− }
837− return false;
838− }
839− return true;
840− },
789+ isSetup: function() {
790+ if (!this._hasJSON) return false; // needs JSON support
791+ if (!this._globalServer) {
792+ if (!this.ravenNotConfiguredError) {
793+ this.ravenNotConfiguredError = true;
794+ this._logDebug('error', 'Error: Raven has not been configured.');
795+ }
796+ return false;
797+ }
798+ return true;
799+ },
841800
842− afterLoad: function() {
843− // TODO: remove window dependence?
801+ afterLoad: function() {
802+ // TODO: remove window dependence?
844803
845− // Attempt to initialize Raven on load
846− var RavenConfig = _window.RavenConfig;
847− if (RavenConfig) {
848− this.config(RavenConfig.dsn, RavenConfig.config).install();
849− }
850− },
804+ // Attempt to initialize Raven on load
805+ var RavenConfig = _window.RavenConfig;
806+ if (RavenConfig) {
807+ this.config(RavenConfig.dsn, RavenConfig.config).install();
808+ }
809+ },
851810
852− showReportDialog: function(options) {
853− if (
854− !_document // doesn't work without a document (React native)
855− )
856− return;
811+ showReportDialog: function(options) {
812+ if (
813+ !_document // doesn't work without a document (React native)
814+ )
815+ return;
857816
858− options = options || {};
817+ options = options || {};
859818
860− var lastEventId = options.eventId || this.lastEventId();
861− if (!lastEventId) {
862− throw new RavenConfigError('Missing eventId');
863− }
819+ var lastEventId = options.eventId || this.lastEventId();
820+ if (!lastEventId) {
821+ throw new RavenConfigError('Missing eventId');
822+ }
864823
865− var dsn = options.dsn || this._dsn;
866− if (!dsn) {
867− throw new RavenConfigError('Missing DSN');
868− }
824+ var dsn = options.dsn || this._dsn;
825+ if (!dsn) {
826+ throw new RavenConfigError('Missing DSN');
827+ }
869828
870− var encode = encodeURIComponent;
871− var qs = '';
872− qs += '?eventId=' + encode(lastEventId);
873− qs += '&dsn=' + encode(dsn);
829+ var encode = encodeURIComponent;
830+ var qs = '';
831+ qs += '?eventId=' + encode(lastEventId);
832+ qs += '&dsn=' + encode(dsn);
874833
875− var user = options.user || this._globalContext.user;
876− if (user) {
877− if (user.name) qs += '&name=' + encode(user.name);
878− if (user.email) qs += '&email=' + encode(user.email);
879− }
834+ var user = options.user || this._globalContext.user;
835+ if (user) {
836+ if (user.name) qs += '&name=' + encode(user.name);
837+ if (user.email) qs += '&email=' + encode(user.email);
838+ }
880839
881− var globalServer = this._getGlobalServer(this._parseDSN(dsn));
882−
883− var script = _document.createElement('script');
884− script.async = true;
885− script.src = globalServer + '/api/embed/error-page/' + qs;
886− (_document.head || _document.body).appendChild(script);
887− },
888−
889− /**** Private functions ****/
890− _ignoreNextOnError: function() {
891− var self = this;
892− this._ignoreOnError += 1;
893− setTimeout(function() {
894− // onerror should trigger before setTimeout
895− self._ignoreOnError -= 1;
896− });
897− },
840+ var globalServer = this._getGlobalServer(this._parseDSN(dsn));
898841
899− _triggerEvent: function(eventType, options) {
900− // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it
901− var evt, key;
842+ var script = _document.createElement('script');
843+ script.async = true;
844+ script.src = globalServer + '/api/embed/error-page/' + qs;
845+ (_document.head || _document.body).appendChild(script);
846+ },
902847
903− if (!this._hasDocument) return;
848+ /**** Private functions ****/
849+ _ignoreNextOnError: function() {
850+ var self = this;
851+ this._ignoreOnError += 1;
852+ setTimeout(function() {
853+ // onerror should trigger before setTimeout
854+ self._ignoreOnError -= 1;
855+ });
856+ },
904857
905− options = options || {};
858+ _triggerEvent: function(eventType, options) {
859+ // NOTE: `event` is a native browser thing, so let's avoid conflicting with it
860+ var evt, key;
906861
907− eventType =
908− 'raven' + eventType.substr(0, 1).toUpperCase() + eventType.substr(1);
862+ if (!this._hasDocument) return;
909863
910− if (_document.createEvent) {
911− evt = _document.createEvent('HTMLEvents');
912− evt.initEvent(eventType, true, true);
913− } else {
914− evt = _document.createEventObject();
915− evt.eventType = eventType;
916− }
864+ options = options || {};
917865
918− for (key in options)
919− if (hasKey(options, key)) {
920− evt[key] = options[key];
921− }
866+ eventType = 'raven' + eventType.substr(0, 1).toUpperCase() + eventType.substr(1);
922867
923− if (_document.createEvent) {
924− // IE9 if standards
925− _document.dispatchEvent(evt);
926− } else {
927− // IE8 regardless of Quirks or Standards
928− // IE9 if quirks
929− try {
930− _document.fireEvent('on' + evt.eventType.toLowerCase(), evt);
931− } catch (e) {
932− // Do nothing
933− }
934− }
935− },
868+ if (_document.createEvent) {
869+ evt = _document.createEvent('HTMLEvents');
870+ evt.initEvent(eventType, true, true);
871+ } else {
872+ evt = _document.createEventObject();
873+ evt.eventType = eventType;
874+ }
875+
876+ for (key in options)
877+ if (hasKey(options, key)) {
878+ evt[key] = options[key];
879+ }
880+
881+ if (_document.createEvent) {
882+ // IE9 if standards
883+ _document.dispatchEvent(evt);
884+ } else {
885+ // IE8 regardless of Quirks or Standards
886+ // IE9 if quirks
887+ try {
888+ _document.fireEvent('on' + evt.eventType.toLowerCase(), evt);
889+ } catch (e) {
890+ // Do nothing
891+ }
892+ }
893+ },
936894
937− /**
895+ /**
938896 * Wraps addEventListener to capture UI breadcrumbs
939897 * @param evtName the event name (e.g. "click")
940898 * @returns {Function}
941899 * @private
942900 */
943− _breadcrumbEventHandler: function(evtName) {
944− var self = this;
945− return function(evt) {
946− // reset keypress timeout; e.g. triggering a 'click' after
947− // a 'keypress' will reset the keypress debounce so that a new
948− // set of keypresses can be recorded
949− self._keypressTimeout = null;
950−
951− // It's possible this handler might trigger multiple times for the same
952− // event (e.g. event propagation through node ancestors). Ignore if we've
953− // already captured the event.
954− if (self._lastCapturedEvent === evt) return;
955−
956− self._lastCapturedEvent = evt;
957−
958− // try/catch both:
959− // - accessing evt.target (see getsentry/raven-js#838, #768)
960− // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly
961− // can throw an exception in some circumstances.
962− var target;
963− try {
964− target = htmlTreeAsString(evt.target);
965− } catch (e) {
966− target = '<unknown>';
967− }
901+ _breadcrumbEventHandler: function(evtName) {
902+ var self = this;
903+ return function(evt) {
904+ // reset keypress timeout; e.g. triggering a 'click' after
905+ // a 'keypress' will reset the keypress debounce so that a new
906+ // set of keypresses can be recorded
907+ self._keypressTimeout = null;
908+
909+ // It's possible this handler might trigger multiple times for the same
910+ // event (e.g. event propagation through node ancestors). Ignore if we've
911+ // already captured the event.
912+ if (self._lastCapturedEvent === evt) return;
913+
914+ self._lastCapturedEvent = evt;
915+
916+ // try/catch both:
917+ // - accessing evt.target (see getsentry/raven-js#838, #768)
918+ // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly
919+ // can throw an exception in some circumstances.
920+ var target;
921+ try {
922+ target = htmlTreeAsString(evt.target);
923+ } catch (e) {
924+ target = '<unknown>';
925+ }
968926
969− self.captureBreadcrumb({
970− category: 'ui.' + evtName, // e.g. ui.click, ui.input
971− message: target
972− });
973− };
974− },
927+ self.captureBreadcrumb({
928+ category: 'ui.' + evtName, // e.g. ui.click, ui.input
929+ message: target
930+ });
931+ };
932+ },
975933
976− /**
934+ /**
977935 * Wraps addEventListener to capture keypress UI events
978936 * @returns {Function}
979937 * @private
980938 */
981− _keypressEventHandler: function() {
982− var self = this,
983− debounceDuration = 1000; // milliseconds
984−
985− // TODO: if somehow user switches keypress target before
986− // debounce timeout is triggered, we will only capture
987− // a single breadcrumb from the FIRST target (acceptable?)
988− return function(evt) {
989− var target;
990− try {
991− target = evt.target;
992− } catch (e) {
993− // just accessing event properties can throw an exception in some rare circumstances
994− // see: https://github.com/getsentry/raven-js/issues/838
995− return;
996− }
997− var tagName = target && target.tagName;
998−
999− // only consider keypress events on actual input elements
1000− // this will disregard keypresses targeting body (e.g. tabbing
1001− // through elements, hotkeys, etc)
1002− if (
1003− !tagName ||
1004− (tagName !== 'INPUT' &&
1005− tagName !== 'TEXTAREA' &&
1006− !target.isContentEditable)
1007− )
1008− return;
1009−
1010− // record first keypress in a series, but ignore subsequent
1011− // keypresses until debounce clears
1012− var timeout = self._keypressTimeout;
1013− if (!timeout) {
1014− self._breadcrumbEventHandler('input')(evt);
1015− }
1016− clearTimeout(timeout);
1017− self._keypressTimeout = setTimeout(function() {
1018− self._keypressTimeout = null;
1019− }, debounceDuration);
1020− };
1021− },
1022−
1023− /**
939+ _keypressEventHandler: function() {
940+ var self = this,
941+ debounceDuration = 1000; // milliseconds
942+
943+ // TODO: if somehow user switches keypress target before
944+ // debounce timeout is triggered, we will only capture
945+ // a single breadcrumb from the FIRST target (acceptable?)
946+ return function(evt) {
947+ var target;
948+ try {
949+ target = evt.target;
950+ } catch (e) {
951+ // just accessing event properties can throw an exception in some rare circumstances
952+ // see: https://github.com/getsentry/raven-js/issues/838
953+ return;
954+ }
955+ var tagName = target && target.tagName;
956+
957+ // only consider keypress events on actual input elements
958+ // this will disregard keypresses targeting body (e.g. tabbing
959+ // through elements, hotkeys, etc)
960+ if (
961+ !tagName ||
962+ (tagName !== 'INPUT' && tagName !== 'TEXTAREA' && !target.isContentEditable)
963+ )
964+ return;
965+
966+ // record first keypress in a series, but ignore subsequent
967+ // keypresses until debounce clears
968+ var timeout = self._keypressTimeout;
969+ if (!timeout) {
970+ self._breadcrumbEventHandler('input')(evt);
971+ }
972+ clearTimeout(timeout);
973+ self._keypressTimeout = setTimeout(function() {
974+ self._keypressTimeout = null;
975+ }, debounceDuration);
976+ };
977+ },
978+
979+ /**
1024980 * Captures a breadcrumb of type "navigation", normalizing input URLs
1025981 * @param to the originating URL
1026982 * @param from the target URL
1027983 * @private
1028984 */
1029− _captureUrlChange: function(from, to) {
1030− var parsedLoc = parseUrl(this._location.href);
1031− var parsedTo = parseUrl(to);
1032− var parsedFrom = parseUrl(from);
1033−
1034− // because onpopstate only tells you the "new" (to) value of location.href, and
1035− // not the previous (from) value, we need to track the value of the current URL
1036− // state ourselves
1037− this._lastHref = to;
1038−
1039− // Use only the path component of the URL if the URL matches the current
1040− // document (almost all the time when using pushState)
1041− if (
1042− parsedLoc.protocol === parsedTo.protocol &&
1043− parsedLoc.host === parsedTo.host
1044− )
1045− to = parsedTo.relative;
1046− if (
1047− parsedLoc.protocol === parsedFrom.protocol &&
1048− parsedLoc.host === parsedFrom.host
1049− )
1050− from = parsedFrom.relative;
1051−
1052− this.captureBreadcrumb({
1053− category: 'navigation',
1054− data: {
1055− to: to,
1056− from: from
1057− }
1058− });
1059− },
985+ _captureUrlChange: function(from, to) {
986+ var parsedLoc = parseUrl(this._location.href);
987+ var parsedTo = parseUrl(to);
988+ var parsedFrom = parseUrl(from);
989+
990+ // because onpopstate only tells you the "new" (to) value of location.href, and
991+ // not the previous (from) value, we need to track the value of the current URL
992+ // state ourselves
993+ this._lastHref = to;
994+
995+ // Use only the path component of the URL if the URL matches the current
996+ // document (almost all the time when using pushState)
997+ if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host)
998+ to = parsedTo.relative;
999+ if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host)
1000+ from = parsedFrom.relative;
1001+
1002+ this.captureBreadcrumb({
1003+ category: 'navigation',
1004+ data: {
1005+ to: to,
1006+ from: from
1007+ }
1008+ });
1009+ },
1010+
1011+ _patchFunctionToString: function() {
1012+ var self = this;
1013+ self._originalFunctionToString = Function.prototype.toString;
1014+ // eslint-disable-next-line no-extend-native
1015+ Function.prototype.toString = function() {
1016+ if (typeof this === 'function' && this.__raven__) {
1017+ return self._originalFunctionToString.apply(this.__orig__, arguments);
1018+ }
1019+ return self._originalFunctionToString.apply(this, arguments);
1020+ };
1021+ },
1022+
1023+ _unpatchFunctionToString: function() {
1024+ if (this._originalFunctionToString) {
1025+ // eslint-disable-next-line no-extend-native
1026+ Function.prototype.toString = this._originalFunctionToString;
1027+ }
1028+ },
10601029
1061− /**
1030+ /**
10621031 * Wrap timer functions and event targets to catch errors and provide
10631032 * better metadata.
10641033 */
1065− _instrumentTryCatch: function() {
1066− var self = this;
1067−
1068− var wrappedBuiltIns = self._wrappedBuiltIns;
1069−
1070− function wrapTimeFn(orig) {
1071− return function(fn, t) {
1072− // preserve arity
1073− // Make a copy of the arguments to prevent deoptimization
1074− // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
1075− var args = new Array(arguments.length);
1076− for (var i = 0; i < args.length; ++i) {
1077− args[i] = arguments[i];
1078− }
1079− var originalCallback = args[0];
1080− if (isFunction(originalCallback)) {
1081− args[0] = self.wrap(originalCallback);
1082− }
1083−
1084− // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
1085− // also supports only two arguments and doesn't care what this is, so we
1086− // can just call the original function directly.
1087− if (orig.apply) {
1088− return orig.apply(this, args);
1089− } else {
1090− return orig(args[0], args[1]);
1091− }
1092− };
1093− }
1034+ _instrumentTryCatch: function() {
1035+ var self = this;
1036+
1037+ var wrappedBuiltIns = self._wrappedBuiltIns;
1038+
1039+ function wrapTimeFn(orig) {
1040+ return function(fn, t) {
1041+ // preserve arity
1042+ // Make a copy of the arguments to prevent deoptimization
1043+ // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
1044+ var args = new Array(arguments.length);
1045+ for (var i = 0; i < args.length; ++i) {
1046+ args[i] = arguments[i];
1047+ }
1048+ var originalCallback = args[0];
1049+ if (isFunction(originalCallback)) {
1050+ args[0] = self.wrap(originalCallback);
1051+ }
1052+
1053+ // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
1054+ // also supports only two arguments and doesn't care what this is, so we
1055+ // can just call the original function directly.
1056+ if (orig.apply) {
1057+ return orig.apply(this, args);
1058+ } else {
1059+ return orig(args[0], args[1]);
1060+ }
1061+ };
1062+ }
1063+
1064+ var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
1065+
1066+ function wrapEventTarget(global) {
1067+ var proto = _window[global] && _window[global].prototype;
1068+ if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) {
1069+ fill(
1070+ proto,
1071+ 'addEventListener',
1072+ function(orig) {
1073+ return function(evtName, fn, capture, secure) {
1074+ // preserve arity
1075+ try {
1076+ if (fn && fn.handleEvent) {
1077+ fn.handleEvent = self.wrap(fn.handleEvent);
1078+ }
1079+ } catch (err) {
1080+ // can sometimes get 'Permission denied to access property "handle Event'
1081+ }
10941082
1095− var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
1096−
1097− function wrapEventTarget(global) {
1098− var proto = _window[global] && _window[global].prototype;
1099− if (
1100− proto &&
1101− proto.hasOwnProperty &&
1102− proto.hasOwnProperty('addEventListener')
1103− ) {
1104− fill(
1105− proto,
1106− 'addEventListener',
1107− function(orig) {
1108− return function(evtName, fn, capture, secure) {
1109− // preserve arity
1110− try {
1111− if (fn && fn.handleEvent) {
1112− fn.handleEvent = self.wrap(fn.handleEvent);
1113− }
1114− } catch (err) {
1115− // can sometimes get 'Permission denied to access property "handle Event'
1116− }
1117−
1118− // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`
1119− // so that we don't have more than one wrapper function
1120− var before, clickHandler, keypressHandler;
1121−
1122− if (
1123− autoBreadcrumbs &&
1124− autoBreadcrumbs.dom &&
1125− (global === 'EventTarget' || global === 'Node')
1126− ) {
1127− // NOTE: generating multiple handlers per addEventListener invocation, should
1128− // revisit and verify we can just use one (almost certainly)
1129− clickHandler = self._breadcrumbEventHandler('click');
1130− keypressHandler = self._keypressEventHandler();
1131− before = function(evt) {
1132− // need to intercept every DOM event in `before` argument, in case that
1133− // same wrapped method is re-used for different events (e.g. mousemove THEN click)
1134− // see #724
1135− if (!evt) return;
1136−
1137− var eventType;
1138− try {
1139− eventType = evt.type;
1140− } catch (e) {
1141− // just accessing event properties can throw an exception in some rare circumstances
1142− // see: https://github.com/getsentry/raven-js/issues/838
1143− return;
1144− }
1145− if (eventType === 'click') return clickHandler(evt);
1146− else if (eventType === 'keypress')
1147− return keypressHandler(evt);
1148− };
1149− }
1150− return orig.call(
1151− this,
1152− evtName,
1153− self.wrap(fn, undefined, before),
1154− capture,
1155− secure
1156− );
1157− };
1158− },
1159− wrappedBuiltIns
1160− );
1161− fill(
1162− proto,
1163− 'removeEventListener',
1164− function(orig) {
1165− return function(evt, fn, capture, secure) {
1166− try {
1167− fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn);
1168− } catch (e) {
1169− // ignore, accessing __raven_wrapper__ will throw in some Selenium environments
1170− }
1171− return orig.call(this, evt, fn, capture, secure);
1172− };
1173− },
1174− wrappedBuiltIns
1175− );
1083+ // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`
1084+ // so that we don't have more than one wrapper function
1085+ var before, clickHandler, keypressHandler;
1086+
1087+ if (
1088+ autoBreadcrumbs &&
1089+ autoBreadcrumbs.dom &&
1090+ (global === 'EventTarget' || global === 'Node')
1091+ ) {
1092+ // NOTE: generating multiple handlers per addEventListener invocation, should
1093+ // revisit and verify we can just use one (almost certainly)
1094+ clickHandler = self._breadcrumbEventHandler('click');
1095+ keypressHandler = self._keypressEventHandler();
1096+ before = function(evt) {
1097+ // need to intercept every DOM event in `before` argument, in case that
1098+ // same wrapped method is re-used for different events (e.g. mousemove THEN click)
1099+ // see #724
1100+ if (!evt) return;
1101+
1102+ var eventType;
1103+ try {
1104+ eventType = evt.type;
1105+ } catch (e) {
1106+ // just accessing event properties can throw an exception in some rare circumstances
1107+ // see: https://github.com/getsentry/raven-js/issues/838
1108+ return;
11761109 }
1177− }
1110+ if (eventType === 'click') return clickHandler(evt);
1111+ else if (eventType === 'keypress') return keypressHandler(evt);
1112+ };
1113+ }
1114+ return orig.call(
1115+ this,
1116+ evtName,
1117+ self.wrap(fn, undefined, before),
1118+ capture,
1119+ secure
1120+ );
1121+ };
1122+ },
1123+ wrappedBuiltIns
1124+ );
1125+ fill(
1126+ proto,
1127+ 'removeEventListener',
1128+ function(orig) {
1129+ return function(evt, fn, capture, secure) {
1130+ try {
1131+ fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn);
1132+ } catch (e) {
1133+ // ignore, accessing __raven_wrapper__ will throw in some Selenium environments
1134+ }
1135+ return orig.call(this, evt, fn, capture, secure);
1136+ };
1137+ },
1138+ wrappedBuiltIns
1139+ );
1140+ }
1141+ }
11781142
1179− fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns);
1180− fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns);
1181− if (_window.requestAnimationFrame) {
1182− fill(
1183− _window,
1184− 'requestAnimationFrame',
1185− function(orig) {
1186− return function(cb) {
1187− return orig(self.wrap(cb));
1188− };
1189− },
1190− wrappedBuiltIns
1191− );
1192− }
1143+ fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns);
1144+ fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns);
1145+ if (_window.requestAnimationFrame) {
1146+ fill(
1147+ _window,
1148+ 'requestAnimationFrame',
1149+ function(orig) {
1150+ return function(cb) {
1151+ return orig(self.wrap(cb));
1152+ };
1153+ },
1154+ wrappedBuiltIns
1155+ );
1156+ }
11931157
1194− // event targets borrowed from bugsnag-js:
1195− // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666
1196− var eventTargets = [
1197− 'EventTarget',
1198− 'Window',
1199− 'Node',
1200− 'ApplicationCache',
1201− 'AudioTrackList',
1202− 'ChannelMergerNode',
1203− 'CryptoOperation',
1204− 'EventSource',
1205− 'FileReader',
1206− 'HTMLUnknownElement',
1207− 'IDBDatabase',
1208− 'IDBRequest',
1209− 'IDBTransaction',
1210− 'KeyOperation',
1211− 'MediaController',
1212− 'MessagePort',
1213− 'ModalWindow',
1214− 'Notification',
1215− 'SVGElementInstance',
1216− 'Screen',
1217− 'TextTrack',
1218− 'TextTrackCue',
1219− 'TextTrackList',
1220− 'WebSocket',
1221− 'WebSocketWorker',
1222− 'Worker',
1223− 'XMLHttpRequest',
1224− 'XMLHttpRequestEventTarget',
1225− 'XMLHttpRequestUpload'
1226− ];
1227− for (var i = 0; i < eventTargets.length; i++) {
1228− wrapEventTarget(eventTargets[i]);
1229− }
1230− },
1158+ // event targets borrowed from bugsnag-js:
1159+ // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666
1160+ var eventTargets = [
1161+ 'EventTarget',
1162+ 'Window',
1163+ 'Node',
1164+ 'ApplicationCache',
1165+ 'AudioTrackList',
1166+ 'ChannelMergerNode',
1167+ 'CryptoOperation',
1168+ 'EventSource',
1169+ 'FileReader',
1170+ 'HTMLUnknownElement',
1171+ 'IDBDatabase',
1172+ 'IDBRequest',
1173+ 'IDBTransaction',
1174+ 'KeyOperation',
1175+ 'MediaController',
1176+ 'MessagePort',
1177+ 'ModalWindow',
1178+ 'Notification',
1179+ 'SVGElementInstance',
1180+ 'Screen',
1181+ 'TextTrack',
1182+ 'TextTrackCue',
1183+ 'TextTrackList',
1184+ 'WebSocket',
1185+ 'WebSocketWorker',
1186+ 'Worker',
1187+ 'XMLHttpRequest',
1188+ 'XMLHttpRequestEventTarget',
1189+ 'XMLHttpRequestUpload'
1190+ ];
1191+ for (var i = 0; i < eventTargets.length; i++) {
1192+ wrapEventTarget(eventTargets[i]);
1193+ }
1194+ },
12311195
1232− /**
1196+ /**
12331197 * Instrument browser built-ins w/ breadcrumb capturing
12341198 * - XMLHttpRequests
12351199 * - DOM interactions (click/typing)
@@ -1238,546 +1202,515 @@ · 2 unmodified lines
12381202 *
12391203 * Can be disabled or individually configured via the `autoBreadcrumbs` config option
12401204 */
1241− _instrumentBreadcrumbs: function() {
1242− var self = this;
1243− var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
1205+ _instrumentBreadcrumbs: function() {
1206+ var self = this;
1207+ var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
12441208
1245− var wrappedBuiltIns = self._wrappedBuiltIns;
1209+ var wrappedBuiltIns = self._wrappedBuiltIns;
12461210
1247− function wrapProp(prop, xhr) {
1248− if (prop in xhr && isFunction(xhr[prop])) {
1249− fill(xhr, prop, function(orig) {
1250− return self.wrap(orig);
1251− }); // intentionally don't track filled methods on XHR instances
1252− }
1253− }
1211+ function wrapProp(prop, xhr) {
1212+ if (prop in xhr && isFunction(xhr[prop])) {
1213+ fill(xhr, prop, function(orig) {
1214+ return self.wrap(orig);
1215+ }); // intentionally don't track filled methods on XHR instances
1216+ }
1217+ }
12541218
1255− if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) {
1256− var xhrproto = XMLHttpRequest.prototype;
1257− fill(
1258− xhrproto,
1259− 'open',
1260− function(origOpen) {
1261− return function(method, url) {
1262− // preserve arity
1263−
1264− // if Sentry key appears in URL, don't capture
1265− if (isString(url) && url.indexOf(self._globalKey) === -1) {
1266− this.__raven_xhr = {
1267− method: method,
1268− url: url,
1269− status_code: null
1270− };
1271− }
1272−
1273− return origOpen.apply(this, arguments);
1274− };
1275− },
1276− wrappedBuiltIns
1277− );
1278−
1279− fill(
1280− xhrproto,
1281− 'send',
1282− function(origSend) {
1283− return function(data) {
1284− // preserve arity
1285− var xhr = this;
1286−
1287− function onreadystatechangeHandler() {
1288− if (xhr.__raven_xhr && xhr.readyState === 4) {
1289− try {
1290− // touching statusCode in some platforms throws
1291− // an exception
1292− xhr.__raven_xhr.status_code = xhr.status;
1293− } catch (e) {
1294− /* do nothing */
1295− }
1296−
1297− self.captureBreadcrumb({
1298− type: 'http',
1299− category: 'xhr',
1300− data: xhr.__raven_xhr
1301− });
1302− }
1303− }
1304−
1305− var props = ['onload', 'onerror', 'onprogress'];
1306− for (var j = 0; j < props.length; j++) {
1307− wrapProp(props[j], xhr);
1308− }
1309−
1310− if (
1311− 'onreadystatechange' in xhr &&
1312− isFunction(xhr.onreadystatechange)
1313− ) {
1314− fill(
1315− xhr,
1316− 'onreadystatechange',
1317− function(orig) {
1318− return self.wrap(
1319− orig,
1320− undefined,
1321− onreadystatechangeHandler
1322− );
1323− } /* intentionally don't track this instrumentation */
1324− );
1325− } else {
1326− // if onreadystatechange wasn't actually set by the page on this xhr, we
1327− // are free to set our own and capture the breadcrumb
1328− xhr.onreadystatechange = onreadystatechangeHandler;
1329− }
1330−
1331− return origSend.apply(this, arguments);
1332− };
1333− },
1334− wrappedBuiltIns
1335− );
1336− }
1219+ if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) {
1220+ var xhrproto = XMLHttpRequest.prototype;
1221+ fill(
1222+ xhrproto,
1223+ 'open',
1224+ function(origOpen) {
1225+ return function(method, url) {
1226+ // preserve arity
1227+
1228+ // if Sentry key appears in URL, don't capture
1229+ if (isString(url) && url.indexOf(self._globalKey) === -1) {
1230+ this.__raven_xhr = {
1231+ method: method,
1232+ url: url,
1233+ status_code: null
1234+ };
1235+ }
13371236
1338− if (autoBreadcrumbs.xhr && 'fetch' in _window) {
1339− fill(
1340− _window,
1341− 'fetch',
1342− function(origFetch) {
1343− return function(fn, t) {
1344− // preserve arity
1345− // Make a copy of the arguments to prevent deoptimization
1346− // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
1347− var args = new Array(arguments.length);
1348− for (var i = 0; i < args.length; ++i) {
1349− args[i] = arguments[i];
1350− }
1351−
1352− var fetchInput = args[0];
1353− var method = 'GET';
1354− var url;
1355−
1356− if (typeof fetchInput === 'string') {
1357− url = fetchInput;
1358− } else {
1359− url = fetchInput.url;
1360− if (fetchInput.method) {
1361− method = fetchInput.method;
1362− }
1363− }
1364−
1365− if (args[1] && args[1].method) {
1366− method = args[1].method;
1367− }
1368−
1369− var fetchData = {
1370− method: method,
1371− url: url,
1372− status_code: null
1373− };
1374−
1375− self.captureBreadcrumb({
1376− type: 'http',
1377− category: 'fetch',
1378− data: fetchData
1379− });
1380−
1381− return origFetch.apply(this, args).then(function(response) {
1382− fetchData.status_code = response.status;
1383−
1384− return response;
1385− });
1386− };
1387− },
1388− wrappedBuiltIns
1389− );
1237+ return origOpen.apply(this, arguments);
1238+ };
1239+ },
1240+ wrappedBuiltIns
1241+ );
1242+
1243+ fill(
1244+ xhrproto,
1245+ 'send',
1246+ function(origSend) {
1247+ return function(data) {
1248+ // preserve arity
1249+ var xhr = this;
1250+
1251+ function onreadystatechangeHandler() {
1252+ if (xhr.__raven_xhr && xhr.readyState === 4) {
1253+ try {
1254+ // touching statusCode in some platforms throws
1255+ // an exception
1256+ xhr.__raven_xhr.status_code = xhr.status;
1257+ } catch (e) {
1258+ /* do nothing */
13901259 }
13911260
1392− // Capture breadcrumbs from any click that is unhandled / bubbled up all the way
1393− // to the document. Do this before we instrument addEventListener.
1394− if (autoBreadcrumbs.dom && this._hasDocument) {
1395− if (_document.addEventListener) {
1396− _document.addEventListener(
1397− 'click',
1398− self._breadcrumbEventHandler('click'),
1399− false
1400− );
1401− _document.addEventListener(
1402− 'keypress',
1403− self._keypressEventHandler(),
1404− false
1405− );
1406− } else {
1407− // IE8 Compatibility
1408− _document.attachEvent(
1409− 'onclick',
1410− self._breadcrumbEventHandler('click')
1411− );
1412− _document.attachEvent('onkeypress', self._keypressEventHandler());
1413− }
1414− }
1261+ self.captureBreadcrumb({
1262+ type: 'http',
1263+ category: 'xhr',
1264+ data: xhr.__raven_xhr
1265+ });
1266+ }
1267+ }
14151268
1416− // record navigation (URL) changes
1417− // NOTE: in Chrome App environment, touching history.pushState, *even inside
1418− // a try/catch block*, will cause Chrome to output an error to console.error
1419− // borrowed from: https://github.com/angular/angular.js/pull/13945/files
1420− var chrome = _window.chrome;
1421− var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime;
1422− var hasPushState =
1423− !isChromePackagedApp && _window.history && history.pushState;
1424− if (autoBreadcrumbs.location && hasPushState) {
1425− // TODO: remove onpopstate handler on uninstall()
1426− var oldOnPopState = _window.onpopstate;
1427− _window.onpopstate = function() {
1428− var currentHref = self._location.href;
1429− self._captureUrlChange(self._lastHref, currentHref);
1430−
1431− if (oldOnPopState) {
1432− return oldOnPopState.apply(this, arguments);
1433− }
1434− };
1435−
1436− fill(
1437− history,
1438− 'pushState',
1439− function(origPushState) {
1440− // note history.pushState.length is 0; intentionally not declaring
1441− // params to preserve 0 arity
1442− return function(/* state, title, url */) {
1443− var url = arguments.length > 2 ? arguments[2] : undefined;
1444−
1445− // url argument is optional
1446− if (url) {
1447− // coerce to string (this is what pushState does)
1448− self._captureUrlChange(self._lastHref, url + '');
1449− }
1450−
1451− return origPushState.apply(this, arguments);
1452− };
1453− },
1454− wrappedBuiltIns
1455− );
1456− }
1269+ var props = ['onload', 'onerror', 'onprogress'];
1270+ for (var j = 0; j < props.length; j++) {
1271+ wrapProp(props[j], xhr);
1272+ }
14571273
1458− if (autoBreadcrumbs.console && 'console' in _window && console.log) {
1459− // console
1460− var consoleMethodCallback = function(msg, data) {
1461− self.captureBreadcrumb({
1462− message: msg,
1463− level: data.level,
1464− category: 'console'
1465− });
1466− };
1467−
1468− each(['debug', 'info', 'warn', 'error', 'log'], function(_, level) {
1469− wrapConsoleMethod(console, level, consoleMethodCallback);
1470− });
1471− }
1472− },
1274+ if ('onreadystatechange' in xhr && isFunction(xhr.onreadystatechange)) {
1275+ fill(
1276+ xhr,
1277+ 'onreadystatechange',
1278+ function(orig) {
1279+ return self.wrap(orig, undefined, onreadystatechangeHandler);
1280+ } /* intentionally don't track this instrumentation */
1281+ );
1282+ } else {
1283+ // if onreadystatechange wasn't actually set by the page on this xhr, we
1284+ // are free to set our own and capture the breadcrumb
1285+ xhr.onreadystatechange = onreadystatechangeHandler;
1286+ }
1287+
1288+ return origSend.apply(this, arguments);
1289+ };
1290+ },
1291+ wrappedBuiltIns
1292+ );
1293+ }
14731294
1474− _restoreBuiltIns: function() {
1475− // restore any wrapped builtins
1476− var builtin;
1477− while (this._wrappedBuiltIns.length) {
1478− builtin = this._wrappedBuiltIns.shift();
1295+ if (autoBreadcrumbs.xhr && 'fetch' in _window) {
1296+ fill(
1297+ _window,
1298+ 'fetch',
1299+ function(origFetch) {
1300+ return function(fn, t) {
1301+ // preserve arity
1302+ // Make a copy of the arguments to prevent deoptimization
1303+ // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
1304+ var args = new Array(arguments.length);
1305+ for (var i = 0; i < args.length; ++i) {
1306+ args[i] = arguments[i];
1307+ }
14791308
1480− var obj = builtin[0],
1481− name = builtin[1],
1482− orig = builtin[2];
1309+ var fetchInput = args[0];
1310+ var method = 'GET';
1311+ var url;
14831312
1484− obj[name] = orig;
1485− }
1486− },
1313+ if (typeof fetchInput === 'string') {
1314+ url = fetchInput;
1315+ } else if ('Request' in _window && fetchInput instanceof _window.Request) {
1316+ url = fetchInput.url;
1317+ if (fetchInput.method) {
1318+ method = fetchInput.method;
1319+ }
1320+ } else {
1321+ url = '' + fetchInput;
1322+ }
14871323
1488− _drainPlugins: function() {
1489− var self = this;
1324+ if (args[1] && args[1].method) {
1325+ method = args[1].method;
1326+ }
14901327
1491− // FIX ME TODO
1492− each(this._plugins, function(_, plugin) {
1493− var installer = plugin[0];
1494− var args = plugin[1];
1495− installer.apply(self, [self].concat(args));
1496− });
1497− },
1328+ var fetchData = {
1329+ method: method,
1330+ url: url,
1331+ status_code: null
1332+ };
14981333
1499− _parseDSN: function(str) {
1500− var m = dsnPattern.exec(str),
1501− dsn = {},
1502− i = 7;
1334+ self.captureBreadcrumb({
1335+ type: 'http',
1336+ category: 'fetch',
1337+ data: fetchData
1338+ });
15031339
1504− try {
1505− while (i--) dsn[dsnKeys[i]] = m[i] || '';
1506− } catch (e) {
1507− throw new RavenConfigError('Invalid DSN: ' + str);
1508− }
1340+ return origFetch.apply(this, args).then(function(response) {
1341+ fetchData.status_code = response.status;
15091342
1510− if (dsn.pass && !this._globalOptions.allowSecretKey) {
1511− throw new RavenConfigError(
1512− 'Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'
1513− );
1514− }
1343+ return response;
1344+ });
1345+ };
1346+ },
1347+ wrappedBuiltIns
1348+ );
1349+ }
15151350
1516− return dsn;
1517− },
1351+ // Capture breadcrumbs from any click that is unhandled / bubbled up all the way
1352+ // to the document. Do this before we instrument addEventListener.
1353+ if (autoBreadcrumbs.dom && this._hasDocument) {
1354+ if (_document.addEventListener) {
1355+ _document.addEventListener('click', self._breadcrumbEventHandler('click'), false);
1356+ _document.addEventListener('keypress', self._keypressEventHandler(), false);
1357+ } else {
1358+ // IE8 Compatibility
1359+ _document.attachEvent('onclick', self._breadcrumbEventHandler('click'));
1360+ _document.attachEvent('onkeypress', self._keypressEventHandler());
1361+ }
1362+ }
15181363
1519− _getGlobalServer: function(uri) {
1520− // assemble the endpoint from the uri pieces
1521− var globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : '');
1364+ // record navigation (URL) changes
1365+ // NOTE: in Chrome App environment, touching history.pushState, *even inside
1366+ // a try/catch block*, will cause Chrome to output an error to console.error
1367+ // borrowed from: https://github.com/angular/angular.js/pull/13945/files
1368+ var chrome = _window.chrome;
1369+ var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime;
1370+ var hasPushAndReplaceState =
1371+ !isChromePackagedApp &&
1372+ _window.history &&
1373+ history.pushState &&
1374+ history.replaceState;
1375+ if (autoBreadcrumbs.location && hasPushAndReplaceState) {
1376+ // TODO: remove onpopstate handler on uninstall()
1377+ var oldOnPopState = _window.onpopstate;
1378+ _window.onpopstate = function() {
1379+ var currentHref = self._location.href;
1380+ self._captureUrlChange(self._lastHref, currentHref);
1381+
1382+ if (oldOnPopState) {
1383+ return oldOnPopState.apply(this, arguments);
1384+ }
1385+ };
1386+
1387+ var historyReplacementFunction = function(origHistFunction) {
1388+ // note history.pushState.length is 0; intentionally not declaring
1389+ // params to preserve 0 arity
1390+ return function(/* state, title, url */) {
1391+ var url = arguments.length > 2 ? arguments[2] : undefined;
1392+
1393+ // url argument is optional
1394+ if (url) {
1395+ // coerce to string (this is what pushState does)
1396+ self._captureUrlChange(self._lastHref, url + '');
1397+ }
15221398
1523− if (uri.protocol) {
1524− globalServer = uri.protocol + ':' + globalServer;
1525− }
1526− return globalServer;
1527− },
1399+ return origHistFunction.apply(this, arguments);
1400+ };
1401+ };
15281402
1529− _handleOnErrorStackInfo: function() {
1530− // if we are intentionally ignoring errors via onerror, bail out
1531− if (!this._ignoreOnError) {
1532− this._handleStackInfo.apply(this, arguments);
1533− }
1534− },
1403+ fill(history, 'pushState', historyReplacementFunction, wrappedBuiltIns);
1404+ fill(history, 'replaceState', historyReplacementFunction, wrappedBuiltIns);
1405+ }
15351406
1536− _handleStackInfo: function(stackInfo, options) {
1537− var frames = this._prepareFrames(stackInfo, options);
1407+ if (autoBreadcrumbs.console && 'console' in _window && console.log) {
1408+ // console
1409+ var consoleMethodCallback = function(msg, data) {
1410+ self.captureBreadcrumb({
1411+ message: msg,
1412+ level: data.level,
1413+ category: 'console'
1414+ });
1415+ };
1416+
1417+ each(['debug', 'info', 'warn', 'error', 'log'], function(_, level) {
1418+ wrapConsoleMethod(console, level, consoleMethodCallback);
1419+ });
1420+ }
1421+ },
15381422
1539− this._triggerEvent('handle', {
1540− stackInfo: stackInfo,
1541− options: options
1542− });
1423+ _restoreBuiltIns: function() {
1424+ // restore any wrapped builtins
1425+ var builtin;
1426+ while (this._wrappedBuiltIns.length) {
1427+ builtin = this._wrappedBuiltIns.shift();
15431428
1544− this._processException(
1545− stackInfo.name,
1546− stackInfo.message,
1547− stackInfo.url,
1548− stackInfo.lineno,
1549− frames,
1550− options
1551− );
1552− },
1553−
1554− _prepareFrames: function(stackInfo, options) {
1555− var self = this;
1556− var frames = [];
1557− if (stackInfo.stack && stackInfo.stack.length) {
1558− each(stackInfo.stack, function(i, stack) {
1559− var frame = self._normalizeFrame(stack, stackInfo.url);
1560− if (frame) {
1561− frames.push(frame);
1562− }
1563− });
1564−
1565− // e.g. frames captured via captureMessage throw
1566− if (options && options.trimHeadFrames) {
1567− for (
1568− var j = 0;
1569− j < options.trimHeadFrames && j < frames.length;
1570− j++
1571− ) {
1572− frames[j].in_app = false;
1573− }
1574− }
1575− }
1576− frames = frames.slice(0, this._globalOptions.stackTraceLimit);
1577− return frames;
1578− },
1579−
1580− _normalizeFrame: function(frame, stackInfoUrl) {
1581− // normalize the frames data
1582− var normalized = {
1583− filename: frame.url,
1584− lineno: frame.line,
1585− colno: frame.column,
1586− function: frame.func || '?'
1587− };
1429+ var obj = builtin[0],
1430+ name = builtin[1],
1431+ orig = builtin[2];
15881432
1589− // Case when we don't have any information about the error
1590− // E.g. throwing a string or raw object, instead of an `Error` in Firefox
1591− // Generating synthetic error doesn't add any value here
1592− //
1593− // We should probably somehow let a user know that they should fix their code
1594− if (!frame.url) {
1595− normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler
1596− }
1433+ obj[name] = orig;
1434+ }
1435+ },
1436+
1437+ _drainPlugins: function() {
1438+ var self = this;
1439+
1440+ // FIX ME TODO
1441+ each(this._plugins, function(_, plugin) {
1442+ var installer = plugin[0];
1443+ var args = plugin[1];
1444+ installer.apply(self, [self].concat(args));
1445+ });
1446+ },
1447+
1448+ _parseDSN: function(str) {
1449+ var m = dsnPattern.exec(str),
1450+ dsn = {},
1451+ i = 7;
1452+
1453+ try {
1454+ while (i--) dsn[dsnKeys[i]] = m[i] || '';
1455+ } catch (e) {
1456+ throw new RavenConfigError('Invalid DSN: ' + str);
1457+ }
15971458
1598− normalized.in_app = !// determine if an exception came from outside of our app
1599− // first we check the global includePaths list.
1600− (
1601− (!!this._globalOptions.includePaths.test &&
1602− !this._globalOptions.includePaths.test(normalized.filename)) ||
1603− // Now we check for fun, if the function name is Raven or TraceKit
1604− /(Raven|TraceKit)\./.test(normalized['function']) ||
1605− // finally, we do a last ditch effort and check for raven.min.js
1606− /raven\.(min\.)?js$/.test(normalized.filename)
1607− );
1608−
1609− return normalized;
1610− },
1611−
1612− _processException: function(
1613− type,
1614− message,
1615− fileurl,
1616− lineno,
1617− frames,
1618− options
1619− ) {
1620− var testString = (type || '') + ': ' + (message || '');
1621−
1622− if (
1623− !!this._globalOptions.ignoreErrors.test &&
1624− this._globalOptions.ignoreErrors.test(testString)
1625− )
1626− return;
1627−
1628− var stacktrace;
1629−
1630− if (frames && frames.length) {
1631− fileurl = frames[0].filename || fileurl;
1632− // Sentry expects frames oldest to newest
1633− // and JS sends them as newest to oldest
1634− frames.reverse();
1635− stacktrace = {frames: frames};
1636− } else if (fileurl) {
1637− stacktrace = {
1638− frames: [
1639− {
1640− filename: fileurl,
1641− lineno: lineno,
1642− in_app: true
1643− }
1644− ]
1645− };
1646− }
1459+ if (dsn.pass && !this._globalOptions.allowSecretKey) {
1460+ throw new RavenConfigError(
1461+ 'Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'
1462+ );
1463+ }
16471464
1648− if (
1649− !!this._globalOptions.ignoreUrls.test &&
1650− this._globalOptions.ignoreUrls.test(fileurl)
1651− )
1652− return;
1653− if (
1654− !!this._globalOptions.whitelistUrls.test &&
1655− !this._globalOptions.whitelistUrls.test(fileurl)
1656− )
1657− return;
1658−
1659− var data = objectMerge(
1660− {
1661− // sentry.interfaces.Exception
1662− exception: {
1663− values: [
1664− {
1665− type: type,
1666− value: message,
1667− stacktrace: stacktrace
1668− }
1669− ]
1670− },
1671− culprit: fileurl
1672− },
1673− options
1674− );
1675−
1676− // Fire away!
1677− this._send(data);
1678− },
1679−
1680− _trimPacket: function(data) {
1681− // For now, we only want to truncate the two different messages
1682− // but this could/should be expanded to just trim everything
1683− var max = this._globalOptions.maxMessageLength;
1684− if (data.message) {
1685− data.message = truncate(data.message, max);
1686− }
1687− if (data.exception) {
1688− var exception = data.exception.values[0];
1689− exception.value = truncate(exception.value, max);
1690− }
1465+ return dsn;
1466+ },
16911467
1692− var request = data.request;
1693− if (request) {
1694− if (request.url) {
1695− request.url = truncate(request.url, this._globalOptions.maxUrlLength);
1696− }
1697− if (request.Referer) {
1698− request.Referer = truncate(
1699− request.Referer,
1700− this._globalOptions.maxUrlLength
1701− );
1702− }
1703− }
1468+ _getGlobalServer: function(uri) {
1469+ // assemble the endpoint from the uri pieces
1470+ var globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : '');
1471+
1472+ if (uri.protocol) {
1473+ globalServer = uri.protocol + ':' + globalServer;
1474+ }
1475+ return globalServer;
1476+ },
1477+
1478+ _handleOnErrorStackInfo: function() {
1479+ // if we are intentionally ignoring errors via onerror, bail out
1480+ if (!this._ignoreOnError) {
1481+ this._handleStackInfo.apply(this, arguments);
1482+ }
1483+ },
1484+
1485+ _handleStackInfo: function(stackInfo, options) {
1486+ var frames = this._prepareFrames(stackInfo, options);
1487+
1488+ this._triggerEvent('handle', {
1489+ stackInfo: stackInfo,
1490+ options: options
1491+ });
1492+
1493+ this._processException(
1494+ stackInfo.name,
1495+ stackInfo.message,
1496+ stackInfo.url,
1497+ stackInfo.lineno,
1498+ frames,
1499+ options
1500+ );
1501+ },
1502+
1503+ _prepareFrames: function(stackInfo, options) {
1504+ var self = this;
1505+ var frames = [];
1506+ if (stackInfo.stack && stackInfo.stack.length) {
1507+ each(stackInfo.stack, function(i, stack) {
1508+ var frame = self._normalizeFrame(stack, stackInfo.url);
1509+ if (frame) {
1510+ frames.push(frame);
1511+ }
1512+ });
1513+
1514+ // e.g. frames captured via captureMessage throw
1515+ if (options && options.trimHeadFrames) {
1516+ for (var j = 0; j < options.trimHeadFrames && j < frames.length; j++) {
1517+ frames[j].in_app = false;
1518+ }
1519+ }
1520+ }
1521+ frames = frames.slice(0, this._globalOptions.stackTraceLimit);
1522+ return frames;
1523+ },
1524+
1525+ _normalizeFrame: function(frame, stackInfoUrl) {
1526+ // normalize the frames data
1527+ var normalized = {
1528+ filename: frame.url,
1529+ lineno: frame.line,
1530+ colno: frame.column,
1531+ function: frame.func || '?'
1532+ };
1533+
1534+ // Case when we don't have any information about the error
1535+ // E.g. throwing a string or raw object, instead of an `Error` in Firefox
1536+ // Generating synthetic error doesn't add any value here
1537+ //
1538+ // We should probably somehow let a user know that they should fix their code
1539+ if (!frame.url) {
1540+ normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler
1541+ }
1542+
1543+ normalized.in_app = !// determine if an exception came from outside of our app
1544+ // first we check the global includePaths list.
1545+ (
1546+ (!!this._globalOptions.includePaths.test &&
1547+ !this._globalOptions.includePaths.test(normalized.filename)) ||
1548+ // Now we check for fun, if the function name is Raven or TraceKit
1549+ /(Raven|TraceKit)\./.test(normalized['function']) ||
1550+ // finally, we do a last ditch effort and check for raven.min.js
1551+ /raven\.(min\.)?js$/.test(normalized.filename)
1552+ );
1553+
1554+ return normalized;
1555+ },
1556+
1557+ _processException: function(type, message, fileurl, lineno, frames, options) {
1558+ var prefixedMessage = (type ? type + ': ' : '') + (message || '');
1559+ if (
1560+ !!this._globalOptions.ignoreErrors.test &&
1561+ (this._globalOptions.ignoreErrors.test(message) ||
1562+ this._globalOptions.ignoreErrors.test(prefixedMessage))
1563+ ) {
1564+ return;
1565+ }
1566+
1567+ var stacktrace;
1568+
1569+ if (frames && frames.length) {
1570+ fileurl = frames[0].filename || fileurl;
1571+ // Sentry expects frames oldest to newest
1572+ // and JS sends them as newest to oldest
1573+ frames.reverse();
1574+ stacktrace = {frames: frames};
1575+ } else if (fileurl) {
1576+ stacktrace = {
1577+ frames: [
1578+ {
1579+ filename: fileurl,
1580+ lineno: lineno,
1581+ in_app: true
1582+ }
1583+ ]
1584+ };
1585+ }
1586+
1587+ if (
1588+ !!this._globalOptions.ignoreUrls.test &&
1589+ this._globalOptions.ignoreUrls.test(fileurl)
1590+ ) {
1591+ return;
1592+ }
17041593
1705− if (data.breadcrumbs && data.breadcrumbs.values)
1706− this._trimBreadcrumbs(data.breadcrumbs);
1594+ if (
1595+ !!this._globalOptions.whitelistUrls.test &&
1596+ !this._globalOptions.whitelistUrls.test(fileurl)
1597+ ) {
1598+ return;
1599+ }
1600+
1601+ var data = objectMerge(
1602+ {
1603+ // sentry.interfaces.Exception
1604+ exception: {
1605+ values: [
1606+ {
1607+ type: type,
1608+ value: message,
1609+ stacktrace: stacktrace
1610+ }
1611+ ]
1612+ },
1613+ culprit: fileurl
1614+ },
1615+ options
1616+ );
1617+
1618+ // Fire away!
1619+ this._send(data);
1620+ },
1621+
1622+ _trimPacket: function(data) {
1623+ // For now, we only want to truncate the two different messages
1624+ // but this could/should be expanded to just trim everything
1625+ var max = this._globalOptions.maxMessageLength;
1626+ if (data.message) {
1627+ data.message = truncate(data.message, max);
1628+ }
1629+ if (data.exception) {
1630+ var exception = data.exception.values[0];
1631+ exception.value = truncate(exception.value, max);
1632+ }
17071633
1708− return data;
1709− },
1634+ var request = data.request;
1635+ if (request) {
1636+ if (request.url) {
1637+ request.url = truncate(request.url, this._globalOptions.maxUrlLength);
1638+ }
1639+ if (request.Referer) {
1640+ request.Referer = truncate(request.Referer, this._globalOptions.maxUrlLength);
1641+ }
1642+ }
17101643
1711− /**
1644+ if (data.breadcrumbs && data.breadcrumbs.values)
1645+ this._trimBreadcrumbs(data.breadcrumbs);
1646+
1647+ return data;
1648+ },
1649+
1650+ /**
17121651 * Truncate breadcrumb values (right now just URLs)
17131652 */
1714− _trimBreadcrumbs: function(breadcrumbs) {
1715− // known breadcrumb properties with urls
1716− // TODO: also consider arbitrary prop values that start with (https?)?://
1717− var urlProps = ['to', 'from', 'url'],
1718− urlProp,
1719− crumb,
1720− data;
1721−
1722− for (var i = 0; i < breadcrumbs.values.length; ++i) {
1723− crumb = breadcrumbs.values[i];
1724− if (
1725− !crumb.hasOwnProperty('data') ||
1726− !isObject(crumb.data) ||
1727− objectFrozen(crumb.data)
1728− )
1729− continue;
1730−
1731− data = objectMerge({}, crumb.data);
1732− for (var j = 0; j < urlProps.length; ++j) {
1733− urlProp = urlProps[j];
1734− if (data.hasOwnProperty(urlProp) && data[urlProp]) {
1735− data[urlProp] = truncate(
1736− data[urlProp],
1737− this._globalOptions.maxUrlLength
1738− );
1739− }
1740− }
1741− breadcrumbs.values[i].data = data;
1742− }
1743− },
1653+ _trimBreadcrumbs: function(breadcrumbs) {
1654+ // known breadcrumb properties with urls
1655+ // TODO: also consider arbitrary prop values that start with (https?)?://
1656+ var urlProps = ['to', 'from', 'url'],
1657+ urlProp,
1658+ crumb,
1659+ data;
1660+
1661+ for (var i = 0; i < breadcrumbs.values.length; ++i) {
1662+ crumb = breadcrumbs.values[i];
1663+ if (
1664+ !crumb.hasOwnProperty('data') ||
1665+ !isObject(crumb.data) ||
1666+ objectFrozen(crumb.data)
1667+ )
1668+ continue;
1669+
1670+ data = objectMerge({}, crumb.data);
1671+ for (var j = 0; j < urlProps.length; ++j) {
1672+ urlProp = urlProps[j];
1673+ if (data.hasOwnProperty(urlProp) && data[urlProp]) {
1674+ data[urlProp] = truncate(data[urlProp], this._globalOptions.maxUrlLength);
1675+ }
1676+ }
1677+ breadcrumbs.values[i].data = data;
1678+ }
1679+ },
17441680
1745− _getHttpData: function() {
1746− if (!this._hasNavigator && !this._hasDocument) return;
1747− var httpData = {};
1681+ _getHttpData: function() {
1682+ if (!this._hasNavigator && !this._hasDocument) return;
1683+ var httpData = {};
17481684
1749− if (this._hasNavigator && _navigator.userAgent) {
1750− httpData.headers = {
1751− 'User-Agent': navigator.userAgent
1752− };
1753− }
1685+ if (this._hasNavigator && _navigator.userAgent) {
1686+ httpData.headers = {
1687+ 'User-Agent': navigator.userAgent
1688+ };
1689+ }
17541690
1755− if (this._hasDocument) {
1756− if (_document.location && _document.location.href) {
1757− httpData.url = _document.location.href;
1758− }
1759− if (_document.referrer) {
1760− if (!httpData.headers) httpData.headers = {};
1761− httpData.headers.Referer = _document.referrer;
1762− }
1763− }
1691+ if (this._hasDocument) {
1692+ if (_document.location && _document.location.href) {
1693+ httpData.url = _document.location.href;
1694+ }
1695+ if (_document.referrer) {
1696+ if (!httpData.headers) httpData.headers = {};
1697+ httpData.headers.Referer = _document.referrer;
1698+ }
1699+ }
17641700
1765− return httpData;
1766− },
1701+ return httpData;
1702+ },
17671703
1768− _resetBackoff: function() {
1769− this._backoffDuration = 0;
1770− this._backoffStart = null;
1771− },
1704+ _resetBackoff: function() {
1705+ this._backoffDuration = 0;
1706+ this._backoffStart = null;
1707+ },
17721708
1773− _shouldBackoff: function() {
1774− return (
1775− this._backoffDuration &&
1776− now() - this._backoffStart < this._backoffDuration
1777− );
1778− },
1709+ _shouldBackoff: function() {
1710+ return this._backoffDuration && now() - this._backoffStart < this._backoffDuration;
1711+ },
17791712
1780− /**
1713+ /**
17811714 * Returns true if the in-process data payload matches the signature
17821715 * of the previously-sent data
17831716 *
@@ -1786,362 +1719,449 @@ · 2 unmodified lines
17861719 * other old browsers). This can take the form of an "exception"
17871720 * data object with a single frame (derived from the onerror args).
17881721 */
1789− _isRepeatData: function(current) {
1790− var last = this._lastData;
1791−
1792− if (
1793− !last ||
1794− current.message !== last.message || // defined for captureMessage
1795− current.culprit !== last.culprit // defined for captureException/onerror
1796− )
1797− return false;
1798−
1799− // Stacktrace interface (i.e. from captureMessage)
1800− if (current.stacktrace || last.stacktrace) {
1801− return isSameStacktrace(current.stacktrace, last.stacktrace);
1802− } else if (current.exception || last.exception) {
1803− // Exception interface (i.e. from captureException/onerror)
1804− return isSameException(current.exception, last.exception);
1805− }
1722+ _isRepeatData: function(current) {
1723+ var last = this._lastData;
1724+
1725+ if (
1726+ !last ||
1727+ current.message !== last.message || // defined for captureMessage
1728+ current.culprit !== last.culprit // defined for captureException/onerror
1729+ )
1730+ return false;
1731+
1732+ // Stacktrace interface (i.e. from captureMessage)
1733+ if (current.stacktrace || last.stacktrace) {
1734+ return isSameStacktrace(current.stacktrace, last.stacktrace);
1735+ } else if (current.exception || last.exception) {
1736+ // Exception interface (i.e. from captureException/onerror)
1737+ return isSameException(current.exception, last.exception);
1738+ }
18061739
1807− return true;
1808− },
1740+ return true;
1741+ },
18091742
1810− _setBackoffState: function(request) {
1811− // If we are already in a backoff state, don't change anything
1812− if (this._shouldBackoff()) {
1813− return;
1814− }
1743+ _setBackoffState: function(request) {
1744+ // If we are already in a backoff state, don't change anything
1745+ if (this._shouldBackoff()) {
1746+ return;
1747+ }
18151748
1816− var status = request.status;
1749+ var status = request.status;
1750+
1751+ // 400 - project_id doesn't exist or some other fatal
1752+ // 401 - invalid/revoked dsn
1753+ // 429 - too many requests
1754+ if (!(status === 400 || status === 401 || status === 429)) return;
1755+
1756+ var retry;
1757+ try {
1758+ // If Retry-After is not in Access-Control-Expose-Headers, most
1759+ // browsers will throw an exception trying to access it
1760+ retry = request.getResponseHeader('Retry-After');
1761+ retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds
1762+ } catch (e) {
1763+ /* eslint no-empty:0 */
1764+ }
18171765
1818− // 400 - project_id doesn't exist or some other fatal
1819− // 401 - invalid/revoked dsn
1820− // 429 - too many requests
1821− if (!(status === 400 || status === 401 || status === 429)) return;
1766+ this._backoffDuration = retry
1767+ ? // If Sentry server returned a Retry-After value, use it
1768+ retry
1769+ : // Otherwise, double the last backoff duration (starts at 1 sec)
1770+ this._backoffDuration * 2 || 1000;
18221771
1823− var retry;
1824− try {
1825− // If Retry-After is not in Access-Control-Expose-Headers, most
1826− // browsers will throw an exception trying to access it
1827− retry = request.getResponseHeader('Retry-After');
1828− retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds
1829− } catch (e) {
1830− /* eslint no-empty:0 */
1831− }
1772+ this._backoffStart = now();
1773+ },
18321774
1833− this._backoffDuration = retry
1834− ? // If Sentry server returned a Retry-After value, use it
1835− retry
1836− : // Otherwise, double the last backoff duration (starts at 1 sec)
1837− this._backoffDuration * 2 || 1000;
1775+ _send: function(data) {
1776+ var globalOptions = this._globalOptions;
18381777
1839− this._backoffStart = now();
1840− },
1778+ var baseData = {
1779+ project: this._globalProject,
1780+ logger: globalOptions.logger,
1781+ platform: 'javascript'
1782+ },
1783+ httpData = this._getHttpData();
18411784
1842− _send: function(data) {
1843− var globalOptions = this._globalOptions;
1785+ if (httpData) {
1786+ baseData.request = httpData;
1787+ }
18441788
1845− var baseData = {
1846− project: this._globalProject,
1847− logger: globalOptions.logger,
1848− platform: 'javascript'
1849− },
1850− httpData = this._getHttpData();
1789+ // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload
1790+ if (data.trimHeadFrames) delete data.trimHeadFrames;
18511791
1852− if (httpData) {
1853− baseData.request = httpData;
1854− }
1792+ data = objectMerge(baseData, data);
18551793
1856− // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload
1857− if (data.trimHeadFrames) delete data.trimHeadFrames;
1858−
1859− data = objectMerge(baseData, data);
1860−
1861− // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge
1862− data.tags = objectMerge(
1863− objectMerge({}, this._globalContext.tags),
1864− data.tags
1865− );
1866− data.extra = objectMerge(
1867− objectMerge({}, this._globalContext.extra),
1868− data.extra
1869− );
1870−
1871− // Send along our own collected metadata with extra
1872− data.extra['session:duration'] = now() - this._startTime;
1873−
1874− if (this._breadcrumbs && this._breadcrumbs.length > 0) {
1875− // intentionally make shallow copy so that additions
1876− // to breadcrumbs aren't accidentally sent in this request
1877− data.breadcrumbs = {
1878− values: [].slice.call(this._breadcrumbs, 0)
1879− };
1880− }
1794+ // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge
1795+ data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags);
1796+ data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra);
18811797
1882− // If there are no tags/extra, strip the key from the payload alltogther.
1883− if (isEmptyObject(data.tags)) delete data.tags;
1798+ // Send along our own collected metadata with extra
1799+ data.extra['session:duration'] = now() - this._startTime;
18841800
1885− if (this._globalContext.user) {
1886− // sentry.interfaces.User
1887− data.user = this._globalContext.user;
1888− }
1801+ if (this._breadcrumbs && this._breadcrumbs.length > 0) {
1802+ // intentionally make shallow copy so that additions
1803+ // to breadcrumbs aren't accidentally sent in this request
1804+ data.breadcrumbs = {
1805+ values: [].slice.call(this._breadcrumbs, 0)
1806+ };
1807+ }
18891808
1890− // Include the environment if it's defined in globalOptions
1891− if (globalOptions.environment)
1892− data.environment = globalOptions.environment;
1809+ // If there are no tags/extra, strip the key from the payload alltogther.
1810+ if (isEmptyObject(data.tags)) delete data.tags;
18931811
1894− // Include the release if it's defined in globalOptions
1895− if (globalOptions.release) data.release = globalOptions.release;
1812+ if (this._globalContext.user) {
1813+ // sentry.interfaces.User
1814+ data.user = this._globalContext.user;
1815+ }
18961816
1897− // Include server_name if it's defined in globalOptions
1898− if (globalOptions.serverName) data.server_name = globalOptions.serverName;
1817+ // Include the environment if it's defined in globalOptions
1818+ if (globalOptions.environment) data.environment = globalOptions.environment;
18991819
1900− if (isFunction(globalOptions.dataCallback)) {
1901− data = globalOptions.dataCallback(data) || data;
1902− }
1820+ // Include the release if it's defined in globalOptions
1821+ if (globalOptions.release) data.release = globalOptions.release;
19031822
1904− // Why??????????
1905− if (!data || isEmptyObject(data)) {
1906− return;
1907− }
1823+ // Include server_name if it's defined in globalOptions
1824+ if (globalOptions.serverName) data.server_name = globalOptions.serverName;
19081825
1909− // Check if the request should be filtered or not
1910− if (
1911− isFunction(globalOptions.shouldSendCallback) &&
1912− !globalOptions.shouldSendCallback(data)
1913− ) {
1914− return;
1915− }
1826+ if (isFunction(globalOptions.dataCallback)) {
1827+ data = globalOptions.dataCallback(data) || data;
1828+ }
19161829
1917− // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests),
1918− // so drop requests until "cool-off" period has elapsed.
1919− if (this._shouldBackoff()) {
1920− this._logDebug('warn', 'Raven dropped error due to backoff: ', data);
1921− return;
1922− }
1830+ // Why??????????
1831+ if (!data || isEmptyObject(data)) {
1832+ return;
1833+ }
19231834
1924− if (typeof globalOptions.sampleRate === 'number') {
1925− if (Math.random() < globalOptions.sampleRate) {
1926− this._sendProcessedPayload(data);
1927− }
1928− } else {
1929− this._sendProcessedPayload(data);
1930− }
1931− },
1835+ // Check if the request should be filtered or not
1836+ if (
1837+ isFunction(globalOptions.shouldSendCallback) &&
1838+ !globalOptions.shouldSendCallback(data)
1839+ ) {
1840+ return;
1841+ }
1842+
1843+ // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests),
1844+ // so drop requests until "cool-off" period has elapsed.
1845+ if (this._shouldBackoff()) {
1846+ this._logDebug('warn', 'Raven dropped error due to backoff: ', data);
1847+ return;
1848+ }
19321849
1933− _getUuid: function() {
1934− return uuid4();
1935− },
1850+ if (typeof globalOptions.sampleRate === 'number') {
1851+ if (Math.random() < globalOptions.sampleRate) {
1852+ this._sendProcessedPayload(data);
1853+ }
1854+ } else {
1855+ this._sendProcessedPayload(data);
1856+ }
1857+ },
19361858
1937− _sendProcessedPayload: function(data, callback) {
1938− var self = this;
1939− var globalOptions = this._globalOptions;
1859+ _getUuid: function() {
1860+ return uuid4();
1861+ },
19401862
1941− if (!this.isSetup()) return;
1863+ _sendProcessedPayload: function(data, callback) {
1864+ var self = this;
1865+ var globalOptions = this._globalOptions;
19421866
1943− // Try and clean up the packet before sending by truncating long values
1944− data = this._trimPacket(data);
1867+ if (!this.isSetup()) return;
19451868
1946− // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback,
1947− // but this would require copying an un-truncated copy of the data packet, which can be
1948− // arbitrarily deep (extra_data) -- could be worthwhile? will revisit
1949− if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) {
1950− this._logDebug('warn', 'Raven dropped repeat event: ', data);
1951− return;
1952− }
1869+ // Try and clean up the packet before sending by truncating long values
1870+ data = this._trimPacket(data);
19531871
1954− // Send along an event_id if not explicitly passed.
1955− // This event_id can be used to reference the error within Sentry itself.
1956− // Set lastEventId after we know the error should actually be sent
1957− this._lastEventId = data.event_id || (data.event_id = this._getUuid());
1872+ // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback,
1873+ // but this would require copying an un-truncated copy of the data packet, which can be
1874+ // arbitrarily deep (extra_data) -- could be worthwhile? will revisit
1875+ if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) {
1876+ this._logDebug('warn', 'Raven dropped repeat event: ', data);
1877+ return;
1878+ }
19581879
1959− // Store outbound payload after trim
1960− this._lastData = data;
1880+ // Send along an event_id if not explicitly passed.
1881+ // This event_id can be used to reference the error within Sentry itself.
1882+ // Set lastEventId after we know the error should actually be sent
1883+ this._lastEventId = data.event_id || (data.event_id = this._getUuid());
19611884
1962− this._logDebug('debug', 'Raven about to send:', data);
1885+ // Store outbound payload after trim
1886+ this._lastData = data;
19631887
1964− var auth = {
1965− sentry_version: '7',
1966− sentry_client: 'raven-js/' + this.VERSION,
1967− sentry_key: this._globalKey
1968− };
1888+ this._logDebug('debug', 'Raven about to send:', data);
19691889
1970− if (this._globalSecret) {
1971− auth.sentry_secret = this._globalSecret;
1972− }
1890+ var auth = {
1891+ sentry_version: '7',
1892+ sentry_client: 'raven-js/' + this.VERSION,
1893+ sentry_key: this._globalKey
1894+ };
19731895
1974− var exception = data.exception && data.exception.values[0];
1975− this.captureBreadcrumb({
1976− category: 'sentry',
1977− message: exception
1978− ? (exception.type ? exception.type + ': ' : '') + exception.value
1979− : data.message,
1980− event_id: data.event_id,
1981− level: data.level || 'error' // presume error unless specified
1982− });
1896+ if (this._globalSecret) {
1897+ auth.sentry_secret = this._globalSecret;
1898+ }
19831899
1984− var url = this._globalEndpoint;
1985− (globalOptions.transport || this._makeRequest).call(this, {
1986− url: url,
1987− auth: auth,
1988− data: data,
1989− options: globalOptions,
1990− onSuccess: function success() {
1991− self._resetBackoff();
1992−
1993− self._triggerEvent('success', {
1994− data: data,
1995− src: url
1996− });
1997− callback && callback();
1998− },
1999− onError: function failure(error) {
2000− self._logDebug('error', 'Raven transport failed to send: ', error);
2001−
2002− if (error.request) {
2003− self._setBackoffState(error.request);
2004− }
2005−
2006− self._triggerEvent('failure', {
2007− data: data,
2008− src: url
2009− });
2010− error =
2011− error ||
2012− new Error('Raven send failed (no additional details provided)');
2013− callback && callback(error);
2014− }
2015− });
2016− },
2017−
2018− _makeRequest: function(opts) {
2019− var request = _window.XMLHttpRequest && new _window.XMLHttpRequest();
2020− if (!request) return;
2021−
2022− // if browser doesn't support CORS (e.g. IE7), we are out of luck
2023− var hasCORS =
2024− 'withCredentials' in request || typeof XDomainRequest !== 'undefined';
2025−
2026− if (!hasCORS) return;
2027−
2028− var url = opts.url;
2029−
2030− if ('withCredentials' in request) {
2031− request.onreadystatechange = function() {
2032− if (request.readyState !== 4) {
2033− return;
2034− } else if (request.status === 200) {
2035− opts.onSuccess && opts.onSuccess();
2036− } else if (opts.onError) {
2037− var err = new Error('Sentry error code: ' + request.status);
2038− err.request = request;
2039− opts.onError(err);
2040− }
2041− };
2042− } else {
2043− request = new XDomainRequest();
2044− // xdomainrequest cannot go http -> https (or vice versa),
2045− // so always use protocol relative
2046− url = url.replace(/^https?:/, '');
2047−
2048− // onreadystatechange not supported by XDomainRequest
2049− if (opts.onSuccess) {
2050− request.onload = opts.onSuccess;
2051− }
2052− if (opts.onError) {
2053− request.onerror = function() {
2054− var err = new Error('Sentry error code: XDomainRequest');
2055− err.request = request;
2056− opts.onError(err);
2057− };
2058− }
2059− }
1900+ var exception = data.exception && data.exception.values[0];
1901+
1902+ // only capture 'sentry' breadcrumb is autoBreadcrumbs is truthy
1903+ if (
1904+ this._globalOptions.autoBreadcrumbs &&
1905+ this._globalOptions.autoBreadcrumbs.sentry
1906+ ) {
1907+ this.captureBreadcrumb({
1908+ category: 'sentry',
1909+ message: exception
1910+ ? (exception.type ? exception.type + ': ' : '') + exception.value
1911+ : data.message,
1912+ event_id: data.event_id,
1913+ level: data.level || 'error' // presume error unless specified
1914+ });
1915+ }
20601916
2061− // NOTE: auth is intentionally sent as part of query string (NOT as custom
2062− // HTTP header) so as to avoid preflight CORS requests
2063− request.open('POST', url + '?' + urlencode(opts.auth));
2064− request.send(stringify(opts.data));
2065− },
2066−
2067− _logDebug: function(level) {
2068− if (this._originalConsoleMethods[level] && this.debug) {
2069− // In IE<10 console methods do not have their own 'apply' method
2070− Function.prototype.apply.call(
2071− this._originalConsoleMethods[level],
2072− this._originalConsole,
2073− [].slice.call(arguments, 1)
2074− );
2075− }
2076− },
2077−
2078− _mergeContext: function(key, context) {
2079− if (isUndefined(context)) {
2080− delete this._globalContext[key];
2081− } else {
2082− this._globalContext[key] = objectMerge(
2083− this._globalContext[key] || {},
2084− context
2085− );
2086− }
2087− }
2088− };
1917+ var url = this._globalEndpoint;
1918+ (globalOptions.transport || this._makeRequest).call(this, {
1919+ url: url,
1920+ auth: auth,
1921+ data: data,
1922+ options: globalOptions,
1923+ onSuccess: function success() {
1924+ self._resetBackoff();
1925+
1926+ self._triggerEvent('success', {
1927+ data: data,
1928+ src: url
1929+ });
1930+ callback && callback();
1931+ },
1932+ onError: function failure(error) {
1933+ self._logDebug('error', 'Raven transport failed to send: ', error);
1934+
1935+ if (error.request) {
1936+ self._setBackoffState(error.request);
1937+ }
1938+
1939+ self._triggerEvent('failure', {
1940+ data: data,
1941+ src: url
1942+ });
1943+ error = error || new Error('Raven send failed (no additional details provided)');
1944+ callback && callback(error);
1945+ }
1946+ });
1947+ },
1948+
1949+ _makeRequest: function(opts) {
1950+ var request = _window.XMLHttpRequest && new _window.XMLHttpRequest();
1951+ if (!request) return;
1952+
1953+ // if browser doesn't support CORS (e.g. IE7), we are out of luck
1954+ var hasCORS = 'withCredentials' in request || typeof XDomainRequest !== 'undefined';
1955+
1956+ if (!hasCORS) return;
1957+
1958+ var url = opts.url;
1959+
1960+ if ('withCredentials' in request) {
1961+ request.onreadystatechange = function() {
1962+ if (request.readyState !== 4) {
1963+ return;
1964+ } else if (request.status === 200) {
1965+ opts.onSuccess && opts.onSuccess();
1966+ } else if (opts.onError) {
1967+ var err = new Error('Sentry error code: ' + request.status);
1968+ err.request = request;
1969+ opts.onError(err);
1970+ }
1971+ };
1972+ } else {
1973+ request = new XDomainRequest();
1974+ // xdomainrequest cannot go http -> https (or vice versa),
1975+ // so always use protocol relative
1976+ url = url.replace(/^https?:/, '');
1977+
1978+ // onreadystatechange not supported by XDomainRequest
1979+ if (opts.onSuccess) {
1980+ request.onload = opts.onSuccess;
1981+ }
1982+ if (opts.onError) {
1983+ request.onerror = function() {
1984+ var err = new Error('Sentry error code: XDomainRequest');
1985+ err.request = request;
1986+ opts.onError(err);
1987+ };
1988+ }
1989+ }
1990+
1991+ // NOTE: auth is intentionally sent as part of query string (NOT as custom
1992+ // HTTP header) so as to avoid preflight CORS requests
1993+ request.open('POST', url + '?' + urlencode(opts.auth));
1994+ request.send(stringify(opts.data));
1995+ },
1996+
1997+ _logDebug: function(level) {
1998+ if (this._originalConsoleMethods[level] && this.debug) {
1999+ // In IE<10 console methods do not have their own 'apply' method
2000+ Function.prototype.apply.call(
2001+ this._originalConsoleMethods[level],
2002+ this._originalConsole,
2003+ [].slice.call(arguments, 1)
2004+ );
2005+ }
2006+ },
2007+
2008+ _mergeContext: function(key, context) {
2009+ if (isUndefined(context)) {
2010+ delete this._globalContext[key];
2011+ } else {
2012+ this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context);
2013+ }
2014+ }
2015+};
2016+
2017+// Deprecations
2018+Raven.prototype.setUser = Raven.prototype.setUserContext;
2019+Raven.prototype.setReleaseContext = Raven.prototype.setRelease;
2020+
2021+module.exports = Raven;
20892022
2090− /*------------------------------------------------
2091− * utils
2023+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
2024+},{"1":1,"2":2,"5":5,"6":6,"7":7}],4:[function(_dereq_,module,exports){
2025+(function (global){
2026+/**
2027+ * Enforces a single instance of the Raven client, and the
2028+ * main entry point for Raven. If you are a consumer of the
2029+ * Raven library, you SHOULD load this file (vs raven.js).
2030+ **/
2031+
2032+var RavenConstructor = _dereq_(3);
2033+
2034+// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
2035+var _window =
2036+ typeof window !== 'undefined'
2037+ ? window
2038+ : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2039+var _Raven = _window.Raven;
2040+
2041+var Raven = new RavenConstructor();
2042+
2043+/*
2044+ * Allow multiple versions of Raven to be installed.
2045+ * Strip Raven from the global context and returns the instance.
20922046 *
2093− * conditionally exported for test via Raven.utils
2094− =================================================
2047+ * @return {Raven}
20952048 */
2096− var objectPrototype = Object.prototype;
2049+Raven.noConflict = function() {
2050+ _window.Raven = _Raven;
2051+ return Raven;
2052+};
2053+
2054+Raven.afterLoad();
2055+
2056+module.exports = Raven;
2057+
2058+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
2059+},{"3":3}],5:[function(_dereq_,module,exports){
2060+(function (global){
2061+var _window =
2062+ typeof window !== 'undefined'
2063+ ? window
2064+ : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2065+
2066+function isObject(what) {
2067+ return typeof what === 'object' && what !== null;
2068+}
2069+
2070+// Yanked from https://git.io/vS8DV re-used under CC0
2071+// with some tiny modifications
2072+function isError(value) {
2073+ switch ({}.toString.call(value)) {
2074+ case '[object Error]':
2075+ return true;
2076+ case '[object Exception]':
2077+ return true;
2078+ case '[object DOMException]':
2079+ return true;
2080+ default:
2081+ return value instanceof Error;
2082+ }
2083+}
20972084
2098− function isUndefined(what) {
2099− return what === void 0;
2100− }
2085+function isErrorEvent(value) {
2086+ return supportsErrorEvent() && {}.toString.call(value) === '[object ErrorEvent]';
2087+}
21012088
2102− function isFunction(what) {
2103− return typeof what === 'function';
2104− }
2089+function isUndefined(what) {
2090+ return what === void 0;
2091+}
21052092
2106− function isString(what) {
2107− return objectPrototype.toString.call(what) === '[object String]';
2108− }
2093+function isFunction(what) {
2094+ return typeof what === 'function';
2095+}
21092096
2110− function isEmptyObject(what) {
2111− for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars
2112− return true;
2113− }
2097+function isString(what) {
2098+ return Object.prototype.toString.call(what) === '[object String]';
2099+}
21142100
2115− function each(obj, callback) {
2116− var i, j;
2101+function isArray(what) {
2102+ return Object.prototype.toString.call(what) === '[object Array]';
2103+}
21172104
2118− if (isUndefined(obj.length)) {
2119− for (i in obj) {
2120− if (hasKey(obj, i)) {
2121− callback.call(null, i, obj[i]);
2122− }
2123− }
2124− } else {
2125− j = obj.length;
2126− if (j) {
2127− for (i = 0; i < j; i++) {
2128− callback.call(null, i, obj[i]);
2129− }
2130− }
2131− }
2132− }
2105+function isEmptyObject(what) {
2106+ for (var _ in what) {
2107+ if (what.hasOwnProperty(_)) {
2108+ return false;
2109+ }
2110+ }
2111+ return true;
2112+}
2113+
2114+function supportsErrorEvent() {
2115+ try {
2116+ new ErrorEvent(''); // eslint-disable-line no-new
2117+ return true;
2118+ } catch (e) {
2119+ return false;
2120+ }
2121+}
21332122
2134− function objectMerge(obj1, obj2) {
2135− if (!obj2) {
2136− return obj1;
2137− }
2138− each(obj2, function(key, value) {
2139− obj1[key] = value;
2140− });
2141− return obj1;
2142− }
2123+function wrappedCallback(callback) {
2124+ function dataCallback(data, original) {
2125+ var normalizedData = callback(data) || data;
2126+ if (original) {
2127+ return original(normalizedData) || normalizedData;
2128+ }
2129+ return normalizedData;
2130+ }
2131+
2132+ return dataCallback;
2133+}
2134+
2135+function each(obj, callback) {
2136+ var i, j;
2137+
2138+ if (isUndefined(obj.length)) {
2139+ for (i in obj) {
2140+ if (hasKey(obj, i)) {
2141+ callback.call(null, i, obj[i]);
2142+ }
2143+ }
2144+ } else {
2145+ j = obj.length;
2146+ if (j) {
2147+ for (i = 0; i < j; i++) {
2148+ callback.call(null, i, obj[i]);
2149+ }
2150+ }
2151+ }
2152+}
2153+
2154+function objectMerge(obj1, obj2) {
2155+ if (!obj2) {
2156+ return obj1;
2157+ }
2158+ each(obj2, function(key, value) {
2159+ obj1[key] = value;
2160+ });
2161+ return obj1;
2162+}
21432163
2144− /**
2164+/**
21452165 * This function is only used for react-native.
21462166 * react-native freezes object that have already been sent over the
21472167 * js bridge. We need this function in order to check if the object is frozen.
@@ -2149,398 +2169,298 @@ · 1 unmodified line
21492169 * supported because it's not relevant for other "platforms". See related issue:
21502170 * https://github.com/getsentry/react-native-sentry/issues/57
21512171 */
2152− function objectFrozen(obj) {
2153− if (!Object.isFrozen) {
2154− return false;
2155− }
2156− return Object.isFrozen(obj);
2157− }
2172+function objectFrozen(obj) {
2173+ if (!Object.isFrozen) {
2174+ return false;
2175+ }
2176+ return Object.isFrozen(obj);
2177+}
21582178
2159− function truncate(str, max) {
2160− return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';
2161− }
2179+function truncate(str, max) {
2180+ return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';
2181+}
21622182
2163− /**
2183+/**
21642184 * hasKey, a better form of hasOwnProperty
21652185 * Example: hasKey(MainHostObject, property) === true/false
21662186 *
21672187 * @param {Object} host object to check property
21682188 * @param {string} key to check
21692189 */
2170− function hasKey(object, key) {
2171− return objectPrototype.hasOwnProperty.call(object, key);
2172− }
2173−
2174− function joinRegExp(patterns) {
2175− // Combine an array of regular expressions and strings into one large regexp
2176− // Be mad.
2177− var sources = [],
2178− i = 0,
2179− len = patterns.length,
2180− pattern;
2181−
2182− for (; i < len; i++) {
2183− pattern = patterns[i];
2184− if (isString(pattern)) {
2185− // If it's a string, we need to escape it
2186− // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
2187− sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
2188− } else if (pattern && pattern.source) {
2189− // If it's a regexp already, we want to extract the source
2190− sources.push(pattern.source);
2191− }
2192− // Intentionally skip other cases
2193− }
2194− return new RegExp(sources.join('|'), 'i');
2195− }
2196−
2197− function urlencode(o) {
2198− var pairs = [];
2199− each(o, function(key, value) {
2200− pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
2201− });
2202− return pairs.join('&');
2203− }
2204−
2205− // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B
2206− // intentionally using regex and not <a/> href parsing trick because React Native and other
2207− // environments where DOM might not be available
2208− function parseUrl(url) {
2209− var match = url.match(
2210− /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
2211− );
2212− if (!match) return {};
2213−
2214− // coerce to undefined values to empty string so we don't get 'undefined'
2215− var query = match[6] || '';
2216− var fragment = match[8] || '';
2217− return {
2218− protocol: match[2],
2219− host: match[4],
2220− path: match[5],
2221− relative: match[5] + query + fragment // everything minus origin
2222− };
2223− }
2224− function uuid4() {
2225− var crypto = _window.crypto || _window.msCrypto;
2226−
2227− if (!isUndefined(crypto) && crypto.getRandomValues) {
2228− // Use window.crypto API if available
2229− // eslint-disable-next-line no-undef
2230− var arr = new Uint16Array(8);
2231− crypto.getRandomValues(arr);
2232−
2233− // set 4 in byte 7
2234− arr[3] = (arr[3] & 0xfff) | 0x4000;
2235− // set 2 most significant bits of byte 9 to '10'
2236− arr[4] = (arr[4] & 0x3fff) | 0x8000;
2237−
2238− var pad = function(num) {
2239− var v = num.toString(16);
2240− while (v.length < 4) {
2241− v = '0' + v;
2242− }
2243− return v;
2244− };
2245−
2246− return (
2247− pad(arr[0]) +
2248− pad(arr[1]) +
2249− pad(arr[2]) +
2250− pad(arr[3]) +
2251− pad(arr[4]) +
2252− pad(arr[5]) +
2253− pad(arr[6]) +
2254− pad(arr[7])
2255− );
2256− } else {
2257− // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
2258− return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
2259− var r = (Math.random() * 16) | 0,
2260− v = c === 'x' ? r : (r & 0x3) | 0x8;
2261− return v.toString(16);
2262− });
2263− }
2264− }
2190+function hasKey(object, key) {
2191+ return Object.prototype.hasOwnProperty.call(object, key);
2192+}
2193+
2194+function joinRegExp(patterns) {
2195+ // Combine an array of regular expressions and strings into one large regexp
2196+ // Be mad.
2197+ var sources = [],
2198+ i = 0,
2199+ len = patterns.length,
2200+ pattern;
2201+
2202+ for (; i < len; i++) {
2203+ pattern = patterns[i];
2204+ if (isString(pattern)) {
2205+ // If it's a string, we need to escape it
2206+ // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
2207+ sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
2208+ } else if (pattern && pattern.source) {
2209+ // If it's a regexp already, we want to extract the source
2210+ sources.push(pattern.source);
2211+ }
2212+ // Intentionally skip other cases
2213+ }
2214+ return new RegExp(sources.join('|'), 'i');
2215+}
2216+
2217+function urlencode(o) {
2218+ var pairs = [];
2219+ each(o, function(key, value) {
2220+ pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
2221+ });
2222+ return pairs.join('&');
2223+}
2224+
2225+// borrowed from https://tools.ietf.org/html/rfc3986#appendix-B
2226+// intentionally using regex and not <a/> href parsing trick because React Native and other
2227+// environments where DOM might not be available
2228+function parseUrl(url) {
2229+ var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);
2230+ if (!match) return {};
2231+
2232+ // coerce to undefined values to empty string so we don't get 'undefined'
2233+ var query = match[6] || '';
2234+ var fragment = match[8] || '';
2235+ return {
2236+ protocol: match[2],
2237+ host: match[4],
2238+ path: match[5],
2239+ relative: match[5] + query + fragment // everything minus origin
2240+ };
2241+}
2242+function uuid4() {
2243+ var crypto = _window.crypto || _window.msCrypto;
2244+
2245+ if (!isUndefined(crypto) && crypto.getRandomValues) {
2246+ // Use window.crypto API if available
2247+ // eslint-disable-next-line no-undef
2248+ var arr = new Uint16Array(8);
2249+ crypto.getRandomValues(arr);
2250+
2251+ // set 4 in byte 7
2252+ arr[3] = (arr[3] & 0xfff) | 0x4000;
2253+ // set 2 most significant bits of byte 9 to '10'
2254+ arr[4] = (arr[4] & 0x3fff) | 0x8000;
2255+
2256+ var pad = function(num) {
2257+ var v = num.toString(16);
2258+ while (v.length < 4) {
2259+ v = '0' + v;
2260+ }
2261+ return v;
2262+ };
2263+
2264+ return (
2265+ pad(arr[0]) +
2266+ pad(arr[1]) +
2267+ pad(arr[2]) +
2268+ pad(arr[3]) +
2269+ pad(arr[4]) +
2270+ pad(arr[5]) +
2271+ pad(arr[6]) +
2272+ pad(arr[7])
2273+ );
2274+ } else {
2275+ // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
2276+ return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
2277+ var r = (Math.random() * 16) | 0,
2278+ v = c === 'x' ? r : (r & 0x3) | 0x8;
2279+ return v.toString(16);
2280+ });
2281+ }
2282+}
22652283
2266− /**
2284+/**
22672285 * Given a child DOM element, returns a query-selector statement describing that
22682286 * and its ancestors
22692287 * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
22702288 * @param elem
22712289 * @returns {string}
22722290 */
2273− function htmlTreeAsString(elem) {
2274− /* eslint no-extra-parens:0*/
2275− var MAX_TRAVERSE_HEIGHT = 5,
2276− MAX_OUTPUT_LEN = 80,
2277− out = [],
2278− height = 0,
2279− len = 0,
2280− separator = ' > ',
2281− sepLength = separator.length,
2282− nextStr;
2283−
2284− while (elem && height++ < MAX_TRAVERSE_HEIGHT) {
2285− nextStr = htmlElementAsString(elem);
2286− // bail out if
2287− // - nextStr is the 'html' element
2288− // - the length of the string that would be created exceeds MAX_OUTPUT_LEN
2289− // (ignore this limit if we are on the first iteration)
2290− if (
2291− nextStr === 'html' ||
2292− (height > 1 &&
2293− len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN)
2294− ) {
2295− break;
2296− }
2291+function htmlTreeAsString(elem) {
2292+ /* eslint no-extra-parens:0*/
2293+ var MAX_TRAVERSE_HEIGHT = 5,
2294+ MAX_OUTPUT_LEN = 80,
2295+ out = [],
2296+ height = 0,
2297+ len = 0,
2298+ separator = ' > ',
2299+ sepLength = separator.length,
2300+ nextStr;
2301+
2302+ while (elem && height++ < MAX_TRAVERSE_HEIGHT) {
2303+ nextStr = htmlElementAsString(elem);
2304+ // bail out if
2305+ // - nextStr is the 'html' element
2306+ // - the length of the string that would be created exceeds MAX_OUTPUT_LEN
2307+ // (ignore this limit if we are on the first iteration)
2308+ if (
2309+ nextStr === 'html' ||
2310+ (height > 1 && len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN)
2311+ ) {
2312+ break;
2313+ }
22972314
2298− out.push(nextStr);
2315+ out.push(nextStr);
22992316
2300− len += nextStr.length;
2301− elem = elem.parentNode;
2302− }
2317+ len += nextStr.length;
2318+ elem = elem.parentNode;
2319+ }
23032320
2304− return out.reverse().join(separator);
2305− }
2321+ return out.reverse().join(separator);
2322+}
23062323
2307− /**
2324+/**
23082325 * Returns a simple, query-selector representation of a DOM element
23092326 * e.g. [HTMLElement] => input#foo.btn[name=baz]
23102327 * @param HTMLElement
23112328 * @returns {string}
23122329 */
2313− function htmlElementAsString(elem) {
2314− var out = [],
2315− className,
2316− classes,
2317− key,
2318− attr,
2319− i;
2320−
2321− if (!elem || !elem.tagName) {
2322− return '';
2323− }
2330+function htmlElementAsString(elem) {
2331+ var out = [],
2332+ className,
2333+ classes,
2334+ key,
2335+ attr,
2336+ i;
2337+
2338+ if (!elem || !elem.tagName) {
2339+ return '';
2340+ }
23242341
2325− out.push(elem.tagName.toLowerCase());
2326− if (elem.id) {
2327− out.push('#' + elem.id);
2328− }
2342+ out.push(elem.tagName.toLowerCase());
2343+ if (elem.id) {
2344+ out.push('#' + elem.id);
2345+ }
23292346
2330− className = elem.className;
2331− if (className && isString(className)) {
2332− classes = className.split(/\s+/);
2333− for (i = 0; i < classes.length; i++) {
2334− out.push('.' + classes[i]);
2335− }
2336− }
2337− var attrWhitelist = ['type', 'name', 'title', 'alt'];
2338− for (i = 0; i < attrWhitelist.length; i++) {
2339− key = attrWhitelist[i];
2340− attr = elem.getAttribute(key);
2341− if (attr) {
2342− out.push('[' + key + '="' + attr + '"]');
2343− }
2344− }
2345− return out.join('');
2346− }
2347+ className = elem.className;
2348+ if (className && isString(className)) {
2349+ classes = className.split(/\s+/);
2350+ for (i = 0; i < classes.length; i++) {
2351+ out.push('.' + classes[i]);
2352+ }
2353+ }
2354+ var attrWhitelist = ['type', 'name', 'title', 'alt'];
2355+ for (i = 0; i < attrWhitelist.length; i++) {
2356+ key = attrWhitelist[i];
2357+ attr = elem.getAttribute(key);
2358+ if (attr) {
2359+ out.push('[' + key + '="' + attr + '"]');
2360+ }
2361+ }
2362+ return out.join('');
2363+}
23472364
2348− /**
2365+/**
23492366 * Returns true if either a OR b is truthy, but not both
23502367 */
2351− function isOnlyOneTruthy(a, b) {
2352− return !!(!!a ^ !!b);
2353− }
2368+function isOnlyOneTruthy(a, b) {
2369+ return !!(!!a ^ !!b);
2370+}
23542371
2355− /**
2372+/**
23562373 * Returns true if the two input exception interfaces have the same content
23572374 */
2358− function isSameException(ex1, ex2) {
2359− if (isOnlyOneTruthy(ex1, ex2)) return false;
2375+function isSameException(ex1, ex2) {
2376+ if (isOnlyOneTruthy(ex1, ex2)) return false;
23602377
2361− ex1 = ex1.values[0];
2362− ex2 = ex2.values[0];
2378+ ex1 = ex1.values[0];
2379+ ex2 = ex2.values[0];
23632380
2364− if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false;
2381+ if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false;
23652382
2366− return isSameStacktrace(ex1.stacktrace, ex2.stacktrace);
2367− }
2383+ return isSameStacktrace(ex1.stacktrace, ex2.stacktrace);
2384+}
23682385
2369− /**
2386+/**
23702387 * Returns true if the two input stack trace interfaces have the same content
23712388 */
2372− function isSameStacktrace(stack1, stack2) {
2373− if (isOnlyOneTruthy(stack1, stack2)) return false;
2374−
2375− var frames1 = stack1.frames;
2376− var frames2 = stack2.frames;
2377−
2378− // Exit early if frame count differs
2379− if (frames1.length !== frames2.length) return false;
2380−
2381− // Iterate through every frame; bail out if anything differs
2382− var a, b;
2383− for (var i = 0; i < frames1.length; i++) {
2384− a = frames1[i];
2385− b = frames2[i];
2386− if (
2387− a.filename !== b.filename ||
2388− a.lineno !== b.lineno ||
2389− a.colno !== b.colno ||
2390− a['function'] !== b['function']
2391− )
2392− return false;
2393− }
2394− return true;
2395− }
2389+function isSameStacktrace(stack1, stack2) {
2390+ if (isOnlyOneTruthy(stack1, stack2)) return false;
2391+
2392+ var frames1 = stack1.frames;
2393+ var frames2 = stack2.frames;
2394+
2395+ // Exit early if frame count differs
2396+ if (frames1.length !== frames2.length) return false;
2397+
2398+ // Iterate through every frame; bail out if anything differs
2399+ var a, b;
2400+ for (var i = 0; i < frames1.length; i++) {
2401+ a = frames1[i];
2402+ b = frames2[i];
2403+ if (
2404+ a.filename !== b.filename ||
2405+ a.lineno !== b.lineno ||
2406+ a.colno !== b.colno ||
2407+ a['function'] !== b['function']
2408+ )
2409+ return false;
2410+ }
2411+ return true;
2412+}
23962413
2397− /**
2414+/**
23982415 * Polyfill a method
23992416 * @param obj object e.g. `document`
24002417 * @param name method name present on object e.g. `addEventListener`
24012418 * @param replacement replacement function
24022419 * @param track {optional} record instrumentation to an array
24032420 */
2404− function fill(obj, name, replacement, track) {
2405− var orig = obj[name];
2406− obj[name] = replacement(orig);
2407− if (track) {
2408− track.push([obj, name, orig]);
2409− }
2410− }
2411−
2412− if (typeof __DEV__ !== 'undefined' && __DEV__) {
2413− Raven.utils = {
2414− isUndefined: isUndefined,
2415− isFunction: isFunction,
2416− isString: isString,
2417− isObject: isObject,
2418− isEmptyObject: isEmptyObject,
2419− isError: isError,
2420− each: each,
2421− objectMerge: objectMerge,
2422− truncate: truncate,
2423− hasKey: hasKey,
2424− joinRegExp: joinRegExp,
2425− urlencode: urlencode,
2426− uuid4: uuid4,
2427− htmlTreeAsString: htmlTreeAsString,
2428− htmlElementAsString: htmlElementAsString,
2429− parseUrl: parseUrl,
2430− fill: fill
2431− };
2432− }
2433−
2434− // Deprecations
2435− Raven.prototype.setUser = Raven.prototype.setUserContext;
2436− Raven.prototype.setReleaseContext = Raven.prototype.setRelease;
2437−
2438− module.exports = Raven;
2439− }.call(
2440− this,
2441− typeof global !== 'undefined'
2442− ? global
2443− : typeof self !== 'undefined'
2444− ? self
2445− : typeof window !== 'undefined' ? window : {}
2446− ));
2447− },
2448− {'1': 1, '2': 2, '5': 5, '6': 6, '7': 7}
2449− ],
2450− 4: [
2451− function(_dereq_, module, exports) {
2452− (function(global) {
2453− /**
2454− * Enforces a single instance of the Raven client, and the
2455− * main entry point for Raven. If you are a consumer of the
2456− * Raven library, you SHOULD load this file (vs raven.js).
2457− **/
2458−
2459− var RavenConstructor = _dereq_(3);
2460−
2461− // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
2462− var _window =
2463− typeof window !== 'undefined'
2464− ? window
2465− : typeof global !== 'undefined'
2466− ? global
2467− : typeof self !== 'undefined' ? self : {};
2468− var _Raven = _window.Raven;
2469−
2470− var Raven = new RavenConstructor();
2471−
2472− /*
2473− * Allow multiple versions of Raven to be installed.
2474− * Strip Raven from the global context and returns the instance.
2475− *
2476− * @return {Raven}
2477− */
2478− Raven.noConflict = function() {
2479− _window.Raven = _Raven;
2480− return Raven;
2481− };
2482−
2483− Raven.afterLoad();
2484−
2485− module.exports = Raven;
2486− }.call(
2487− this,
2488− typeof global !== 'undefined'
2489− ? global
2490− : typeof self !== 'undefined'
2491− ? self
2492− : typeof window !== 'undefined' ? window : {}
2493− ));
2494− },
2495− {'3': 3}
2496− ],
2497− 5: [
2498− function(_dereq_, module, exports) {
2499− function isObject(what) {
2500− return typeof what === 'object' && what !== null;
2501− }
2502−
2503− // Yanked from https://git.io/vS8DV re-used under CC0
2504− // with some tiny modifications
2505− function isError(value) {
2506− switch ({}.toString.call(value)) {
2507− case '[object Error]':
2508− return true;
2509− case '[object Exception]':
2510− return true;
2511− case '[object DOMException]':
2512− return true;
2513− default:
2514− return value instanceof Error;
2515− }
2516− }
2517−
2518− function wrappedCallback(callback) {
2519− function dataCallback(data, original) {
2520− var normalizedData = callback(data) || data;
2521− if (original) {
2522− return original(normalizedData) || normalizedData;
2523− }
2524− return normalizedData;
2525− }
2526−
2527− return dataCallback;
2528− }
2421+function fill(obj, name, replacement, track) {
2422+ var orig = obj[name];
2423+ obj[name] = replacement(orig);
2424+ obj[name].__raven__ = true;
2425+ obj[name].__orig__ = orig;
2426+ if (track) {
2427+ track.push([obj, name, orig]);
2428+ }
2429+}
2430+
2431+module.exports = {
2432+ isObject: isObject,
2433+ isError: isError,
2434+ isErrorEvent: isErrorEvent,
2435+ isUndefined: isUndefined,
2436+ isFunction: isFunction,
2437+ isString: isString,
2438+ isArray: isArray,
2439+ isEmptyObject: isEmptyObject,
2440+ supportsErrorEvent: supportsErrorEvent,
2441+ wrappedCallback: wrappedCallback,
2442+ each: each,
2443+ objectMerge: objectMerge,
2444+ truncate: truncate,
2445+ objectFrozen: objectFrozen,
2446+ hasKey: hasKey,
2447+ joinRegExp: joinRegExp,
2448+ urlencode: urlencode,
2449+ uuid4: uuid4,
2450+ htmlTreeAsString: htmlTreeAsString,
2451+ htmlElementAsString: htmlElementAsString,
2452+ isSameException: isSameException,
2453+ isSameStacktrace: isSameStacktrace,
2454+ parseUrl: parseUrl,
2455+ fill: fill
2456+};
2457+
2458+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
2459+},{}],6:[function(_dereq_,module,exports){
2460+(function (global){
2461+var utils = _dereq_(5);
25292462
2530− module.exports = {
2531− isObject: isObject,
2532− isError: isError,
2533− wrappedCallback: wrappedCallback
2534− };
2535− },
2536− {}
2537− ],
2538− 6: [
2539− function(_dereq_, module, exports) {
2540− (function(global) {
2541− var utils = _dereq_(5);
2542−
2543− /*
2463+/*
25442464 TraceKit - Cross brower stack traces
25452465
25462466 This was originally forked from github.com/occ/TraceKit, but has since been
@@ -2550,33 +2470,31 @@ · 3 unmodified lines
25502470 MIT license
25512471 */
25522472
2553− var TraceKit = {
2554− collectWindowErrors: true,
2555− debug: false
2556− };
2473+var TraceKit = {
2474+ collectWindowErrors: true,
2475+ debug: false
2476+};
25572477
2558− // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
2559− var _window =
2560− typeof window !== 'undefined'
2561− ? window
2562− : typeof global !== 'undefined'
2563− ? global
2564− : typeof self !== 'undefined' ? self : {};
2478+// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
2479+var _window =
2480+ typeof window !== 'undefined'
2481+ ? window
2482+ : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
25652483
2566− // global reference to slice
2567− var _slice = [].slice;
2568− var UNKNOWN_FUNCTION = '?';
2484+// global reference to slice
2485+var _slice = [].slice;
2486+var UNKNOWN_FUNCTION = '?';
25692487
2570− // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
2571− var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/;
2488+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
2489+var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/;
25722490
2573− function getLocationHref() {
2574− if (typeof document === 'undefined' || document.location == null) return '';
2491+function getLocationHref() {
2492+ if (typeof document === 'undefined' || document.location == null) return '';
25752493
2576− return document.location.href;
2577− }
2494+ return document.location.href;
2495+}
25782496
2579− /**
2497+/**
25802498 * TraceKit.report: cross-browser processing of unhandled exceptions
25812499 *
25822500 * Syntax:
@@ -2615,68 +2533,68 @@ · 32 unmodified lines
26152533 * Handlers receive a stackInfo object as described in the
26162534 * TraceKit.computeStackTrace docs.
26172535 */
2618− TraceKit.report = (function reportModuleWrapper() {
2619− var handlers = [],
2620− lastArgs = null,
2621− lastException = null,
2622− lastExceptionStack = null;
2536+TraceKit.report = (function reportModuleWrapper() {
2537+ var handlers = [],
2538+ lastArgs = null,
2539+ lastException = null,
2540+ lastExceptionStack = null;
26232541
2624− /**
2542+ /**
26252543 * Add a crash handler.
26262544 * @param {Function} handler
26272545 */
2628− function subscribe(handler) {
2629− installGlobalHandler();
2630− handlers.push(handler);
2631− }
2546+ function subscribe(handler) {
2547+ installGlobalHandler();
2548+ handlers.push(handler);
2549+ }
26322550
2633− /**
2551+ /**
26342552 * Remove a crash handler.
26352553 * @param {Function} handler
26362554 */
2637− function unsubscribe(handler) {
2638− for (var i = handlers.length - 1; i >= 0; --i) {
2639− if (handlers[i] === handler) {
2640− handlers.splice(i, 1);
2641− }
2642− }
2643− }
2555+ function unsubscribe(handler) {
2556+ for (var i = handlers.length - 1; i >= 0; --i) {
2557+ if (handlers[i] === handler) {
2558+ handlers.splice(i, 1);
2559+ }
2560+ }
2561+ }
26442562
2645− /**
2563+ /**
26462564 * Remove all crash handlers.
26472565 */
2648− function unsubscribeAll() {
2649− uninstallGlobalHandler();
2650− handlers = [];
2651− }
2566+ function unsubscribeAll() {
2567+ uninstallGlobalHandler();
2568+ handlers = [];
2569+ }
26522570
2653− /**
2571+ /**
26542572 * Dispatch stack information to all handlers.
26552573 * @param {Object.<string, *>} stack
26562574 */
2657− function notifyHandlers(stack, isWindowError) {
2658− var exception = null;
2659− if (isWindowError && !TraceKit.collectWindowErrors) {
2660− return;
2661− }
2662− for (var i in handlers) {
2663− if (handlers.hasOwnProperty(i)) {
2664− try {
2665− handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
2666− } catch (inner) {
2667− exception = inner;
2668− }
2669− }
2670− }
2575+ function notifyHandlers(stack, isWindowError) {
2576+ var exception = null;
2577+ if (isWindowError && !TraceKit.collectWindowErrors) {
2578+ return;
2579+ }
2580+ for (var i in handlers) {
2581+ if (handlers.hasOwnProperty(i)) {
2582+ try {
2583+ handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
2584+ } catch (inner) {
2585+ exception = inner;
2586+ }
2587+ }
2588+ }
26712589
2672− if (exception) {
2673− throw exception;
2674− }
2675− }
2590+ if (exception) {
2591+ throw exception;
2592+ }
2593+ }
26762594
2677− var _oldOnerrorHandler, _onErrorHandlerInstalled;
2595+ var _oldOnerrorHandler, _onErrorHandlerInstalled;
26782596
2679− /**
2597+ /**
26802598 * Ensures all global unhandled exceptions are recorded.
26812599 * Supported by Gecko and IE.
26822600 * @param {string} message Error message.
@@ -2687,135 +2605,132 @@ · 4 unmodified lines
26872605 * occurred.
26882606 * @param {?Error} ex The actual Error object.
26892607 */
2690− function traceKitWindowOnError(message, url, lineNo, colNo, ex) {
2691− var stack = null;
2692−
2693− if (lastExceptionStack) {
2694− TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(
2695− lastExceptionStack,
2696− url,
2697− lineNo,
2698− message
2699− );
2700− processLastException();
2701− } else if (ex && utils.isError(ex)) {
2702− // non-string `ex` arg; attempt to extract stack trace
2703−
2704− // New chrome and blink send along a real error object
2705− // Let's just report that like a normal error.
2706− // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
2707− stack = TraceKit.computeStackTrace(ex);
2708− notifyHandlers(stack, true);
2709− } else {
2710− var location = {
2711− url: url,
2712− line: lineNo,
2713− column: colNo
2714− };
2715−
2716− var name = undefined;
2717− var msg = message; // must be new var or will modify original `arguments`
2718− var groups;
2719− if ({}.toString.call(message) === '[object String]') {
2720− var groups = message.match(ERROR_TYPES_RE);
2721− if (groups) {
2722− name = groups[1];
2723− msg = groups[2];
2724− }
2725− }
2608+ function traceKitWindowOnError(message, url, lineNo, colNo, ex) {
2609+ var stack = null;
2610+
2611+ if (lastExceptionStack) {
2612+ TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(
2613+ lastExceptionStack,
2614+ url,
2615+ lineNo,
2616+ message
2617+ );
2618+ processLastException();
2619+ } else if (ex && utils.isError(ex)) {
2620+ // non-string `ex` arg; attempt to extract stack trace
2621+
2622+ // New chrome and blink send along a real error object
2623+ // Let's just report that like a normal error.
2624+ // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
2625+ stack = TraceKit.computeStackTrace(ex);
2626+ notifyHandlers(stack, true);
2627+ } else {
2628+ var location = {
2629+ url: url,
2630+ line: lineNo,
2631+ column: colNo
2632+ };
2633+
2634+ var name = undefined;
2635+ var msg = message; // must be new var or will modify original `arguments`
2636+ var groups;
2637+ if ({}.toString.call(message) === '[object String]') {
2638+ var groups = message.match(ERROR_TYPES_RE);
2639+ if (groups) {
2640+ name = groups[1];
2641+ msg = groups[2];
2642+ }
2643+ }
27262644
2727− location.func = UNKNOWN_FUNCTION;
2645+ location.func = UNKNOWN_FUNCTION;
27282646
2729− stack = {
2730− name: name,
2731− message: msg,
2732− url: getLocationHref(),
2733− stack: [location]
2734− };
2735− notifyHandlers(stack, true);
2736− }
2647+ stack = {
2648+ name: name,
2649+ message: msg,
2650+ url: getLocationHref(),
2651+ stack: [location]
2652+ };
2653+ notifyHandlers(stack, true);
2654+ }
27372655
2738− if (_oldOnerrorHandler) {
2739− return _oldOnerrorHandler.apply(this, arguments);
2740− }
2656+ if (_oldOnerrorHandler) {
2657+ return _oldOnerrorHandler.apply(this, arguments);
2658+ }
27412659
2742− return false;
2743− }
2660+ return false;
2661+ }
27442662
2745− function installGlobalHandler() {
2746− if (_onErrorHandlerInstalled) {
2747− return;
2748− }
2749− _oldOnerrorHandler = _window.onerror;
2750− _window.onerror = traceKitWindowOnError;
2751− _onErrorHandlerInstalled = true;
2752− }
2663+ function installGlobalHandler() {
2664+ if (_onErrorHandlerInstalled) {
2665+ return;
2666+ }
2667+ _oldOnerrorHandler = _window.onerror;
2668+ _window.onerror = traceKitWindowOnError;
2669+ _onErrorHandlerInstalled = true;
2670+ }
27532671
2754− function uninstallGlobalHandler() {
2755− if (!_onErrorHandlerInstalled) {
2756− return;
2757− }
2758− _window.onerror = _oldOnerrorHandler;
2759− _onErrorHandlerInstalled = false;
2760− _oldOnerrorHandler = undefined;
2761− }
2672+ function uninstallGlobalHandler() {
2673+ if (!_onErrorHandlerInstalled) {
2674+ return;
2675+ }
2676+ _window.onerror = _oldOnerrorHandler;
2677+ _onErrorHandlerInstalled = false;
2678+ _oldOnerrorHandler = undefined;
2679+ }
27622680
2763− function processLastException() {
2764− var _lastExceptionStack = lastExceptionStack,
2765− _lastArgs = lastArgs;
2766− lastArgs = null;
2767− lastExceptionStack = null;
2768− lastException = null;
2769− notifyHandlers.apply(
2770− null,
2771− [_lastExceptionStack, false].concat(_lastArgs)
2772− );
2773− }
2681+ function processLastException() {
2682+ var _lastExceptionStack = lastExceptionStack,
2683+ _lastArgs = lastArgs;
2684+ lastArgs = null;
2685+ lastExceptionStack = null;
2686+ lastException = null;
2687+ notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs));
2688+ }
27742689
2775− /**
2690+ /**
27762691 * Reports an unhandled Error to TraceKit.
27772692 * @param {Error} ex
27782693 * @param {?boolean} rethrow If false, do not re-throw the exception.
27792694 * Only used for window.onerror to not cause an infinite loop of
27802695 * rethrowing.
27812696 */
2782− function report(ex, rethrow) {
2783− var args = _slice.call(arguments, 1);
2784− if (lastExceptionStack) {
2785− if (lastException === ex) {
2786− return; // already caught by an inner catch block, ignore
2787− } else {
2788− processLastException();
2789− }
2790− }
2697+ function report(ex, rethrow) {
2698+ var args = _slice.call(arguments, 1);
2699+ if (lastExceptionStack) {
2700+ if (lastException === ex) {
2701+ return; // already caught by an inner catch block, ignore
2702+ } else {
2703+ processLastException();
2704+ }
2705+ }
27912706
2792− var stack = TraceKit.computeStackTrace(ex);
2793− lastExceptionStack = stack;
2794− lastException = ex;
2795− lastArgs = args;
2796−
2797− // If the stack trace is incomplete, wait for 2 seconds for
2798− // slow slow IE to see if onerror occurs or not before reporting
2799− // this exception; otherwise, we will end up with an incomplete
2800− // stack trace
2801− setTimeout(function() {
2802− if (lastException === ex) {
2803− processLastException();
2804− }
2805− }, stack.incomplete ? 2000 : 0);
2707+ var stack = TraceKit.computeStackTrace(ex);
2708+ lastExceptionStack = stack;
2709+ lastException = ex;
2710+ lastArgs = args;
2711+
2712+ // If the stack trace is incomplete, wait for 2 seconds for
2713+ // slow slow IE to see if onerror occurs or not before reporting
2714+ // this exception; otherwise, we will end up with an incomplete
2715+ // stack trace
2716+ setTimeout(function() {
2717+ if (lastException === ex) {
2718+ processLastException();
2719+ }
2720+ }, stack.incomplete ? 2000 : 0);
28062721
2807− if (rethrow !== false) {
2808− throw ex; // re-throw to propagate to the top level (and cause window.onerror)
2809− }
2810− }
2722+ if (rethrow !== false) {
2723+ throw ex; // re-throw to propagate to the top level (and cause window.onerror)
2724+ }
2725+ }
28112726
2812− report.subscribe = subscribe;
2813− report.unsubscribe = unsubscribe;
2814− report.uninstall = unsubscribeAll;
2815− return report;
2816− })();

724 more lines aren’t shown here.

assets/javascripts/views/content/content.coffee+7−1
@@ -19,7 +19,10 @@ class app.views.Content extends app.View · 18 unmodified lines
1919 after: 'afterRoute'
2020
2121 init: ->
22− @scrollEl = if app.isMobile() then document.body else @el
22+ @scrollEl = if app.isMobile()
23+ (document.scrollingElement || document.body)
24+ else
25+ @el
2326 @scrollMap = {}
2427 @scrollStack = []
2528
@@ -150,6 +153,9 @@ class app.views.Content extends app.View · 124 unmodified lines
150153 return
151154
152155 afterRoute: (route, context) =>
156+ if route != 'entry' and route != 'type'
157+ resetFavicon()
158+
153159 switch route
154160 when 'root'
155161 @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
4040 if app.disabledDocs.findBy 'slug', @entry.doc.slug
4141 @hiddenView = new app.views.HiddenPage @el, @entry
4242
43+ setFaviconForDoc(@entry.doc)
4344 @delay @polyfillMathML
4445 @trigger 'loaded'
4546 return
@@ -123,7 +124,7 @@ class app.views.EntryPage extends app.View · 77 unmodified lines
123124 @render @tmpl('pageLoadError')
124125 @resetClass()
125126 @addClass @constructor.errorClass
126− app.appCache?.update()
127+ app.serviceWorker?.update()
127128 return
128129
129130 cache: ->
assets/javascripts/views/content/offline_page.coffee+1−0
@@ -57,6 +57,7 @@ class app.views.OfflinePage extends app.View · 56 unmodified lines
5757 doc[action](@onInstallSuccess.bind(@, doc), @onInstallError.bind(@, doc), @onInstallProgress.bind(@, doc))
5858 el.parentNode.innerHTML = "#{el.textContent.replace(/e$/, '')}ing…"
5959 else if action = el.getAttribute('data-action-all')
60+ return unless action isnt 'uninstall' or window.confirm('Uninstall all docs?')
6061 app.db.migrate()
6162 $.click(el) for el in @findAll("[data-action='#{action}']")
6263 return
assets/javascripts/views/content/root_page.coffee+10−3
@@ -9,10 +9,17 @@ class app.views.RootPage extends app.View · 8 unmodified lines
99
1010 render: ->
1111 @empty()
12− if app.isAndroidWebview()
13− @append @tmpl('androidWarning')
12+
13+ tmpl = if app.isAndroidWebview()
14+ 'androidWarning'
15+ else if @isHidden()
16+ 'splash'
17+ else if app.isMobile()
18+ 'mobileIntro'
1419 else
15− @append @tmpl if @isHidden() then 'splash' else if app.isMobile() then 'mobileIntro' else 'intro'
20+ 'intro'
21+
22+ @append @tmpl(tmpl)
1623 return
1724
1825 hideIntro: ->
assets/javascripts/views/content/settings_page.coffee+10−12
@@ -1,7 +1,4 @@
11 class app.views.SettingsPage extends app.View
2− LAYOUTS = ['_max-width', '_sidebar-hidden', '_native-scrollbars']
3− SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden'
4−
52 @className: '_static'
63
74 @events:
@@ -17,32 +14,31 @@ class app.views.SettingsPage extends app.View · 9 unmodified lines
1714 settings.dark = app.settings.get('dark')
1815 settings.smoothScroll = !app.settings.get('fastScroll')
1916 settings.arrowScroll = app.settings.get('arrowScroll')
20− settings[layout] = app.settings.hasLayout(layout) for layout in LAYOUTS
17+ 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
2120 settings
2221
2322 getTitle: ->
2423 'Preferences'
2524
2625 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)
3126 app.settings.set('dark', !!enable)
32− app.appCache?.updateInBackground()
3327 return
3428
3529 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')
3830 app.settings.setLayout(layout, enable)
39− app.appCache?.updateInBackground()
4031 return
4132
4233 toggleSmoothScroll: (enable) ->
4334 app.settings.set('fastScroll', !enable)
4435 return
4536
37+ toggleAnalyticsConsent: (enable) ->
38+ app.settings.set('analyticsConsent', if enable then '1' else '0')
39+ resetAnalytics() unless enable
40+ return
41+
4642 toggle: (name, enable) ->
4743 app.settings.set(name, enable)
4844 return
@@ -86,6 +82,8 @@ class app.views.SettingsPage extends app.View · 37 unmodified lines
8682 @toggleSmoothScroll input.checked
8783 when 'import'
8884 @import input.files[0], input
85+ when 'analyticsConsent'
86+ @toggleAnalyticsConsent input.checked
8987 else
9088 @toggle input.name, input.checked
9189 return
assets/javascripts/views/content/type_page.coffee+1−0
@@ -9,6 +9,7 @@ class app.views.TypePage extends app.View · 8 unmodified lines
99
1010 render: (@type) ->
1111 @html @tmpl('typePage', @type)
12+ setFaviconForDoc(@type.doc)
1213 return
1314
1415 getTitle: ->
assets/javascripts/views/layout/document.coffee+8−6
@@ -28,7 +28,7 @@ class app.views.Document extends app.View · 27 unmodified lines
2828 return
2929
3030 setTitle: (title) ->
31− @el.title = if title then "DevDocs — #{title}" else 'DevDocs API Documentation'
31+ @el.title = if title then "#{title} — DevDocs" else 'DevDocs API Documentation'
3232
3333 afterRoute: (route) =>
3434 if route is 'settings'
@@ -75,9 +75,11 @@ class app.views.Document extends app.View · 40 unmodified lines
7575 return unless target.hasAttribute('data-behavior')
7676 $.stopEvent(event)
7777 switch target.getAttribute('data-behavior')
78− when 'back' then history.back()
79− when 'reload' then window.location.reload()
80− when 'reboot' then window.location = '/'
81− when 'hard-reload' then app.reload()
82− when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?')
78+ when 'back' then history.back()
79+ when 'reload' then window.location.reload()
80+ when 'reboot' then app.reboot()
81+ when 'hard-reload' then app.reload()
82+ when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?')
83+ when 'accept-analytics' then Cookies.set('analyticsConsent', '1') && app.reboot()
84+ when 'decline-analytics' then Cookies.set('analyticsConsent', '0') && app.reboot()
8385 return
assets/javascripts/views/layout/mobile.coffee+2−2
@@ -16,8 +16,8 @@ class app.views.Mobile extends app.View · 15 unmodified lines
1616 @detect: ->
1717 try
1818 (window.matchMedia('(max-width: 480px)').matches) or
19− (window.matchMedia('(max-device-width: 767px)').matches) or
20− (window.matchMedia('(max-device-height: 767px) and (max-device-width: 1024px)').matches) or
19+ (window.matchMedia('(max-width: 767px)').matches) or
20+ (window.matchMedia('(max-height: 767px) and (max-width: 1024px)').matches) or
2121 # Need to sniff the user agent because some Android and Windows Phone devices don't take
2222 # resolution (dpi) into account when reporting device width/height.
2323 (navigator.userAgent.indexOf('Android') isnt -1 and navigator.userAgent.indexOf('Mobile') isnt -1) or
assets/javascripts/views/layout/resizer.coffee+3−10
@@ -11,9 +11,6 @@ class app.views.Resizer extends app.View · 10 unmodified lines
1111 init: ->
1212 @el.setAttribute('draggable', 'true')
1313 @appendTo $('._app')
14−
15− @style = $('style[data-resizer]')
16− @size = @style.getAttribute('data-size')
1714 return
1815
1916 MIN = 260
@@ -24,15 +21,11 @@ class app.views.Resizer extends app.View · 4 unmodified lines
2421 return unless value > 0
2522 value = Math.min(Math.max(Math.round(value), MIN), MAX)
2623 newSize = "#{value}px"
27− @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize)
28− @size = newSize
29− if save
30− app.settings.setSize(value)
31− app.appCache?.updateInBackground()
24+ document.documentElement.style.setProperty('--sidebarWidth', newSize)
25+ app.settings.setSize(value) if save
3226 return
3327
3428 onDragStart: (event) =>
35− @style.removeAttribute('disabled')
3629 event.dataTransfer.effectAllowed = 'link'
3730 event.dataTransfer.setData('Text', '')
3831 $.on(window, 'dragover', @onDrag)
@@ -50,7 +43,7 @@ class app.views.Resizer extends app.View · 11 unmodified lines
5043 onDragEnd: (event) =>
5144 $.off(window, 'dragover', @onDrag)
5245 value = event.pageX or (event.screenX - window.screenX)
53− if @lastDragValue and not (@lastDragValue - 5 < value < @lastDragValue + 5) # https://github.com/Thibaut/devdocs/issues/265
46+ if @lastDragValue and not (@lastDragValue - 5 < value < @lastDragValue + 5) # https://github.com/freeCodeCamp/devdocs/issues/265
5447 value = @lastDragValue
5548 @resize(value, true)
5649 return
assets/javascripts/views/layout/settings.coffee+1−9
@@ -25,7 +25,6 @@ class app.views.Settings extends app.View · 24 unmodified lines
2525 if super
2626 @render()
2727 document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT)
28− app.appCache?.on 'progress', @onAppCacheProgress
2928 return
3029
3130 deactivate: ->
@@ -33,7 +32,6 @@ class app.views.Settings extends app.View · 1 unmodified line
3332 @resetClass()
3433 @docPicker.detach()
3534 document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)
36− app.appCache?.off 'progress', @onAppCacheProgress
3735 return
3836
3937 render: ->
@@ -52,7 +50,7 @@ class app.views.Settings extends app.View · 12 unmodified lines
5250 docs = @docPicker.getSelectedDocs()
5351 app.settings.setDocs(docs)
5452
55− @saveBtn.textContent = if app.appCache then 'Downloading\u2026' else 'Saving\u2026'
53+ @saveBtn.textContent = 'Saving\u2026'
5654 disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1)
5755 disabledDocs.uninstall ->
5856 app.db.migrate()
@@ -83,9 +81,3 @@ class app.views.Settings extends app.View · 24 unmodified lines
8381 $.stopEvent(event)
8482 app.router.show '/'
8583 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−1
@@ -17,6 +17,7 @@ class app.views.Search extends app.View · 16 unmodified lines
1717 typing: 'focus'
1818 altG: 'google'
1919 altS: 'stackoverflow'
20+ altD: 'duckduckgo'
2021
2122 @routes:
2223 after: 'afterRoute'
@@ -29,6 +30,9 @@ class app.views.Search extends app.View · 6 unmodified lines
2930 .on 'results', @onResults
3031 .on 'end', @onEnd
3132
33+ @scope
34+ .on 'change', @onScopeChange
35+
3236 app.on 'ready', @onReady
3337 $.on window, 'hashchange', @searchUrl
3438 $.on window, 'focus', @onWindowFocus
@@ -113,6 +117,10 @@ class app.views.Search extends app.View · 78 unmodified lines
113117 @externalSearch "https://stackoverflow.com/search?q="
114118 return
115119
120+ duckduckgo: =>
121+ @externalSearch "https://duckduckgo.com/?t=devdocs&q="
122+ return
123+
116124 onResults: (results) =>
117125 @hasResults = true if results.length
118126 @trigger 'results', results, @flags
@@ -127,13 +135,17 @@ class app.views.Search extends app.View · 8 unmodified lines
127135 if event.target is @resetLink
128136 $.stopEvent(event)
129137 @reset()
130− app.document.onEscape()
131138 return
132139
133140 onSubmit: (event) ->
134141 $.stopEvent(event)
135142 return
136143
144+ onScopeChange: =>
145+ @value = ''
146+ @onInput()
147+ return
148+
137149 afterRoute: (name, context) =>
138150 return if app.shortcuts.eventInProgress?.name is 'escape'
139151 @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
66 tag: '._search-tag'
77
88 @events:
9+ click: 'onClick'
910 keydown: 'onKeydown'
11+ textInput: 'onTextInput'
1012
1113 @routes:
1214 after: 'afterRoute'
@@ -87,17 +89,33 @@ class app.views.SearchScope extends app.View · 74 unmodified lines
8789 @trigger 'change', null, previousDoc
8890 return
8991
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+
90103 onKeydown: (event) =>
91104 if event.which is 8 # backspace
92− if @doc and not @input.value
93− $.stopEvent(event)
105+ if @doc and @input.selectionEnd is 0
94106 @reset()
95− else if not @doc and @input.value
107+ $.stopEvent(event)
108+ else if not @doc and @input.value and not $.isChromeForAndroid()
96109 return if event.ctrlKey or event.metaKey or event.altKey or event.shiftKey
97110 if event.which is 9 or # tab
98111 (event.which is 32 and app.isMobile()) # space
99− @search @input.value[0...@input.selectionStart]
100− $.stopEvent(event) if @doc
112+ @doScopeSearch(event)
113+ return
114+
115+ onTextInput: (event) =>
116+ return unless $.isChromeForAndroid()
117+ if not @doc and @input.value and event.data == ' '
118+ @doScopeSearch(event)
101119 return
102120
103121 extractHashValue: ->
assets/javascripts/views/sidebar/sidebar.coffee+1−1
@@ -28,7 +28,7 @@ class app.views.Sidebar extends app.View · 27 unmodified lines
2828
2929 app.on 'ready', @onReady
3030
31− $.on document.documentElement, 'mouseleave', (event) => @display() if event.clientX < 10
31+ $.on document.documentElement, 'mouseleave', (event) => @display() unless event.clientX <= 0
3232 $.on document.documentElement, 'mouseenter', => @resetDisplay(forceNoHover: false)
3333 return
3434
assets/stylesheets/application-dark.css.scss+0−101
@@ -1,101 +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-2017 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/icons',
16− 'global/classes',
17− 'global/base';
18−
19−@import 'components/app',
20− 'components/header',
21− 'components/notif',
22− 'components/sidebar',
23− 'components/settings',
24− 'components/content',
25− 'components/page',
26− 'components/fail',
27− 'components/path',
28− 'components/notice',
29− 'components/prism',
30− 'components/mobile';
31−
32−@import 'pages/simple',
33− 'pages/angular',
34− 'pages/angularjs',
35− 'pages/apache',
36− 'pages/async',
37− 'pages/bootstrap',
38− 'pages/c',
39− 'pages/cakephp',
40− 'pages/clojure',
41− 'pages/codeception',
42− 'pages/coffeescript',
43− 'pages/cordova',
44− 'pages/crystal',
45− 'pages/d',
46− 'pages/d3',
47− 'pages/dojo',
48− 'pages/drupal',
49− 'pages/elixir',
50− 'pages/ember',
51− 'pages/erlang',
52− 'pages/express',
53− 'pages/git',
54− 'pages/github',
55− 'pages/go',
56− 'pages/haskell',
57− 'pages/jquery',
58− 'pages/julia',
59− 'pages/knockout',
60− 'pages/kotlin',
61− 'pages/laravel',
62− 'pages/liquid',
63− 'pages/love',
64− 'pages/lua',
65− 'pages/mdn',
66− 'pages/meteor',
67− 'pages/modernizr',
68− 'pages/moment',
69− 'pages/nginx',
70− 'pages/node',
71− 'pages/npm',
72− 'pages/openjdk',
73− 'pages/perl',
74− 'pages/phalcon',
75− 'pages/phaser',
76− 'pages/php',
77− 'pages/phpunit',
78− 'pages/postgres',
79− 'pages/pug',
80− 'pages/ramda',
81− 'pages/rdoc',
82− 'pages/react_native',
83− 'pages/redis',
84− 'pages/rethinkdb',
85− 'pages/rfc',
86− 'pages/rubydoc',
87− 'pages/rust',
88− 'pages/scala',
89− 'pages/sinon',
90− 'pages/socketio',
91− 'pages/sphinx',
92− 'pages/sphinx_simple',
93− 'pages/sqlite',
94− 'pages/support_tables',
95− 'pages/tcl_tk',
96− 'pages/tensorflow',
97− 'pages/underscore',
98− 'pages/vue',
99− 'pages/webpack',
100− 'pages/yard',
101− 'pages/yii';
assets/stylesheets/application.css.scss+15−6
@@ -1,17 +1,19 @@
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
1+//= depend_on sprites/docs.png
2+//= depend_on sprites/docs@2x.png
3+//= depend_on sprites/docs.json
54
65 /*!
7− * Copyright 2013-2017 Thibaut Courouble and other contributors
6+ * Copyright 2013-2019 Thibaut Courouble and other contributors
87 *
98 * This source code is licensed under the terms of the Mozilla
109 * Public License, v. 2.0, a copy of which may be obtained at:
1110 * http://mozilla.org/MPL/2.0/
1211 */
1312
14−@import 'global/variables',
13+@import 'global/variables-light',
14+ 'global/variables-dark',
15+ 'global/variables',
16+ 'global/mixins',
1517 'global/icons',
1618 'global/classes',
1719 'global/base';
@@ -34,6 +36,7 @@ · 16 unmodified lines
3436 'pages/angularjs',
3537 'pages/apache',
3638 'pages/async',
39+ 'pages/bash',
3740 'pages/bootstrap',
3841 'pages/c',
3942 'pages/cakephp',
@@ -44,6 +47,7 @@ · 4 unmodified lines
4447 'pages/crystal',
4548 'pages/d',
4649 'pages/d3',
50+ 'pages/dart',
4751 'pages/dojo',
4852 'pages/drupal',
4953 'pages/elixir',
@@ -53,7 +57,9 @@ · 3 unmodified lines
5357 'pages/git',
5458 'pages/github',
5559 'pages/go',
60+ 'pages/graphite',
5661 'pages/haskell',
62+ 'pages/jekyll',
5763 'pages/jquery',
5864 'pages/julia',
5965 'pages/knockout',
@@ -77,6 +83,8 @@ · 17 unmodified lines
7783 'pages/phpunit',
7884 'pages/postgres',
7985 'pages/pug',
86+ 'pages/python',
87+ 'pages/qt',
8088 'pages/ramda',
8189 'pages/rdoc',
8290 'pages/react_native',
@@ -94,6 +102,7 @@ · 11 unmodified lines
94102 'pages/support_tables',
95103 'pages/tcl_tk',
96104 'pages/tensorflow',
105+ 'pages/terraform',
97106 'pages/underscore',
98107 'pages/vue',
99108 'pages/webpack',
assets/stylesheets/components/_app.scss+5−5
@@ -1,6 +1,6 @@
1−html._booting { background: $contentBackground; }
1+html._booting { background: var(--contentBackground); }
22 body._max-width { background: none; }
3−html._booting body._max-width { background: $documentBackground; }
3+html._booting body._max-width { background: var(--documentBackground); }
44
55 ._app {
66 position: relative;
@@ -15,8 +15,8 @@ html._booting body._max-width { background: $documentBackground; } · 8 unmodified lines
1515
1616 ._max-width & {
1717 margin: 0 auto;
18− max-width: $maxWidth;
19− background: $contentBackground;
20− box-shadow: 1px 0 $headerBorder, -1px 0 $headerBorder;
18+ max-width: var(--maxWidth);
19+ background: var(--contentBackground);
20+ box-shadow: 1px 0 var(--headerBorder), -1px 0 var(--headerBorder);
2121 }
2222 }
assets/stylesheets/components/_content.scss+30−32
@@ -4,13 +4,13 @@ · 3 unmodified lines
44
55 ._container {
66 position: relative;
7− z-index: $contentZ;
7+ z-index: var(--contentZ);
88 height: 100%;
9− margin-left: $sidebarWidth;
9+ margin-left: var(--sidebarWidth);
1010 pointer-events: none;
1111 @extend %border-box;
1212
13− @media #{$mediumScreen} { margin-left: $sidebarMediumWidth; }
13+ @include mobile { margin-left: var(--sidebarMediumWidth); }
1414
1515 ._sidebar-hidden & { margin-left: 0; }
1616 body:not(._native-scrollbars) & { -webkit-margin-end: -1px; }
@@ -30,7 +30,7 @@ · 13 unmodified lines
3030 ._sidebar-hidden &:before {
3131 content: '';
3232 display: block;
33− margin-top: $headerHeight;
33+ margin-top: var(--headerHeight);
3434 }
3535
3636 ._overlay-scrollbars & { padding-left: .625rem; }
@@ -54,7 +54,7 @@ · 17 unmodified lines
5454 font-size: 4rem;
5555 font-weight: 300;
5656 letter-spacing: -.125rem;
57− color: $loadingText;
57+ color: var(--loadingText);
5858 text-align: center;
5959 cursor: default;
6060 }
@@ -68,7 +68,7 @@ · 7 unmodified lines
6868 //
6969
7070 ._splash-title {
71− color: $splashText;
71+ color: var(--splashText);
7272 @extend %loading, %user-select-none;
7373 }
7474
@@ -88,7 +88,7 @@ · 13 unmodified lines
8888 min-height: calc(100vh - 2.375rem);
8989
9090 ._sidebar-hidden & {
91− min-height: calc(100vh - 2.375rem - #{$headerHeight});
91+ min-height: calc(100vh - 2.375rem - var(--headerHeight));
9292 }
9393 }
9494
@@ -140,12 +140,12 @@ · 45 unmodified lines
140140
141141 ._error-text {
142142 margin: 0 0 1rem;
143− color: $textColorLight;
143+ color: var(--textColorLight);
144144 }
145145
146146 ._error-links {
147147 font-size: 1rem;
148− font-weight: $boldFontWeight;
148+ font-weight: var(--boldFontWeight);
149149 }
150150
151151 ._error-link { padding: 0 .5rem; }
@@ -167,7 +167,7 @@ · 15 unmodified lines
167167 height: 1px;
168168 margin-top: .25rem;
169169 margin-left: 1rem;
170− background: $boxBorderLight;
170+ background: var(--boxBorderLight);
171171 }
172172 }
173173
@@ -197,7 +197,7 @@ · 23 unmodified lines
197197 ._toc-title {
198198 margin: 0 0 .5rem;
199199 font-size: inherit;
200− font-weight: $boldFontWeight;
200+ font-weight: var(--boldFontWeight);
201201 }
202202
203203 ._toc-list {
@@ -252,7 +252,7 @@ · 48 unmodified lines
252252 ._docs-size {
253253 text-align: right;
254254
255− > small { color: $textColorLight; }
255+ > small { color: var(--textColorLight); }
256256 }
257257
258258 ._docs-tools {
@@ -285,7 +285,7 @@ · 26 unmodified lines
285285 padding: 0 .75rem;
286286 }
287287 ._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); }
289289 }
290290
291291 //
@@ -297,7 +297,7 @@ · 5 unmodified lines
297297 position: relative;
298298 padding-left: 10em;
299299 font-size: .8125rem;
300− color: $textColorLight;
300+ color: var(--textColorLight);
301301
302302 + ._news-row { margin-top: 1em; }
303303 }
@@ -305,7 +305,7 @@ · 1 unmodified line
305305 ._news-title {
306306 display: block;
307307 font-size: .875rem;
308− color: $textColor;
308+ color: var(--textColor);
309309 }
310310
311311 ._news-date {
@@ -372,16 +372,18 @@ · 60 unmodified lines
372372 // Utilities
373373 //
374374
375−._bold { font-weight: $boldFontWeight; }
375+._bold { font-weight: var(--boldFontWeight); }
376376 ._note { @extend %note; }
377377 ._note-green { @extend %note-green; }
378378 ._label { @extend %label; }
379379 ._code { @extend %code; }
380−._highlight { background: $highlightBackground !important; }
380+._highlight { background: var(--highlightBackground) !important; }
381381
382382 ._table { width: 100%; }
383383 ._mobile ._table { overflow-x: auto; }
384384
385+._pre-heading { @extend %pre-heading; }
386+
385387 ._pre-clip {
386388 display: none;
387389 position: absolute;
@@ -394,12 +396,9 @@ · 6 unmodified lines
394396 pre:hover > & { display: block; }
395397 &:hover { opacity: 1; }
396398
397− > svg { @extend %svg-icon; }
398−
399− @if $style == 'dark' {
400− > svg { fill: white; }
401− } @else {
402− > svg { fill: black; }
399+ > svg {
400+ @extend %svg-icon;
401+ fill: var(--absolute);
403402 }
404403
405404 &._pre-clip-success > svg,
@@ -417,15 +416,13 @@ · 11 unmodified lines
417416 line-height: normal;
418417 white-space: nowrap;
419418 padding: .375rem .675rem;
420− background-image: linear-gradient(lighten($boxBackground, 4%), darken($boxBackground, 2%));
421− border: 1px solid $boxBorder;
419+ background-color: var(--boxBackground);
420+ border: 1px solid var(--boxBorder);
422421 border-radius: 3px;
423− box-shadow: 0 1px rgba($boxBorder, .08);
424422 cursor: pointer;
425423
426424 &:active {
427− background-color: $boxBackground;
428− box-shadow: inset 0 1px 1px rgba(black, .05), inset 0 1px 4px $boxBorder;
425+ box-shadow: inset 0 1px 1px rgba(black, .05), inset 0 1px 4px var(--boxBorder);
429426 }
430427 }
431428
@@ -445,22 +442,23 @@ · 13 unmodified lines
445442
446443 ._btn-link {
447444 line-height: inherit;
448− color: $linkColor;
449− text-decoration: $linkTextDecoration;
445+ color: var(--linkColor);
446+ text-decoration: var(--linkTextDecoration);
450447
451448 &:hover {
452− color: $linkColorHover;
449+ color: var(--linkColorHover);
453450 text-decoration: underline;
454451 }
455452 }
456453
457454 ._reset-btn,
458455 ._reset-btn:hover {
459− color: $textColorRed;
456+ color: var(--textColorRed);
460457 }
461458
462459 ._github-btn {
463460 display: inline-block;
464461 vertical-align: text-top;
465462 margin-left: .25rem;
463+ background: inherit;
466464 }
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.
22 display: block;
33 position: relative;
44 top: 1.5rem;
@@ -23,7 +23,7 @@ · 18 unmodified lines
2323 ._fail-title {
2424 margin: 0 0 1rem;
2525 font-size: 1rem;
26− font-weight: $boldFontWeight;
26+ font-weight: bold;
2727 }
2828
2929 ._fail-text, ._fail-list {
@@ -32,5 +32,3 @@ · 2 unmodified lines
3232 }
3333
3434 ._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
44
55 ._header {
66 position: absolute;
7− z-index: $headerZ;
7+ z-index: var(--headerZ);
88 top: 0;
99 left: 0;
1010 display: -ms-flexbox;
1111 display: flex;
12− width: $sidebarWidth;
13− height: $headerHeight;
14− background: $headerBackground;
15− border-bottom: 1px solid $headerBorder;
16− border-right: 1px solid $headerBorder;
12+ width: var(--sidebarWidth);
13+ height: var(--headerHeight);
14+ background: var(--headerBackground);
15+ border-bottom: 1px solid var(--headerBorder);
16+ border-right: 1px solid var(--headerBorder);
1717 @extend %border-box;
1818 @extend %user-select-none;
1919
20− @media #{$mediumScreen} { width: $sidebarMediumWidth; }
20+ @include mobile { width: var(--sidebarMediumWidth); }
2121 }
2222
2323 ._header-left {
@@ -35,7 +35,7 @@ · 11 unmodified lines
3535 flex: 0 0 auto;
3636 width: 2.25rem;
3737 height: 100%;
38− color: $textColorLight;
38+ color: var(--textColorLight);
3939 text-align: center;
4040
4141 &[hidden] { display: none; }
@@ -67,8 +67,8 @@ · 25 unmodified lines
6767 word-wrap: normal;
6868 overflow-wrap: normal;
6969 font-size: .875rem;
70− background: $contentBackground;
71− border: 1px solid $headerBorder;
70+ background: var(--contentBackground);
71+ border: 1px solid var(--headerBorder);
7272 border-radius: 3px;
7373 box-shadow: -1px 1px 1px rgba(black, .05);
7474 transition: all 0ms cubic-bezier(0.23, 1, 0.32, 1) 1ms;
@@ -98,10 +98,10 @@ · 23 unmodified lines
9898 margin: 0;
9999 line-height: 1.5rem;
100100 font-size: 1rem;
101− font-weight: $boldFontWeight;
101+ font-weight: var(--boldFontWeight);
102102 letter-spacing: -.5px;
103− background: $sidebarBackground;
104− border-bottom: 1px solid $sidebarBorder;
103+ background: var(--sidebarBackground);
104+ border-bottom: 1px solid var(--sidebarBorder);
105105 border-radius: 2px 2px 0 0;
106106 }
107107
@@ -109,7 +109,7 @@ · 1 unmodified line
109109 ._menu-title-link:hover {
110110 display: block;
111111 padding: .5rem 1rem;
112− color: $focusText;
112+ color: var(--focusText);
113113 text-decoration: none;
114114 }
115115
@@ -121,9 +121,9 @@ · 5 unmodified lines
121121 text-decoration: none;
122122
123123 &:hover {
124− color: $focusText;
124+ color: var(--focusText);
125125 text-decoration: none;
126− background: $sidebarBackground;
126+ background: var(--sidebarBackground);
127127 }
128128
129129 &:last-child { border-radius: 0 0 2px 2px; }
@@ -151,11 +151,7 @@ · 21 unmodified lines
151151 opacity: .42;
152152 @extend %svg-icon;
153153
154− @if $style == 'dark' {
155− fill: white;
156− } @else {
157− fill: black;
158− }
154+ fill: var(--absolute);
159155 }
160156 }
161157
@@ -166,21 +162,19 @@ · 4 unmodified lines
166162 height: 100%;
167163 padding: 0 .75rem 1px 1.75rem;
168164 font-size: .875rem;
169− background: $contentBackground;
165+ background: var(--contentBackground);
170166 border: 1px solid;
171− border-color: darken($headerBorder, 2%);
167+ border-color: var(--searchBorder);
172168 border-radius: 3px;
173169
174170 &:focus {
175171 outline: 0;
176− @if $inputFocusBorder {
177− border-color: $inputFocusBorder;
178− box-shadow: 0 0 1px $inputFocusBorder;
179− }
172+ border-color: var(--inputFocusBorder);
173+ box-shadow: 0 0 1px var(--inputFocusBorder);
180174 }
181175
182176 &[disabled] {
183− background: $sidebarBackground;
177+ background: var(--sidebarBackground);
184178 cursor: not-allowed;
185179 }
186180 }
@@ -202,13 +196,9 @@ · 15 unmodified lines
202196 top: .5rem;
203197 left: .375rem;
204198 @extend %svg-icon;
199+ fill: var(--absolute);
205200 }
206201
207− @if $style == 'dark' {
208− > svg { fill: white; }
209− } @else {
210− > svg { fill: black; }
211− }
212202 ._search-active > & { display: block; }
213203 }
214204
@@ -222,8 +212,9 @@ · 7 unmodified lines
222212 line-height: 1.25rem;
223213 max-width: 50%;
224214 font-size: .8125rem;
225− color: $textColorLight;
226− background: darken($headerBackground, 5%);
215+ color: var(--textColorLight);
216+ background: var(--searchTagBackground);
227217 border-radius: 2px;
218+ cursor: pointer;
228219 @extend %truncate-text;
229220 }
assets/stylesheets/components/_mobile.scss+2−2
@@ -4,7 +4,7 @@ · 3 unmodified lines
44
55 ._mobile {
66 font-size: 100%;
7− background: $contentBackground;
7+ background: var(--contentBackground);
88
99 ._hide-on-mobile { display: none; }
1010
@@ -18,7 +18,7 @@ · 7 unmodified lines
1818
1919 ._container {
2020 margin: 0;
21− padding-top: $headerHeight;
21+ padding-top: var(--headerHeight);
2222 }
2323
2424 ._content {
assets/stylesheets/components/_notice.scss+5−5
@@ -1,15 +1,15 @@
11 ._notice {
22 position: absolute;
3− z-index: $noticeZ;
3+ z-index: var(--noticeZ);
44 bottom: 0;
5− left: $sidebarWidth;
5+ left: var(--sidebarWidth);
66 right: 0;
77 height: 2.5rem;
88 padding: 0 1.25rem;
9− background: $noticeBackground;
10− box-shadow: inset 0 1px $noticeBorder;
9+ background: var(--noticeBackground);
10+ box-shadow: inset 0 1px var(--noticeBorder);
1111
12− @media #{$mediumScreen} { left: $sidebarMediumWidth; }
12+ @include mobile { left: var(--sidebarMediumWidth); }
1313
1414 ._sidebar-hidden & { left: 0; }
1515
assets/stylesheets/components/_notif.scss+16−12
@@ -7,9 +7,9 @@ · 6 unmodified lines
77 max-width: 90%;
88 padding: .625rem 1rem;
99 font-size: .75rem;
10− color: $notifColor;
11− background: $notifBackground;
12− border: $notifBorder;
10+ color: var(--notifColor);
11+ background: var(--notifBackground);
12+ border: var(--notifBorder);
1313 border-radius: .25rem;
1414 transition: opacity .2s;
1515 opacity: 0;
@@ -30,7 +30,7 @@ · 14 unmodified lines
3030
3131 ._notif-info {
3232 float: right;
33− color: $notifColorLight;
33+ color: var(--notifColorLight);
3434 }
3535
3636 ._notif-link,
@@ -69,13 +69,13 @@ · 32 unmodified lines
6969 &::-webkit-scrollbar { width: 10px !important; }
7070
7171 &::-webkit-scrollbar-track {
72− background: $notifBackground !important;
72+ background: var(--notifBackground) !important;
7373 border: 0 !important;
7474 border-radius: 5px !important;
7575 }
7676
7777 &::-webkit-scrollbar-thumb {
78− border: 3px solid $notifBackground !important;
78+ border: 3px solid var(--notifBackground) !important;
7979
8080 &:hover, &:active { border-width: 2px !important; }
8181 }
@@ -90,7 +90,7 @@ · 8 unmodified lines
9090 > ._news-row {
9191 line-height: 1.125rem;
9292 font-size: .6875rem;
93− color: $notifColorLight;
93+ color: var(--notifColorLight);
9494 margin-bottom: .25rem;
9595
9696 + ._news-row { margin-top: .625rem; }
@@ -107,7 +107,7 @@ · 10 unmodified lines
107107 ._news-date {
108108 float: right;
109109 margin-left: 1rem;
110− font-weight: $boldFontWeight;
110+ font-weight: var(--boldFontWeight);
111111 }
112112
113113 code {
@@ -128,9 +128,13 @@ · 14 unmodified lines
128128 }
129129
130130 ._notif-tip {
131− color: $textColor;
132− background: $tipBackground;
133− border: $tipBorder;
131+ color: var(--textColor);
132+ background: var(--tipBackground);
133+ border: var(--tipBorder);
134134
135− ._notif-info { color: $textColorLight; }
135+ ._notif-info { color: var(--textColorLight); }
136+}
137+
138+._notif-right {
139+ float: right;
136140 }
assets/stylesheets/components/_page.scss+4−4
@@ -23,7 +23,7 @@ · 22 unmodified lines
2323 max-width: 100%;
2424 margin-bottom: 1em;
2525 padding: 1px;
26− border: 1px dotted $boxBorder;
26+ border: 1px dotted var(--boxBorder);
2727 border-radius: 3px;
2828 @extend %border-box;
2929 }
@@ -50,7 +50,7 @@ · 20 unmodified lines
5050 display: inline-block;
5151 vertical-align: top;
5252 padding: 0 .5rem;
53− background: $contentBackground;
53+ background: var(--contentBackground);
5454 @extend %internal-link;
5555
5656 & + & { margin-left: .75rem; }
@@ -66,7 +66,7 @@ · 9 unmodified lines
6666 clear: both;
6767 margin: 2rem 0 1.5rem;
6868 font-size: .75rem;
69− color: $textColorLight;
69+ color: var(--textColorLight);
7070 text-align: center;
7171 -webkit-font-smoothing: subpixel-antialiased;
7272
@@ -78,7 +78,7 @@ · 5 unmodified lines
7878 display: inline-block;
7979 margin: 0;
8080 padding: .25rem .75rem;
81− background: $labelBackground;
81+ background: var(--labelBackground);
8282 border-radius: 3px;
8383 }
8484
assets/stylesheets/components/_path.scss+6−6
@@ -1,17 +1,17 @@
11 ._path {
22 position: absolute;
3− z-index: $noticeZ - 1;
3+ z-index: var(--headerZ);
44 bottom: 0;
5− left: $sidebarWidth;
5+ left: var(--sidebarWidth);
66 right: 0;
77 height: 2rem;
88 line-height: 2rem;
99 padding: 0 .375rem;
1010 font-size: .875rem;
11− background: $pathBackground;
12− box-shadow: inset 0 1px $pathBorder;
11+ background: var(--pathBackground);
12+ box-shadow: inset 0 1px var(--pathBorder);
1313
14− @media #{$mediumScreen} { left: $sidebarMediumWidth; }
14+ @include mobile { left: var(--sidebarMediumWidth); }
1515
1616 ._sidebar-hidden & { left: 0; }
1717
@@ -24,7 +24,7 @@ · 6 unmodified lines
2424 display: inline-block;
2525 vertical-align: top;
2626 padding: 0 .375rem;
27− color: $textColor;
27+ color: var(--textColor);
2828 text-decoration: none;
2929
3030 &:first-child:before {
assets/stylesheets/components/_prism.scss+25−32
@@ -1,9 +1,26 @@
1+html {
2+ --prismValue: #905;
3+ --prismText: #5e8e01;
4+ --prismOperator: #a67f59;
5+ --prismKeyword: #0070a3;
6+ --prismFunction: #dd4a68;
7+ --prismVariable: #e90;
8+}
9+html._theme-dark {
10+ --prismValue: #eb8160;
11+ --prismText: #ddcf88;
12+ --prismOperator: #b1c676;
13+ --prismKeyword: #91b3ed;
14+ --prismFunction: #c79e6b;
15+ --prismVariable: #e9c062;
16+}
17+
118 .token.comment,
219 .token.prolog,
320 .token.doctype,
421 .token.cdata,
522 .token.punctuation {
6− color: $textColorLight;
23+ color: var(--textColorLight);
724 }
825
926 .namespace {
@@ -17,11 +34,7 @@ · 7 unmodified lines
1734 .token.constant,
1835 .token.symbol,
1936 .token.deleted {
20− @if $style == 'dark' {
21− color: #eb8160;
22− } @else {
23− color: #905;
24− }
37+ color: var(--prismValue);
2538 }
2639
2740 .token.selector,
@@ -30,11 +43,7 @@ · 2 unmodified lines
3043 .token.char,
3144 .token.builtin,
3245 .token.inserted {
33− @if $style == 'dark' {
34− color: #ddcf88;
35− } @else {
36− color: #5e8e01;
37− }
46+ color: var(--prismText);
3847 }
3948
4049 .token.operator,
@@ -42,44 +51,28 @@ · 1 unmodified line
4251 .token.url,
4352 .language-css .token.string,
4453 .style .token.string {
45− @if $style == 'dark' {
46− color: #b1c676;
47− } @else {
48− color: #a67f59;
49− }
54+ color: var(--prismOperator);
5055 }
5156
5257 .token.atrule,
5358 .token.attr-value,
5459 .token.keyword {
55− @if $style == 'dark' {
56− color: #91b3ed;
57− } @else {
58− color: #0070a3;
59− }
60+ color: var(--prismKeyword);
6061 }
6162
6263 .token.function {
63− @if $style == 'dark' {
64− color: #c79e6b;
65− } @else {
66− color: #dd4a68;
67− }
64+ color: var(--prismFunction);
6865 }
6966
7067 .token.regex,
7168 .token.important,
7269 .token.variable {
73− @if $style == 'dark' {
74− color: #e9c062;
75− } @else {
76− color: #e90;
77− }
70+ color: var(--prismVariable);
7871 }
7972
8073 .token.important,
8174 .token.bold {
82− font-weight: $boldFontWeight;
75+ font-weight: var(--boldFontWeight);
8376 }
8477
8578 .token.italic {
assets/stylesheets/components/_settings.scss+12−12
@@ -7,15 +7,15 @@ · 6 unmodified lines
77 position: absolute;
88 top: 0;
99 bottom: 0;
10− z-index: $headerZ;
10+ z-index: var(--headerZ);
1111
1212 &._in { display: block; }
1313
1414 > ._header { justify-content: space-between; }
1515
1616 &._dirty > ._header {
17− background: $noteGreenBackground;
18− border-color: $noteGreenBorder;
17+ background: var(--noteGreenBackground);
18+ border-color: var(--noteGreenBorder);
1919 }
2020 }
2121
@@ -37,7 +37,7 @@ · 15 unmodified lines
3737 padding-right: .5rem;
3838 line-height: inherit;
3939 font-size: inherit;
40− font-weight: $boldFontWeight;
40+ font-weight: var(--boldFontWeight);
4141 text-align: right;
4242 @extend %border-box;
4343 }
@@ -52,7 +52,7 @@ · 8 unmodified lines
5252
5353 > small {
5454 display: block;
55− color: $textColorLight;
55+ color: var(--textColorLight);
5656 margin-left: 1.75rem;
5757 }
5858
@@ -62,7 +62,7 @@ · 3 unmodified lines
6262 }
6363 }
6464
65−@media (max-width: $maxWidth) {
65+@media (max-width: 80rem) {
6666 ._setting-max-width { display: none; }
6767 }
6868
@@ -80,7 +80,7 @@ · 11 unmodified lines
8080 line-height: 1.5rem;
8181 padding: 0 .75rem;
8282 font-size: .875rem;
83− font-weight: $boldFontWeight;
83+ font-weight: var(--boldFontWeight);
8484 color: inherit;
8585 text-align: left;
8686 cursor: pointer;
@@ -119,12 +119,12 @@ · 32 unmodified lines
119119 position: relative;
120120 vertical-align: top;
121121 padding: 0 .75rem;
122− line-height: $headerHeight;
123− color: $textColorLight;
122+ line-height: var(--headerHeight);
123+ color: var(--textColorLight);
124124
125125 &.active {
126− color: $textColor;
127− font-weight: $boldFontWeight;
128− box-shadow: inset 0 -2px $linkColor;
126+ color: var(--textColor);
127+ font-weight: var(--boldFontWeight);
128+ box-shadow: inset 0 -2px var(--linkColor);
129129 }
130130 }
assets/stylesheets/components/_sidebar.scss+37−39
@@ -4,14 +4,14 @@ · 3 unmodified lines
44
55 ._sidebar {
66 position: absolute;
7− z-index: $sidebarZ;
7+ z-index: var(--sidebarZ);
88 top: 0;
99 bottom: 0;
1010 left: 0;
1111 overflow-x: hidden;
1212 overflow-y: scroll;
13− padding-top: $headerHeight;
14− background: $sidebarBackground;
13+ padding-top: var(--headerHeight);
14+ background: var(--sidebarBackground);
1515 background-clip: content-box;
1616 -webkit-overflow-scrolling: touch;
1717 -ms-overflow-style: none; // IE 10 doesn't support pointer-events
@@ -22,13 +22,13 @@ · 4 unmodified lines
2222
2323 ._overlay-scrollbars & {
2424 padding-top: 0;
25− top: $headerHeight;
25+ top: var(--headerHeight);
2626 }
2727
2828 body:not(._native-scrollbars) & {
2929 &::-webkit-scrollbar { width: 10px; }
3030 &::-webkit-scrollbar-track {
31− background: $contentBackground;
31+ background: var(--contentBackground);
3232 border: 0;
3333 }
3434 &::-webkit-scrollbar-thumb {
@@ -52,10 +52,10 @@ · 17 unmodified lines
5252
5353 ._resizer {
5454 position: absolute;
55− z-index: $sidebarZ + 1;
56− top: $headerHeight;
57− bottom: $headerHeight;
58− left: $sidebarWidth;
55+ z-index: var(--headerZ);
56+ top: var(--headerHeight);
57+ bottom: var(--headerHeight);
58+ left: var(--sidebarWidth);
5959 margin-left: -2px;
6060 width: 3px;
6161 cursor: col-resize;
@@ -72,11 +72,11 @@ · 10 unmodified lines
7272 margin: 0;
7373 padding: 0;
7474 list-style: none;
75− width: $sidebarWidth;
76− box-shadow: inset -1px 0 $sidebarBorder;
75+ width: var(--sidebarWidth);
76+ box-shadow: inset -1px 0 var(--sidebarBorder);
7777 @extend %border-box;
7878
79− @media #{$mediumScreen} { width: $sidebarMediumWidth; }
79+ @include mobile { width: var(--sidebarMediumWidth); }
8080
8181 ._sidebar > & { min-height: 100%; }
8282
@@ -89,7 +89,7 @@ · 6 unmodified lines
8989 padding: 0 .75rem 0 2.125rem;
9090 line-height: 2rem;
9191 font-size: .75rem;
92− color: $textColorLight;
92+ color: var(--textColorLight);
9393 text-transform: uppercase;
9494 cursor: default;
9595 }
@@ -110,8 +110,8 @@ · 14 unmodified lines
110110 line-height: 1.5rem;
111111 font-size: .875rem;
112112 cursor: default;
113− background: $sidebarBackground;
114− box-shadow: inset -1px 0 $sidebarBorder;
113+ background: var(--sidebarBackground);
114+ box-shadow: inset -1px 0 var(--sidebarBorder);
115115 @extend %truncate-text;
116116
117117 &, &:hover {
@@ -123,16 +123,16 @@ · 5 unmodified lines
123123 &.focus:hover,
124124 &.active,
125125 &.active:hover {
126− color: $focusText;
127− background: $focusBackground;
128− box-shadow: inset -1px 0 $focusBorder;
126+ color: var(--focusText);
127+ background: var(--focusBackground);
128+ box-shadow: inset -1px 0 var(--focusBorder);
129129 }
130130
131131 &.active,
132132 &.active:hover {
133− color: $selectionText;
134− background: $selectionBackground;
135− box-shadow: inset -1px 0 $selectionBorder;
133+ color: var(--selectionText);
134+ background: var(--selectionBackground);
135+ box-shadow: inset -1px 0 var(--selectionBorder);
136136 }
137137
138138 &:before {
@@ -160,7 +160,7 @@ · 21 unmodified lines
160160 }
161161
162162 ._list-count {
163− color: $textColorLighter;
163+ color: var(--textColorLighter);
164164 pointer-events: none;
165165
166166 ._list-disabled:hover > & { display: none; }
@@ -168,7 +168,7 @@ · 1 unmodified line
168168
169169 ._list-enable {
170170 display: none;
171− color: $linkColor;
171+ color: var(--linkColor);
172172 cursor: pointer;
173173
174174 &:hover { text-decoration: underline; }
@@ -188,7 +188,7 @@ · 13 unmodified lines
188188 ._list-disabled {
189189 @extend %_list-dir;
190190
191− &, &:hover { color: $textColorLight; }
191+ &, &:hover { color: var(--textColorLight); }
192192 &:before { opacity: .7; }
193193 }
194194
@@ -200,11 +200,9 @@ · 5 unmodified lines
200200 width: 1rem;
201201 height: 1rem;
202202 cursor: pointer;
203− fill: black;
203+ fill: var(--absolute);
204204 opacity: .4;
205205
206− @if $style == 'dark' { fill: white; }
207−
208206 &:hover { opacity: .65; }
209207
210208 ._list-rdir > & {
@@ -239,11 +237,11 @@ · 28 unmodified lines
239237 //
240238
241239 ._list-pagelink {
242− color: $linkColor;
240+ color: var(--linkColor);
243241 cursor: pointer;
244242
245243 &:hover {
246− color: $linkColorHover;
244+ color: var(--linkColorHover);
247245 text-decoration: underline;
248246 }
249247 }
@@ -275,9 +273,9 @@ · 25 unmodified lines
275273 left: .75rem;
276274 width: .75rem;
277275 height: 1px;
278− background: rgba($selectionText, .9);
279− box-shadow: 0 -3px rgba($selectionText, .9), // top line
280− 0 3px rgba($selectionText, .9); // bottom line
276+ background: var(--transparentSelectionText);
277+ box-shadow: 0 -3px var(--transparentSelectionText), // top line
278+ 0 3px var(--transparentSelectionText); // bottom line
281279 }
282280 }
283281
@@ -289,7 +287,7 @@ · 5 unmodified lines
289287 padding: .5rem .75rem;
290288 line-height: 1.25rem;
291289 font-size: .8125rem;
292− color: $textColorLight;
290+ color: var(--textColorLight);
293291
294292 & + & { padding-top: 0; }
295293 }
@@ -303,9 +301,9 @@ · 7 unmodified lines
303301 ._list-hover.clone {
304302 position: fixed;
305303 overflow: visible;
306− z-index: $hoverZ;
304+ z-index: var(--hoverZ);
307305 left: 0;
308− min-width: $sidebarWidth;
306+ min-width: var(--sidebarWidth);
309307 padding: .25rem .75rem;
310308 pointer-events: none;
311309 -webkit-font-smoothing: subpixel-antialiased;
@@ -313,7 +311,7 @@ · 1 unmodified line
313311 transform: translate3d(0, 0, 0);
314312 @extend %border-box;
315313
316− @media #{$mediumScreen} { min-width: $sidebarMediumWidth; }
314+ @include mobile { min-width: var(--sidebarMediumWidth); }
317315
318316 > ._list-text { display: inline; }
319317
@@ -346,10 +344,10 @@ · 26 unmodified lines
346344 padding: .5rem .75rem .25rem .75rem;
347345 line-height: 1.5rem;
348346 font-size: .75rem;
349− font-weight: $bolderFontWeight;
350− color: $textColorLight;
347+ font-weight: var(--bolderFontWeight);
348+ color: var(--textColorLight);
351349 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));
353351 cursor: default;
354352 }
355353
assets/stylesheets/global/_base.scss+40−31
@@ -1,9 +1,13 @@
11 html {
22 height: 100%;
33 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);
56
6− @media #{$mediumScreen} { font-size: 93.75%; }
7+ @include mobile { font-size: 93.75%; }
8+
9+ @include print { background: none; }
10+ @import 'global/print';
711 }
812
913 body {
@@ -14,10 +18,11 @@ body { · 4 unmodified lines
1418 font-weight: normal;
1519 font-family: $baseFont;
1620 line-height: 1.7;
17− color: $textColor;
21+ color: $textColor; // fallback to show the error message to browsers that don't support CSS variables.
22+ color: var(--textColor);
1823 word-wrap: break-word;
1924 overflow-wrap: break-word;
20− background: $contentBackground;
25+ background: var(--contentBackground);
2126 touch-action: manipulation;
2227 -webkit-tap-highlight-color: rgba(black, 0);
2328 -webkit-touch-callout: none;
@@ -26,11 +31,11 @@ body { · 2 unmodified lines
2631 }
2732
2833 a {
29− color: $linkColor;
30− text-decoration: $linkTextDecoration;
34+ color: var(--linkColor);
35+ text-decoration: var(--linkTextDecoration);
3136
3237 &:hover {
33− color: $linkColorHover;
38+ color: var(--linkColorHover);
3439 text-decoration: underline;
3540 }
3641 }
@@ -44,7 +49,7 @@ img { · 7 unmodified lines
4449 h1, h2, h3, h4, h5, h6 {
4550 margin: 1.5em 0 1em;
4651 line-height: 1.3;
47− font-weight: $bolderFontWeight;
52+ font-weight: var(--bolderFontWeight);
4853 }
4954
5055 h1 { font-size: 1.5em; }
@@ -56,7 +61,7 @@ h5, h6 { font-size: 1em; } · 5 unmodified lines
5661 p { margin: 0 0 1em; }
5762 p:last-child { margin-bottom: 0; }
5863
59−b, strong { font-weight: $boldFontWeight; }
64+b, strong { font-weight: var(--boldFontWeight); }
6065
6166 small { font-size: .9em; }
6267
@@ -76,7 +81,7 @@ li > ul, li > ol, dd > ul, dd > ol { margin: .5em 0; } · 13 unmodified lines
7681 li > p { margin-bottom: .25em; }
7782
7883 dl { margin: 1.5em 0; }
79−dt { font-weight: $boldFontWeight; }
84+dt { font-weight: var(--boldFontWeight); }
8085 dd {
8186 margin: .375em;
8287 padding-left: 1em;
@@ -88,15 +93,15 @@ dfn, var { font-style: normal; } · 5 unmodified lines
8893
8994 abbr, acronym, dfn {
9095 cursor: help;
91− border-bottom: 1px dotted $textColor;
96+ border-bottom: 1px dotted var(--textColor);
9297 }
9398
9499 pre, code, samp, %pre, %code {
95− font-family: $monoFont;
100+ font-family: var(--monoFont);
96101 font-weight: normal;
97102 font-style: normal;
98103 font-size: .9em;
99− color: $textColor;
104+ color: var(--textColor);
100105 white-space: pre-wrap;
101106 direction: ltr;
102107 -moz-tab-size: 2;
@@ -118,14 +123,14 @@ a > code { color: inherit; } · 15 unmodified lines
118123 table {
119124 margin: 1.5em 0;
120125 background: none;
121− border: 1px solid $boxBorder;
126+ border: 1px solid var(--boxBorder);
122127 border-collapse: separate;
123128 border-spacing: 0;
124129 border-radius: 3px;
125130 }
126131
127132 caption {
128− font-weight: $boldFontWeight;
133+ font-weight: var(--boldFontWeight);
129134 padding: 0 .7em .3em;
130135 }
131136
@@ -139,15 +144,15 @@ th, td { · 7 unmodified lines
139144 }
140145
141146 th {
142− font-weight: $boldFontWeight;
147+ font-weight: var(--boldFontWeight);
143148 border: 0;
144− border-bottom: 1px solid $boxBorder;
149+ border-bottom: 1px solid var(--boxBorder);
145150 border-radius: 0;
146151 @extend %heading-box;
147152
148153 &:empty { background: none; }
149154
150− + th, + td { border-left: 1px solid $boxBorder; }
155+ + th, + td { border-left: 1px solid var(--boxBorder); }
151156
152157 tr:first-child > &:first-child { border-top-left-radius: 3px; }
153158 tr:first-child > &:last-child { border-top-right-radius: 3px; }
@@ -158,10 +163,10 @@ th { · 4 unmodified lines
158163 }
159164
160165 td {
161− background: $contentBackground;
162− border-bottom: 1px solid $boxBorderLight;
166+ background: var(--contentBackground);
167+ border-bottom: 1px solid var(--boxBorderLight);
163168
164− + td { border-left: 1px solid $boxBorderLight; }
169+ + td { border-left: 1px solid var(--boxBorderLight); }
165170 tr:last-child > & { border-bottom: 0; }
166171
167172 > pre:only-child, > p:only-child, > ul:only-child, > ol:only-child {
@@ -187,7 +192,7 @@ input, button { · 19 unmodified lines
187192 margin: 0;
188193 font-family: inherit;
189194 font-size: 100%;
190− color: $textColor;
195+ color: var(--textColor);
191196 line-height: normal;
192197 @extend %border-box;
193198 }
@@ -215,6 +220,10 @@ button:focus { · 21 unmodified lines
215220 outline: -webkit-focus-ring-color auto 5px;
216221 }
217222
223+img, iframe {
224+ background: var(--externalsBackground);
225+}
226+
218227 input[type="search"]::-webkit-search-cancel-button,
219228 input[type="search"]::-webkit-search-decoration {
220229 -webkit-appearance: none;
@@ -227,9 +236,9 @@ input[type="search"]::-webkit-search-decoration { · 6 unmodified lines
227236 border: 0 !important;
228237 }
229238
230−::-webkit-input-placeholder { color: $textColorLighter; }
231−::-moz-placeholder { color: $textColorLighter; opacity: 1; }
232−:-ms-input-placeholder { color: $textColorLighter; }
239+::-webkit-input-placeholder { color: var(--textColorLighter); }
240+::-moz-placeholder { color: var(--textColorLighter); opacity: 1; }
241+:-ms-input-placeholder { color: var(--textColorLighter); }
233242
234243 body:not(._native-scrollbars) {
235244 *::-webkit-scrollbar { -webkit-appearance: none; }
@@ -240,12 +249,12 @@ body:not(._native-scrollbars) { · 4 unmodified lines
240249 *::-webkit-scrollbar-corner { display: none; }
241250
242251 *::-webkit-scrollbar-track {
243− background: $contentBackground;
244− border: 1px solid $contentBackground;
252+ background: var(--contentBackground);
253+ border: 1px solid var(--contentBackground);
245254
246255 &:hover {
247− background: $sidebarBackground;
248− border-color: $sidebarBorder;
256+ background: var(--sidebarBackground);
257+ border-color: var(--sidebarBorder);
249258 }
250259
251260 &:vertical { border-width: 0 0 0 1px; }
@@ -263,14 +272,14 @@ body:not(._native-scrollbars) { · 11 unmodified lines
263272
264273 *::-webkit-scrollbar-thumb {
265274 min-height: 2rem;
266− background: $scrollbarColor;
275+ background: var(--scrollbarColor);
267276 background-clip: padding-box;
268277 border: 5px solid rgba(black, 0);
269278 border-radius: 10px;
270279
271280 &:hover,
272281 &:active {
273− background-color: $scrollbarColorHover;
282+ background-color: var(--scrollbarColorHover);
274283 border-width: 4px;
275284 }
276285 }
assets/stylesheets/global/_classes.scss+24−25
@@ -36,15 +36,15 @@ · 35 unmodified lines
3636 //
3737
3838 %box {
39− background: $boxBackground;
40− border: 1px solid $boxBorder;
39+ background: var(--boxBackground);
40+ border: 1px solid var(--boxBorder);
4141 border-radius: 3px;
4242 }
4343
4444 %heading-box {
45− color: $boxHeaderColor;
46− background: $boxHeaderBackground;
47− border: 1px solid $boxBorder;
45+ color: var(--boxHeaderColor);
46+ background: var(--boxHeaderBackground);
47+ border: 1px solid var(--boxBorder);
4848 border-radius: 3px;
4949 }
5050
@@ -82,15 +82,15 @@ · 31 unmodified lines
8282 %note {
8383 margin: 1.5rem 0;
8484 padding: .5rem .875rem;
85− background: $noteBackground;
86− border: 1px solid $noteBorder;
85+ background: var(--noteBackground);
86+ border: 1px solid var(--noteBorder);
8787 border-radius: 3px;
8888 }
8989
9090 %label {
9191 margin: 0 1px;
9292 padding: 1px 4px 2px;
93− background: $labelBackground;
93+ background: var(--labelBackground);
9494 border-radius: 3px;
9595 }
9696
@@ -102,35 +102,35 @@ · 5 unmodified lines
102102 padding-right: .5em;
103103 overflow: hidden;
104104 font-size: inherit;
105− color: $boxHeaderColor;
106− border: 1px solid $boxBorder;
105+ color: var(--boxHeaderColor);
106+ border: 1px solid var(--boxBorder);
107107 border-radius: 2px;
108108 @extend %label;
109109 }
110110
111111 %label-yellow {
112− background: $noteBackground;
113− border-color: $noteBorder;
112+ background: var(--noteBackground);
113+ border-color: var(--noteBorder);
114114 }
115115
116116 %note-green, %label-green {
117− background: $noteGreenBackground;
118− border-color: $noteGreenBorder;
117+ background: var(--noteGreenBackground);
118+ border-color: var(--noteGreenBorder);
119119 }
120120
121121 %note-blue, %label-blue {
122− background: $noteBlueBackground;
123− border-color: $noteBlueBorder;
122+ background: var(--noteBlueBackground);
123+ border-color: var(--noteBlueBorder);
124124 }
125125
126126 %note-orange, %label-orange {
127− background: $noteOrangeBackground;
128− border-color: $noteOrangeBorder;
127+ background: var(--noteOrangeBackground);
128+ border-color: var(--noteOrangeBorder);
129129 }
130130
131131 %note-red, %label-red {
132− background: $noteRedBackground;
133− border-color: $noteRedBorder;
132+ background: var(--noteRedBackground);
133+ border-color: var(--noteRedBorder);
134134 }
135135
136136 //
@@ -149,12 +149,11 @@ · 12 unmodified lines
149149 pointer-events: none;
150150
151151 // <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' {
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− }
152+ background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzMzNzdjMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
157153 }
158154 }
155+html._theme-dark %external-link:after {
156+ background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2NiZDBkMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
157+}
159158
160159 %internal-link:after { content: none !important; }
assets/stylesheets/global/_icons.scss+0−166
@@ -1,166 +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-mongoose:before { background-position: -7rem -5rem; }
96−._icon-phpunit:before { background-position: -8rem -5rem; }
97−._icon-nokogiri:before { background-position: -9rem -5rem; @extend %darkIconFix !optional; }
98−._icon-rethinkdb:before { background-position: 0 -6rem; }
99−._icon-react:before { background-position: -1rem -6rem; }
100−._icon-socketio:before { background-position: -2rem -6rem; }
101−._icon-modernizr:before { background-position: -3rem -6rem; }
102−._icon-bower:before { background-position: -4rem -6rem; }
103−._icon-fish:before { background-position: -5rem -6rem; @extend %darkIconFix !optional; }
104−._icon-scikit_image:before { background-position: -6rem -6rem; }
105−._icon-twig:before { background-position: -7rem -6rem; }
106−._icon-pandas:before { background-position: -8rem -6rem; }
107−._icon-scikit_learn:before { background-position: -9rem -6rem; }
108−._icon-bottle:before { background-position: 0 -7rem; }
109−._icon-docker:before { background-position: -1rem -7rem; }
110−._icon-cakephp:before { background-position: -2rem -7rem; }
111−._icon-lua:before { background-position: -3rem -7rem; @extend %darkIconFix !optional; }
112−._icon-clojure:before { background-position: -4rem -7rem; }
113−._icon-symfony:before { background-position: -5rem -7rem; }
114−._icon-mocha:before { background-position: -6rem -7rem; }
115−._icon-meteor:before { background-position: -7rem -7rem; @extend %darkIconFix !optional; }
116−._icon-npm:before { background-position: -8rem -7rem; }
117−._icon-apache_http_server:before { background-position: -9rem -7rem; }
118−._icon-drupal:before { background-position: 0 -8rem; }
119−._icon-webpack:before { background-position: -1rem -8rem; }
120−._icon-phaser:before { background-position: -2rem -8rem; }
121−._icon-vue:before { background-position: -3rem -8rem; }
122−._icon-opentsdb:before { background-position: -4rem -8rem; }
123−._icon-q:before { background-position: -5rem -8rem; }
124−._icon-crystal:before { background-position: -6rem -8rem; @extend %darkIconFix !optional; }
125−._icon-julia:before { background-position: -7rem -8rem; @extend %darkIconFix !optional; }
126−._icon-redux:before { background-position: -8rem -8rem; @extend %darkIconFix !optional; }
127−._icon-bootstrap:before { background-position: -9rem -8rem; }
128−._icon-react_native:before { background-position: 0 -9rem; }
129−._icon-phalcon:before { background-position: -1rem -9rem; }
130−._icon-matplotlib:before { background-position: -2rem -9rem; }
131−._icon-cmake:before { background-position: -3rem -9rem; }
132−._icon-elixir:before { background-position: -4rem -9rem; @extend %darkIconFix !optional; }
133−._icon-vagrant:before { background-position: -5rem -9rem; }
134−._icon-dojo:before { background-position: -6rem -9rem; }
135−._icon-flow:before { background-position: -7rem -9rem; }
136−._icon-relay:before { background-position: -8rem -9rem; }
137−._icon-phoenix:before { background-position: -9rem -9rem; }
138−
139−._icon-tcl_tk:before { background-position: 0 0; @extend %doc-icon-2; }
140−._icon-erlang:before { background-position: -1rem 0; @extend %doc-icon-2; }
141−._icon-chef:before { background-position: -2rem 0; @extend %doc-icon-2; }
142−._icon-ramda:before { background-position: -3rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
143−._icon-codeigniter:before { background-position: -4rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
144−._icon-influxdata:before { background-position: -5rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
145−._icon-tensorflow:before { background-position: -6rem 0; @extend %doc-icon-2; }
146−._icon-haxe:before { background-position: -7rem 0; @extend %doc-icon-2; }
147−._icon-ansible:before { background-position: -8rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; }
148−._icon-typescript:before { background-position: -9rem 0; @extend %doc-icon-2; }
149−._icon-browser_support_tables:before { background-position: 0rem -1rem; @extend %doc-icon-2; }
150−._icon-gnu_fortran:before { background-position: -1rem -1rem; @extend %doc-icon-2; }
151−._icon-gcc:before { background-position: -2rem -1rem; @extend %doc-icon-2; }
152−._icon-perl:before { background-position: -3rem -1rem; @extend %doc-icon-2; }
153−._icon-apache_pig:before { background-position: -4rem -1rem; @extend %doc-icon-2; }
154−._icon-numpy:before { background-position: -5rem -1rem; @extend %doc-icon-2; }
155−._icon-kotlin:before { background-position: -6rem -1rem; @extend %doc-icon-2; }
156−._icon-padrino:before { background-position: -7rem -1rem; @extend %doc-icon-2; }
157−._icon-angular:before { background-position: -8rem -1rem; @extend %doc-icon-2; }
158−._icon-love:before { background-position: -9rem -1rem; @extend %doc-icon-2; }
159−._icon-jasmine:before { background-position: 0 -2rem; @extend %doc-icon-2; }
160−._icon-pug:before { background-position: -1rem -2rem; @extend %doc-icon-2; }
161−._icon-electron:before { background-position: -2rem -2rem; @extend %doc-icon-2; }
162−._icon-falcon:before { background-position: -3rem -2rem; @extend %doc-icon-2; }
163−._icon-godot:before { background-position: -4rem -2rem; @extend %doc-icon-2; }
164−._icon-nim:before { background-position: -5rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; }
165−._icon-vulkan:before { background-position: -6rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; }
166−._icon-d:before { background-position: -7rem -2rem; @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+
25+html._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+15−0
@@ -0,0 +1,15 @@
1+//
2+// Mixins
3+//
4+
5+@mixin print {
6+ @media print {
7+ @content;
8+ }
9+}
10+
11+@mixin mobile {
12+ @media (max-width: 800px) {
13+ @content;
14+ }
15+}
assets/stylesheets/global/_print.scss+57−0
@@ -0,0 +1,57 @@
1+@include print {
2+ ._header, ._sidebar, ._path, ._notif, ._toc, ._pre-clip, ._notice, ._links {
3+ display: none !important;
4+ }
5+
6+ body, ._app, ._container, ._content {
7+ margin: 0;
8+ padding: 0;
9+ height: initial;
10+ background: none;
11+
12+ &:after {
13+ content: '';
14+ clear: both;
15+ }
16+ }
17+
18+ ::-webkit-scrollbar {
19+ display: none;
20+ }
21+
22+ %external-link:after {
23+ display: none;
24+ }
25+
26+ ._attribution-p {
27+ background: none;
28+ border: 2px solid var(--boxBorder);
29+ }
30+
31+ ._attribution:last-child:after {
32+ content: 'Exported from DevDocs \2014 https://devdocs.io';
33+ display: block;
34+ margin-top: 1rem;
35+ font-weight: var(--bolderFontWeight);
36+ }
37+
38+ ._attribution {
39+ page-break-inside: avoid;
40+ }
41+
42+ h1, h2, h3, h4, h5, h6 {
43+ page-break-inside: avoid;
44+ page-break-after: avoid;
45+ }
46+
47+ pre {
48+ page-break-before: avoid;
49+ orphans: 5;
50+ widows: 5;
51+ }
52+
53+ p {
54+ orphans: 2;
55+ widows: 2;
56+ }
57+}
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;
2−$monoFont: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
3−$boldFontWeight: 500;
4−$bolderFontWeight: 600;
1+html._theme-dark {
2+ --absolute: white;
53
6−$style: 'dark';
4+ --documentBackground: #222;
5+ --contentBackground: #33373a;
76
8−$maxWidth: 80rem;
9−$headerHeight: 3rem;
10−$sidebarWidth: 20rem;
11−$sidebarMediumWidth: 16rem;
7+ --textColor: #cbd0d0;
8+ --textColorLight: #9da5ad;
9+ --textColorLighter: #77787a;
1210
13−$documentBackground: #222;
14−$contentBackground: #33373a;
11+ --externalsBackground: #fff;
1512
16−$textColor: #cbd0d0;
17−$textColorLight: #9da5ad;
18−$textColorLighter: #77787a;
19−$textColorRed: #f44336;
13+ --inputFocusBorder: transparent;
2014
21−$inputFocusBorder: false;
15+ --focusBackground: #3f4042;
16+ --focusBorder: #000;
17+ --focusText: #f7f2f2;
2218
23−$focusBackground: #3f4042;
24−$focusBorder: #000;
25−$focusText: #f7f2f2;
19+ --loadingText: #5d6164;
2620
27−$loadingText: #5d6164;
28−$splashText: $loadingText;
21+ --selectionBackground: #007acc;
22+ --selectionBorder: #000;
2923
30−$selectionBackground: #007acc;
31−$selectionBorder: #000;
32−$selectionText: #fff;
24+ --highlightBackground: #64675f;
3325
34−$highlightBackground: #64675f;
26+ --linkColor: var(--textColor);
27+ --linkColorHover: white;
28+ --linkTextDecoration: underline;
3529
36−$linkColor: $textColor;
37−$linkColorHover: white;
38−$linkTextDecoration: underline;
30+ --headerBackground: #1c1c1c;
31+ --headerBorder: #000;
32+ --searchTagBackground: #{darken(#1c1c1c, 5%)};
33+ --searchBorder: #{darken(#000, 2%)};
3934
40−$headerBackground: #1c1c1c;
41−$headerBorder: #000;
35+ --sidebarBackground: #24282a;
36+ --transparentSidebarBackground: #{rgba(#24282a, 0)};
37+ --sidebarBorder: #000;
4238
43−$sidebarBackground: #24282a;
44−$sidebarBorder: #000;
39+ --scrollbarColor: #6c6c6f;
40+ --scrollbarColorHover: #949697;
4541
46−$scrollbarColor: #6c6c6f;
47−$scrollbarColorHover: #949697;
42+ --pathBackground: var(--headerBackground);
43+ --pathBorder: var(--headerBorder);
4844
49−$pathBackground: $headerBackground;
50−$pathBorder: $headerBorder;
45+ --noticeBackground: var(--sidebarBackground);
46+ --noticeBorder: var(--sidebarBorder);
5147
52−$noticeBackground: $sidebarBackground;
53−$noticeBorder: $sidebarBorder;
48+ --boxBackground: var(--sidebarBackground);
49+ --boxBorder: var(--headerBorder);
50+ --boxBorderLight: var(--headerBorder);
51+ --boxHeaderColor: #dbe4e4;
52+ --boxHeaderBackground: var(--sidebarBackground);
5453
55−$boxBackground: $sidebarBackground;
56−$boxBorder: $headerBorder;
57−$boxBorderLight: $headerBorder;
58−$boxHeaderColor: #dbe4e4;
59−$boxHeaderBackground: $sidebarBackground;
54+ --noteBackground: #45474b;
55+ --noteBorder: #000;
6056
61−$noteBackground: #45474b;
62−$noteBorder: #000;
57+ --noteGreenBackground: #284a2a;
58+ --noteGreenBorder: #000;
6359
64−$noteGreenBackground: #284a2a;
65−$noteGreenBorder: #000;
60+ --noteBlueBackground: #2a4151;
61+ --noteBlueBorder: #000;
6662
67−$noteBlueBackground: #2a4151;
68−$noteBlueBorder: #000;
63+ --noteOrangeBackground: #563322;
64+ --noteOrangeBorder: #000;
6965
70−$noteOrangeBackground: #563322;
71−$noteOrangeBorder: #000;
66+ --noteRedBackground: #603033;
67+ --noteRedBorder: #000;
7268
73−$noteRedBackground: #603033;
74−$noteRedBorder: #000;
69+ --labelBackground: var(--boxBackground);
7570
76−$labelBackground: $boxBackground;
71+ --notifBackground: #{rgba(#555, .95)};
72+ --notifBorder: 1px solid #000;
7773
78−$notifBackground: rgba(#555, .95);
79−$notifBorder: 1px solid #000;
80−$notifColor: #fff;
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;
74+ --tipBackground: var(--notifBackground);
75+ --tipBorder: var(--notifBorder);
76+}
assets/stylesheets/global/_variables-light.scss+76−0
@@ -0,0 +1,76 @@
1+html._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.
12 $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−
163 $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;
664
67−$noteBlueBackground: #d4f3fd;
68−$noteBlueBorder: #94bbeb;
5+html {
6+ --baseFont: #{$baseFont};
7+ --monoFont: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
8+ --boldFontWeight: 500;
9+ --bolderFontWeight: 600;
6910
70−$noteOrangeBackground: #fbe6d1;
71−$noteOrangeBorder: #ec8b01;
11+ --textColorRed: #f44336;
7212
73−$noteRedBackground: #fed5d3;
74−$noteRedBorder: #dc7874;
13+ --splashText: var(--loadingText);
7514
76−$labelBackground: #f4f4f4;
15+ --selectionText: #fff;
16+ --transparentSelectionText: rgba(255, 255, 255, 0.9);
7717
78−$notifBackground: rgba(#333, .85);
79−$notifBorder: none;
80−$notifColor: #fff;
81−$notifColorLight: #ccc;
18+ --notifColor: #fff;
19+ --notifColorLight: #ccc;
8220
83−$tipBackground: rgba(#fffdcd, .95);
84−$tipBorder: 1px solid #e7dca9;
21+ --maxWidth: 80rem;
22+ --headerHeight: 3rem;
23+ --sidebarWidth: 20rem;
24+ --sidebarMediumWidth: 16rem;
8525
86−$mediumScreen: '(max-width: 800px)';
26+ --focusBackground: #e5e5e5;
27+ --focusBorder: #d4d4d4;
28+ --focusText: #000;
8729
88−$contentZ: 1;
89−$sidebarZ: 2;
90−$headerZ: 3;
91−$noticeZ: 4;
92−$hoverZ: 5;
30+ --contentZ: 1;
31+ --sidebarZ: 2;
32+ --headerZ: 3;
33+ --noticeZ: 4;
34+ --hoverZ: 5;
35+}
assets/stylesheets/pages/_angular.scss+2−0
@@ -19,4 +19,6 @@ · 18 unmodified lines
1919 font-style: italic;
2020 text-align: right;
2121 }
22+
23+ td h3 { margin: 0 !important; }
2224 }
assets/stylesheets/pages/_angularjs.scss+1−1
@@ -8,7 +8,7 @@ · 7 unmodified lines
88 .nav-index-section {
99 margin: 1.5em 0 1em -2em;
1010 list-style: none;
11− font-weight: $boldFontWeight;
11+ font-weight: var(--boldFontWeight);
1212 text-transform: capitalize;
1313 }
1414
assets/stylesheets/pages/_async.scss+2−2
@@ -3,13 +3,13 @@ · 2 unmodified lines
33
44 h3 > .type-signature {
55 float: right;
6− color: $textColorLight;
6+ color: var(--textColorLight);
77 }
88
99 h3 > .signature-attributes {
1010 font-size: .75rem;
1111 font-weight: normal;
1212 font-style: italic;
13− color: $textColorLighter;
13+ color: var(--textColorLighter);
1414 }
1515 }
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
44 h4 > code, h5 > code, strong > code { @extend %label; }
55
66 h2 > small {
7− color: $textColorLight;
7+ color: var(--textColorLight);
88 float: right;
99 }
1010
assets/stylesheets/pages/_c.scss+4−2
@@ -23,7 +23,7 @@ · 22 unmodified lines
2323
2424 .t-spar { // language/switch, language/for, etc.
2525 font-style: italic;
26− color: $textColorLight;
26+ color: var(--textColorLight);
2727 }
2828 .t-sdsc-nopad dl, .t-sdsc-nopad dd { margin: 0; }
2929
@@ -46,6 +46,8 @@ · 16 unmodified lines
4646
4747 + div { margin-left: .5em; }
4848 }
49+
50+ > table { margin: 0; }
4951 }
5052
5153 .t-dcl-rev-aux > td:empty { padding: 0; }
@@ -55,7 +57,7 @@ · 3 unmodified lines
5557 margin: 1rem 0;
5658 padding: .375rem;
5759 font-size: .75rem;
58− border: 1px solid $boxBorder;
60+ border: 1px solid var(--boxBorder);
5961 border-radius: 2px;
6062 }
6163
assets/stylesheets/pages/_clojure.scss+1−1
@@ -5,6 +5,6 @@ · 4 unmodified lines
55 .type {
66 float: right;
77 font-size: .9em;
8− color: $textColorLight;
8+ color: var(--textColorLight);
99 }
1010 }
assets/stylesheets/pages/_d.scss+3−3
@@ -2,10 +2,10 @@ · 1 unmodified line
22 h2 { @extend %block-heading; }
33 h3, .d_decl { @extend %block-label, %label-blue; }
44 .d_decl { @extend %code; }
5− .d_decl > small { color: $textColorLight; }
6− .d_decl > strong { font-weight: $bolderFontWeight; }
5+ .d_decl > small { color: var(--textColorLight); }
6+ .d_decl > strong { font-weight: var(--bolderFontWeight); }
77
88 p > code, li > code, td > code, dd > code { @extend %label; }
99
10− span.red { color: $textColorRed; }
10+ span.red { color: var(--textColorRed); }
1111 }
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 @@
11 ._dojo {
22 @extend %simple;
33
4− .jsdoc-inheritance { color: $textColorLight; }
4+ .jsdoc-inheritance { color: var(--textColorLight); }
55 }
assets/stylesheets/pages/_ember.scss+2−2
@@ -5,7 +5,7 @@ · 4 unmodified lines
55
66 h3 > .access {
77 float: right;
8− color: $textColorLight;
8+ color: var(--textColorLight);
99 font-weight: normal;
1010 }
1111
@@ -15,6 +15,6 @@ · 3 unmodified lines
1515 }
1616
1717 p.github-link {
18− color: $textColorLight;
18+ color: var(--textColorLight);
1919 }
2020 }
assets/stylesheets/pages/_erlang.scss+1−1
@@ -5,5 +5,5 @@ · 4 unmodified lines
55 code.code { @extend %label; }
66 .note { @extend %note; }
77 .warning { @extend %note, %note-red; }
8− .note .label, .warning .label { font-weight: $boldFontWeight; }
8+ .note .label, .warning .label { font-weight: var(--boldFontWeight); }
99 }
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/_jekyll.scss+7−0
@@ -0,0 +1,7 @@
1+._jekyll {
2+ @extend %simple;
3+
4+ .note.info { @extend %note-blue; }
5+ .note.warning { @extend %note-red; }
6+ .note.unreleased { @extend %note-orange; }
7+}
assets/stylesheets/pages/_jquery.scss+2−2
@@ -32,7 +32,7 @@ · 31 unmodified lines
3232
3333 .toc-list {
3434 margin-top: 0;
35− font-weight: $boldFontWeight;
35+ font-weight: var(--boldFontWeight);
3636
3737 > li + li { margin-top: 1em; }
3838 > li > ul { font-weight: normal; }
@@ -48,7 +48,7 @@ · 9 unmodified lines
4848 .returns,
4949 .option-type {
5050 float: right;
51− font-weight: $boldFontWeight;
51+ font-weight: var(--boldFontWeight);
5252 margin-left: 1em;
5353 }
5454
assets/stylesheets/pages/_love.scss+2−2
@@ -12,6 +12,6 @@ · 11 unmodified lines
1212 .smwtable { width: 100%; }
1313 .smwtable td:nth-last-child(2), .smwtable td:last-child { width: 2.5em; }
1414
15− .cell-green { background: $noteGreenBackground; }
16− .cell-red { background: $noteRedBackground; }
15+ .cell-green { background: var(--noteGreenBackground); }
16+ .cell-red { background: var(--noteRedBackground); }
1717 }
assets/stylesheets/pages/_mdn.scss+28−3
@@ -7,7 +7,7 @@ · 6 unmodified lines
77 > span {
88 display: block;
99 font-size: 1rem;
10− font-weight: $boldFontWeight;
10+ font-weight: var(--boldFontWeight);
1111 }
1212
1313 ul, ol {
@@ -30,6 +30,7 @@ · 16 unmodified lines
3030 .notice,
3131 .warning,
3232 .overheadIndicator,
33+ .blockIndicator,
3334 .syntaxbox, // CSS, JavaScript
3435 .twopartsyntaxbox, // CSS
3536 .inheritsbox, // JavaScript
@@ -42,7 +43,7 @@ · 6 unmodified lines
4243 > .note {
4344 em {
4445 font-style: normal;
45− font-weight: $boldFontWeight;
46+ font-weight: var(--boldFontWeight);
4647 }
4748
4849 > ul { margin: 1em 0; }
@@ -93,7 +94,7 @@ · 44 unmodified lines
9394 dt > strong > code, // HTML element attribute
9495 dl > dt > code { // CSS property value, Javascript function argument
9596 font-family: inherit;
96− font-weight: $boldFontWeight;
97+ font-weight: var(--boldFontWeight);
9798 font-size: inherit;
9899 }
99100
@@ -104,4 +105,28 @@ · 4 unmodified lines
104105 .cleared { clear: both; } // CSS/box-shadow
105106
106107 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+ }
107132 }
assets/stylesheets/pages/_meteor.scss+2−2
@@ -9,10 +9,10 @@ · 8 unmodified lines
99 dt > code { @extend %label; }
1010
1111 .api-heading { overflow: hidden; }
12− .api-heading > code { font-weight: $boldFontWeight; }
12+ .api-heading > code { font-weight: var(--boldFontWeight); }
1313 .locus, .src-code { float: right; }
1414 .locus, .type, .src-code { margin-left: .5em; }
1515 h2 .subtext-api { margin-top: .25rem; }
1616 .locus, .subtext-api, .subtext-api > code { font-size: .75rem; }
17− .locus, .type { color: $textColorLight; }
17+ .locus, .type { color: var(--textColorLight); }
1818 }
assets/stylesheets/pages/_node.scss+3−0
@@ -20,5 +20,8 @@ · 19 unmodified lines
2020 margin: 0 0 1em 1em;
2121 @extend %label;
2222 }
23+
24+ .srclink { float: right; }
25+ details > table { margin: 0; }
2326 }
2427
assets/stylesheets/pages/_npm.scss+1−1
@@ -8,7 +8,7 @@ · 7 unmodified lines
88
99 .faint.heading {
1010 font-size: .9em;
11− color: $textColorLight;
11+ color: var(--textColorLight);
1212 }
1313
1414 .youtube-video iframe { width: 420px; height: 315px; }
assets/stylesheets/pages/_phalcon.scss+1−1
@@ -3,6 +3,6 @@ · 2 unmodified lines
33
44 h3 > small {
55 float: right;
6− color: $textColorLight;
6+ color: var(--textColorLight);
77 }
88 }
assets/stylesheets/pages/_php.scss+2−2
@@ -13,7 +13,7 @@ · 12 unmodified lines
1313
1414 .verinfo {
1515 float: right;
16− font-weight: $boldFontWeight;
16+ font-weight: var(--boldFontWeight);
1717 }
1818
1919 .classsynopsis,
@@ -21,7 +21,7 @@ · 1 unmodified line
2121 .description > .methodsynopsis,
2222 .description > .fieldsynopsis { @extend %note, %note-blue; }
2323
24− .classsynopsisinfo_comment { color: $textColorLight; }
24+ .classsynopsisinfo_comment { color: var(--textColorLight); }
2525
2626 .classsynopsisinfo_comment,
2727 .classsynopsis > .constructorsynopsis,
assets/stylesheets/pages/_postgres.scss+12−7
@@ -1,19 +1,24 @@
11 ._postgres {
22 padding-left: 1rem;
33
4− h1, h1 ~ p, h1 ~ pre, h1 ~ blockquote, h2, .NAVFOOTER { margin-left: -1rem; }
4+ h1, h1 ~ p, h1 ~ pre, h1 ~ ul, h1 ~ blockquote, h2, .navfooter { margin-left: -1rem; }
55 h2 { @extend %block-heading; }
66
7− .VARIABLELIST dt { @extend %block-label, %label-blue; }
7+ .variablelist dt { @extend %block-label, %label-blue; }
88
9− blockquote.NOTE, blockquote.IMPORTANT, blockquote.TIP, blockquote.CAUTION { @extend %note; }
10− blockquote.TIP { @extend %note-green; }
11− blockquote.CAUTION { @extend %note-orange; }
9+ blockquote.note, blockquote.important, blockquote.tip, blockquote.caution { @extend %note; }
10+ blockquote.tip { @extend %note-green; }
11+ blockquote.caution { @extend %note-orange; }
12+
13+ blockquote > h3 {
14+ font-size: .875rem;
15+ margin: 0 0 .25rem;
16+ }
1217
1318 p > code { @extend %label; }
14− p.c2 { font-weight: $boldFontWeight; }
19+ p.c2 { font-weight: var(--boldFontWeight); }
1520
16− .NAVFOOTER > table { width: 100%; }
21+ .navfooter > table { width: 100%; }
1722 td[align=center] { text-align: center; }
1823 td[align=right] { text-align: right; }
1924 }
assets/stylesheets/pages/_python.scss+6−0
@@ -0,0 +1,6 @@
1+._python {
2+ @extend %sphinx;
3+
4+ h2 > a, h3 > a, dt[id] > a.external, dt[id] > a.internal { float: none !important; }
5+ }
6+
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+6−13
@@ -6,7 +6,7 @@ · 5 unmodified lines
66 .method-description > h2, h3, h4, h5, h6 { font-size: 1em; }
77
88 .method-heading {
9− font-weight: $boldFontWeight;
9+ font-weight: var(--boldFontWeight);
1010 @extend %block-label, %label-blue;
1111
1212 + .method-heading { margin-top: -.5em; }
@@ -22,26 +22,19 @@ · 9 unmodified lines
2222 a.method-click-advice {
2323 float: right;
2424 font-size: .75rem;
25− color: $linkColor;
25+ color: var(--linkColor);
2626 cursor: pointer;
2727 @extend %user-select-none;
2828
2929 &:hover { text-decoration: underline; }
30− }
3130
32− .method-description { position: relative; }
31+ @include print {
32+ display: none;
33+ }
34+ }
3335
3436 .method-source-code {
3537 display: none;
36− position: absolute;
37− z-index: 1;
38− top: 0;
39− left: -1em;
40− right: 0;
41− background: rgba($contentBackground, .95);
42− box-shadow: 0 1em 1em 1em $contentBackground;
43−
44− > pre { margin: 0; }
4538 }
4639
4740 // Rails guides
assets/stylesheets/pages/_react_native.scss+1−1
@@ -2,7 +2,7 @@ · 1 unmodified line
22 @extend %simple;
33
44 .deprecated { @extend %note, %note-orange; }
5− .deprecatedTitle { font-weight: $boldFontWeight; }
5+ .deprecatedTitle { font-weight: var(--boldFontWeight); }
66
77 span.platform { float: right; }
88 span.propType, span.platform { font-weight: normal; }
assets/stylesheets/pages/_redis.scss+2−2
@@ -14,7 +14,7 @@ · 13 unmodified lines
1414
1515 .args {
1616 font-size: .75rem;
17− color: $textColorLight;
17+ color: var(--textColorLight);
1818 }
1919
2020 // Others
@@ -35,7 +35,7 @@ · 14 unmodified lines
3535 > .prompt {
3636 float: left;
3737 margin-right: .5em;
38− color: $textColorLight;
38+ color: var(--textColorLight);
3939 }
4040
4141 > code {
assets/stylesheets/pages/_rfc.scss+1−1
@@ -10,6 +10,6 @@ · 9 unmodified lines
1010
1111 > h1, > h2, > h3, > h4, > h5 {
1212 margin: 0;
13− font-family: $baseFont;
13+ font-family: var(--baseFont);
1414 }
1515 }
assets/stylesheets/pages/_rust.scss+10−1
@@ -3,9 +3,18 @@ · 2 unmodified lines
33
44 h4 { @extend %block-label; }
55 .docblock { margin-left: 1em; }
6+ div.information, div.important-traits {
7+ @extend %note;
8+
9+ > pre { margin: .5rem 0; }
10+ }
611
712 div.stability { margin-bottom: 1em; }
813 em.stab, span.stab { @extend %label; }
914 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+ }
1120 }
assets/stylesheets/pages/_simple.scss+1−31
@@ -17,34 +17,4 @@ · 16 unmodified lines
1717 blockquote > h4, blockquote > h5 { margin-top: .25rem; }
1818 }
1919
20−._apache_pig,
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−}
20+._simple { @extend %simple; }
assets/stylesheets/pages/_sphinx.scss+3−2
@@ -10,10 +10,11 @@ · 9 unmodified lines
1010
1111 .important { @extend %note-orange; }
1212 .warning, .deprecated-removed, .deprecated { @extend %note-red; }
13+ .hint { @extend %note-green; }
1314
1415 .versionmodified, span.title, .topic-title {
1516 display: block;
16− font-weight: $boldFontWeight;
17+ font-weight: var(--boldFontWeight);
1718 }
1819
1920 p > code, li > code, dd > code, .docutils > dt > code { @extend %label; }
@@ -25,7 +26,7 @@ · 5 unmodified lines
2526 .admonition-title {
2627 float: left;
2728 margin: 0 .5em 0 0;
28− font-weight: $boldFontWeight;
29+ font-weight: var(--boldFontWeight);
2930
3031 &:after { content: ':'; }
3132 }
assets/stylesheets/pages/_sphinx_simple.scss+1−1
@@ -6,7 +6,7 @@ · 5 unmodified lines
66 .admonition.tip { @extend %note-green; }
77 .admonition-title {
88 margin: 0 0 .25rem;
9− font-weight: $boldFontWeight;
9+ font-weight: var(--boldFontWeight);
1010 }
1111
1212 code { @extend %label; }
assets/stylesheets/pages/_support_tables.scss+1−1
@@ -24,7 +24,7 @@ · 23 unmodified lines
2424 }
2525
2626 tr.current {
27− font-weight: $boldFontWeight;
27+ font-weight: var(--boldFontWeight);
2828 font-size: 1rem;
2929 }
3030
assets/stylesheets/pages/_terraform.scss+4−0
@@ -0,0 +1,4 @@
1+._terraform {
2+ @extend %simple;
3+ .note, .alert { @extend %note; }
4+}
assets/stylesheets/pages/_yard.scss+1−1
@@ -1,5 +1,5 @@
11 ._yard {
22 @extend %simple;
33
4− .tag_title { font-weight: $boldFontWeight; }
4+ .tag_title { font-weight: var(--boldFontWeight); }
55 }
assets/stylesheets/pages/_yii.scss+1−1
@@ -3,6 +3,6 @@ · 2 unmodified lines
33
44 .detail-header-tag, .detailHeaderTag {
55 float: right;
6− color: $textColorLight;
6+ color: var(--textColorLight);
77 }
88 }
docs/adding-docs.md+24−0
@@ -0,0 +1,24 @@
1+Adding 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+
5+1. 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`)
6+2. Add the appropriate class attributes and filter options (see the [Scraper Reference](./scraper-reference.md) page).
7+3. Check that the scraper is listed in `thor docs:list`.
8+4. 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.
12+5. 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`).
13+6. 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).
14+7. Start the server, open the app, enable the documentation, and see how everything plays out.
15+8. Tweak the scraper/filters and repeat 5) and 6) until the pages and metadata are ok.
16+9. 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.
17+10. 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).
18+11. 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.
19+12. 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.
20+13. Ensure `thor updates:check [my_doc]` shows the correct latest version.
21+
22+If 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+
24+Finally, 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+
12+Filters 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+
14+Filters 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
17+module Docs
18+ class CustomFilter < Filter
19+ def call
20+ doc
21+ end
22+ end
23+end
24+```
25+
26+Filters 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+
28+The `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+
90+Scrapers 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+
96+The `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+
98+Nokogiri'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+
100+Here's an example implementation that covers the most common use-cases:
101+
102+```ruby
103+module 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
129+end
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+
140+The `Entries` filter is responsible for extracting the page's metadata, represented by a set of _entries_, each with a name, type and path.
141+
142+The 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+
147+Each 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+
171+The 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
191+module 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
220+end
221+```
222+
223+
224+return [[Home]]
docs/maintainers.md+102−0
@@ -0,0 +1,102 @@
1+# Maintainer's Guide
2+
3+This 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+
15+The 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+
27+Note: 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+
31+In 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+
50+In 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+
84+Once 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+
90+If 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+
92+If 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+
102+Interested 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+
11+Starting 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+
13+Scrapers 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+
19+There 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+
21+To 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+
29+Each URL is requested only once (case-insensitive).
30+
31+## Configuration
32+
33+Configuration 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+
84+Each 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+
86+HTML 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+
88+Filter stacks are like sorted sets. They can modified using the following methods:
89+
90+```ruby
91+push(*names) # append one or more filters at the end
92+insert_before(index, *names) # insert one filter before another (index can be a name)
93+insert_after(index, *names) # insert one filter after another (index can be a name)
94+replace(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+
99+Default `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+
108+Default `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+
114+Additionally:
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+
121+The filter options are stored in the `options` Hash. The Hash is inheritable (a recursive copy) and empty by default.
122+
123+More 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+
190+In 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+
192+To 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+51−47
@@ -12,6 +12,8 @@ class App < Sinatra::Application · 11 unmodified lines
1212 Rack::Mime::MIME_TYPES['.webapp'] = 'application/x-web-app-manifest+json'
1313
1414 configure do
15+ use Rack::SslEnforcer, only_environments: ['production', 'test'], hsts: true, force_secure_cookies: false
16+
1517 set :sentry_dsn, ENV['SENTRY_DSN']
1618 set :protection, except: [:frame_options, :xss_header]
1719
@@ -35,6 +37,9 @@ class App < Sinatra::Application · 17 unmodified lines
3537
3638 set :csp, false
3739
40+ require 'docs'
41+ Docs.generate_manifest
42+
3843 Dir[docs_path, root.join(assets_prefix, '*/')].each do |path|
3944 sprockets.append_path(path)
4045 end
@@ -48,6 +53,11 @@ class App < Sinatra::Application · 7 unmodified lines
4853 end
4954
5055 configure :test, :development do
56+ require 'thor'
57+ load 'tasks/sprites.thor'
58+
59+ SpritesCLI.new.invoke(:generate)
60+
5161 require 'active_support/per_thread_registry'
5262 require 'active_support/cache'
5363 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
6777 set :static, false
6878 set :cdn_origin, 'https://cdn.devdocs.io'
6979 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:;"
7181
7282 use Rack::ConditionalGet
7383 use Rack::ETag
@@ -133,7 +143,7 @@ class App < Sinatra::Application · 59 unmodified lines
133143 end
134144
135145 def canonical_origin
136− "http://#{request.host_with_port}"
146+ "https://#{request.host_with_port}"
137147 end
138148
139149 def browser
@@ -190,51 +200,45 @@ class App < Sinatra::Application · 50 unmodified lines
190200 request.query_string.empty? ? nil : "?#{request.query_string}"
191201 end
192202
193− def manifest_asset_urls
194− @@manifest_asset_urls ||= [
203+ def service_worker_asset_urls
204+ @@service_worker_asset_urls ||= [
195205 javascript_path('application', asset_host: false),
196206 stylesheet_path('application'),
197− stylesheet_path('application-dark'),
198− image_path('docs-1.png'),
199− image_path('docs-1@2x.png'),
200− image_path('docs-2.png'),
201− image_path('docs-2@2x.png'),
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]
207+ image_path('sprites/docs.png'),
208+ image_path('sprites/docs@2x.png'),
209+ asset_path('docs.js'),
210+ App.production? ? nil : javascript_path('debug'),
211+ ].compact
212212 end
213213
214− def stylesheet_paths
215− @@stylesheet_paths ||= {
216− default: stylesheet_path('application'),
217− dark: stylesheet_path('application-dark')
218− }
219− end
220−
221− def app_size
222− @app_size ||= memoized_cookies['size'].nil? ? '20rem' : "#{memoized_cookies['size']}px"
223− end
214+ # Returns a cache name for the service worker to use which changes if any of the assets changes
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+ # If a manifest does not exist, it is created every time this method is called because the assets can change while the server is running
217+ def service_worker_cache_name
218+ if File.exist?(App.assets_manifest_path)
219+ if defined?(@@service_worker_cache_name)
220+ return @@service_worker_cache_name
221+ end
224222
225− def app_layout
226− memoized_cookies['layout']
227− end
223+ digest = Sprockets::Manifest
224+ .new(nil, App.assets_manifest_path)
225+ .files
226+ .values
227+ .map {|file| file["digest"]}
228+ .join
228229
229− def app_theme
230− @app_theme ||= memoized_cookies['dark'].nil? ? 'default' : 'dark'
231− end
230+ return @@service_worker_cache_name ||= Digest::MD5.hexdigest(digest)
231+ else
232+ paths = App.sprockets
233+ .each_file
234+ .to_a
235+ .reject {|file| file.start_with?(App.docs_path)}
232236
233− def dark_theme?
234− app_theme == 'dark'
237+ return App.sprockets.pack_hexdigest(App.sprockets.files_digest(paths))
238+ end
235239 end
236240
237− def redirect_via_js(path) # courtesy of HTML5 App Cache
241+ def redirect_via_js(path)
238242 response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/'
239243 redirect '/', 302
240244 end
@@ -253,19 +257,19 @@ class App < Sinatra::Application · 12 unmodified lines
253257 before do
254258 if request.host == OUT_HOST && !request.path.start_with?('/s/')
255259 query_string = "?#{request.query_string}" unless request.query_string.empty?
256− redirect "http://devdocs.io#{request.path}#{query_string}", 302
260+ redirect "https://devdocs.io#{request.path}#{query_string}", 302
257261 end
258262 end
259263
260− get '/manifest.appcache' do
261− content_type 'text/cache-manifest'
264+ get '/service-worker.js' do
265+ content_type 'application/javascript'
262266 expires 0, :'no-cache'
263− erb :manifest
267+ erb :'service-worker.js'
264268 end
265269
266270 get '/' do
267271 return redirect "/#q=#{params[:q]}" if params[:q]
268− return redirect '/' unless request.query_string.empty? # courtesy of HTML5 App Cache
272+ return redirect '/' unless request.query_string.empty?
269273 response.headers['Content-Security-Policy'] = settings.csp if settings.csp
270274 erb :index
271275 end
@@ -435,13 +439,13 @@ class App < Sinatra::Application · 163 unmodified lines
435439
436440 maker.channel.links.new_link do |link|
437441 link.rel = 'self'
438− link.href = 'http://devdocs.io/feed.atom'
442+ link.href = 'https://devdocs.io/feed.atom'
439443 link.type = 'application/atom+xml'
440444 end
441445
442446 maker.channel.links.new_link do |link|
443447 link.rel = 'alternate'
444− link.href = 'http://devdocs.io/'
448+ link.href = 'https://devdocs.io/'
445449 link.type = 'text/html'
446450 end
447451
@@ -450,14 +454,14 @@ class App < Sinatra::Application · 2 unmodified lines
450454 item.id = "tag:devdocs.io,2014:News/#{settings.news.length - i}"
451455 item.title = news[1].split("\n").first.gsub(/<\/?[^>]*>/, '')
452456 item.description do |desc|
453− desc.content = news[1..-1].join.gsub("\n", '<br>').gsub('href="/', 'href="http://devdocs.io/')
457+ desc.content = news[1..-1].join.gsub("\n", '<br>').gsub('href="/', 'href="https://devdocs.io/')
454458 desc.type = 'html'
455459 end
456460 item.updated = "#{news.first}T14:00:00Z"
457461 item.published = "#{news.first}T14:00:00Z"
458462 item.links.new_link do |link|
459463 link.rel = 'alternate'
460− link.href = 'http://devdocs.io/'
464+ link.href = 'https://devdocs.io/'
461465 link.type = 'text/html'
462466 end
463467 end
lib/docs.rb+18−1
@@ -1,5 +1,5 @@
11 require 'bundler/setup'
2−Bundler.require :docs
2+Bundler.require :default, :docs
33
44 require 'active_support'
55 require 'active_support/core_ext'
@@ -29,6 +29,7 @@ module Docs · 23 unmodified lines
2929 self.rescue_errors = false
3030
3131 class DocNotFound < NameError; end
32+ class SetupError < StandardError; end
3233
3334 def self.all
3435 Dir["#{root_path}/docs/scrapers/**/*.rb"].
@@ -74,6 +75,22 @@ module Docs · 39 unmodified lines
7475 end
7576 end
7677
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+
7794 def self.generate_page(name, version, page_id)
7895 find(name, version).store_page(store, page_id)
7996 end
lib/docs/core/doc.rb+118−0
@@ -2,6 +2,7 @@ module Docs · 1 unmodified line
22 class Doc
33 INDEX_FILENAME = 'index.json'
44 DB_FILENAME = 'db.json'
5+ META_FILENAME = 'meta.json'
56
67 class << self
78 include Instrumentable
@@ -73,6 +74,10 @@ module Docs · 65 unmodified lines
7374 File.join path, DB_FILENAME
7475 end
7576
77+ def meta_path
78+ File.join path, META_FILENAME
79+ end
80+
7681 def as_json
7782 json = { name: name, slug: slug, type: type }
7883 json[:links] = links if links.present?
@@ -90,6 +95,9 @@ module Docs · 11 unmodified lines
9095 false
9196 end
9297 end
98+ rescue Docs::SetupError => error
99+ puts "ERROR: #{error.message}"
100+ false
93101 end
94102
95103 def store_pages(store)
@@ -107,11 +115,15 @@ module Docs · 11 unmodified lines
107115 if index.present?
108116 store_index(store, INDEX_FILENAME, index)
109117 store_index(store, DB_FILENAME, pages)
118+ store_meta(store)
110119 true
111120 else
112121 false
113122 end
114123 end
124+ rescue Docs::SetupError => error
125+ puts "ERROR: #{error.message}"
126+ false
115127 end
116128
117129 private
@@ -131,6 +143,13 @@ module Docs · 13 unmodified lines
131143 instrument "#{filename.remove('.json')}.doc", before: old_json, after: new_json
132144 store.write(filename, new_json)
133145 end
146+
147+ def store_meta(store)
148+ json = as_json
149+ json[:mtime] = Time.now.to_i
150+ json[:db_size] = store.size(DB_FILENAME)
151+ store.write(META_FILENAME, json.to_json)
152+ end
134153 end
135154
136155 def initialize
@@ -144,5 +163,104 @@ module Docs · 7 unmodified lines
144163 def build_pages(&block)
145164 raise NotImplementedError
146165 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
147265 end
148266 end
lib/docs/core/filter.rb+3−12
@@ -91,19 +91,10 @@ module Docs · 90 unmodified lines
9191 super
9292 end
9393
94− def decode_cloudflare_email(str)
95− mask = "0x#{str[0..1]}".hex | 0
96− result = ''
97−
98− str.chars.drop(2).each_slice(2) do |slice|
99− result += "%" + "0#{("0x#{slice.join}".hex ^ mask).to_s(16)}"[-2..-1]
100− end
101−
102− URI.decode(result)
103− end
104−
10594 def clean_path(path)
106− path.gsub %r{[!;:]+}, '-'
95+ path = path.gsub %r{[!;:]}, '-'
96+ path = path.gsub %r{\+}, '_plus_'
97+ path
10798 end
10899 end
109100 end
lib/docs/core/manifest.rb+4−22
@@ -14,32 +14,14 @@ module Docs · 13 unmodified lines
1414 end
1515
1616 def as_json
17− indexed_docs.map do |doc|
18− json = doc.as_json
19− json[:mtime] = doc_mtime(doc)
20− json[:db_size] = doc_db_size(doc)
21− json
17+ @docs.each_with_object [] do |doc, result|
18+ next unless @store.exist?(doc.meta_path)
19+ result << JSON.parse(@store.read(doc.meta_path))
2220 end
2321 end
2422
2523 def to_json
26− JSON.generate(as_json)
27− end
28−
29− private
30−
31− def indexed_docs
32− @docs.select do |doc|
33− @store.exist?(doc.index_path) && @store.exist?(doc.db_path)
34− end
35− end
36−
37− def doc_mtime(doc)
38− [@store.mtime(doc.index_path).to_i, @store.mtime(doc.db_path).to_i].max
39− end
40−
41− def doc_db_size(doc)
42− @store.size(doc.db_path)
24+ JSON.pretty_generate(as_json)
4325 end
4426 end
4527 end
lib/docs/core/requester.rb+8−4
@@ -1,5 +1,7 @@
11 module Docs
22 class Requester < Typhoeus::Hydra
3+ include Instrumentable
4+
35 attr_reader :request_options
46
57 def self.run(urls, options = {}, &block)
@@ -20,7 +22,7 @@ module Docs · 14 unmodified lines
2022 def initialize(options = {})
2123 @request_options = options.extract!(:request_options)[:request_options].try(:dup) || {}
2224 options[:max_concurrency] ||= 20
23− options[:pipelining] = false
25+ options[:pipelining] = 0
2426 super
2527 end
2628
@@ -52,9 +54,11 @@ module Docs · 25 unmodified lines
5254 end
5355
5456 def handle_response(response)
55− on_response.each do |callback|
56− result = callback.call(response)
57− result.each { |url| request(url) } if result.is_a? Array
57+ instrument 'handle_response.requester', url: response.url do
58+ on_response.each do |callback|
59+ result = callback.call(response)
60+ result.each { |url| request(url) } if result.is_a?(Array)
61+ end
5862 end
5963 end
6064 end
lib/docs/core/scraper.rb+1−1
@@ -41,7 +41,7 @@ module Docs · 40 unmodified lines
4141 self.html_filters = FilterStack.new
4242 self.text_filters = FilterStack.new
4343
44− html_filters.push 'apply_base_url', 'container', 'clean_html', 'normalize_urls', 'internal_urls', 'normalize_paths'
44+ html_filters.push 'apply_base_url', 'container', 'clean_html', 'normalize_urls', 'internal_urls', 'normalize_paths', 'parse_cf_email'
4545 text_filters.push 'images' # ensure the images filter runs after all html filters
4646 text_filters.push 'inner_html', 'clean_text', 'attribution'
4747
lib/docs/core/scrapers/file_scraper.rb+21−7
@@ -1,14 +1,13 @@
11 module Docs
22 class FileScraper < Scraper
3+ SOURCE_DIRECTORY = File.expand_path '../../../../../docs', __FILE__
4+
35 Response = Struct.new :body, :url
46
57 class << self
6− attr_accessor :dir
7−
88 def inherited(subclass)
99 super
1010 subclass.base_url = base_url
11− subclass.dir = dir
1211 end
1312 end
1413
@@ -16,13 +15,25 @@ module Docs · 1 unmodified line
1615
1716 html_filters.push 'clean_local_urls'
1817
18+ def source_directory
19+ @source_directory ||= File.join(SOURCE_DIRECTORY, self.class.path)
20+ end
21+
1922 private
2023
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+
2130 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)
2333 end
2434
2535 def request_all(urls)
36+ assert_source_directory_exists
2637 queue = [urls].flatten
2738 until queue.empty?
2839 result = yield request_one(queue.shift)
@@ -34,12 +45,15 @@ module Docs · 5 unmodified lines
3445 response.body.present?
3546 end
3647
37− def file_path_for(url)
38− File.join self.class.dir, url.remove(base_url.to_s)
48+ def url_to_path(url)
49+ url.remove(base_url.to_s)
3950 end
4051
4152 def read_file(path)
42− File.read(path) rescue nil
53+ File.read(path)
54+ rescue
55+ instrument 'warn.doc', msg: "Failed to open file: #{path}"
56+ nil
4357 end
4458 end
4559 end
lib/docs/core/scrapers/url_scraper.rb+46−1
@@ -13,6 +13,8 @@ module Docs · 12 unmodified lines
1313 end
1414 end
1515
16+ @@rate_limiter = nil
17+
1618 self.params = {}
1719 self.headers = { 'User-Agent' => 'DevDocs' }
1820 self.force_gzip = false
@@ -24,6 +26,15 @@ module Docs · 5 unmodified lines
2426 end
2527
2628 def request_all(urls, &block)
29+ if options[:rate_limit]
30+ if @@rate_limiter
31+ @@rate_limiter.limit = options[:rate_limit]
32+ else
33+ @@rate_limiter = RateLimiter.new(options[:rate_limit])
34+ Typhoeus.before(&@@rate_limiter.to_proc)
35+ end
36+ end
37+
2738 Requester.run urls, request_options: request_options, &block
2839 end
2940
@@ -35,7 +46,11 @@ module Docs · 5 unmodified lines
3546
3647 def process_response?(response)
3748 if response.error?
38− raise "Error status code (#{response.code}): #{response.return_message}\n#{response.url}"
49+ raise <<~ERROR
50+ Error status code (#{response.code}): #{response.return_message}
51+ #{response.url}
52+ #{JSON.pretty_generate(response.headers).slice(2..-3)}
53+ ERROR
3954 elsif response.blank?
4055 raise "Empty response body: #{response.url}"
4156 end
@@ -161,5 +176,35 @@ module Docs · 119 unmodified lines
161176 super.merge! redirections: self.class.redirections
162177 end
163178 end
179+
180+ class RateLimiter
181+ attr_accessor :limit
182+
183+ def initialize(limit)
184+ @limit = limit
185+ @minute = nil
186+ @counter = 0
187+ end
188+
189+ def call(*)
190+ if @minute != Time.now.min
191+ @minute = Time.now.min
192+ @counter = 0
193+ end
194+
195+ @counter += 1
196+
197+ if @counter >= @limit
198+ wait = Time.now.end_of_minute.to_i - Time.now.to_i + 1
199+ sleep wait
200+ end
201+
202+ true
203+ end
204+
205+ def to_proc
206+ method(:call).to_proc
207+ end
208+ end
164209 end
165210 end
lib/docs/core/url.rb+1−1
@@ -72,7 +72,7 @@ module Docs · 71 unmodified lines
7272
7373 if base == dest
7474 ''
75− elsif dest.start_with? File.join(base, '')
75+ elsif dest.start_with?(::File.join(base, ''))
7676 url.path[(path.length)..-1]
7777 end
7878 end
lib/docs/filters/angular/clean_html.rb+9−1
@@ -7,7 +7,7 @@ module Docs · 6 unmodified lines
77 at_css('h1').content = 'Angular Documentation'
88 end
99
10− css('br', 'hr', '.material-icons', '.header-link').remove
10+ css('br', 'hr', '.material-icons', '.header-link', '.breadcrumb').remove
1111
1212 css('.content', 'article', '.api-header', 'section', '.instance-member').each do |node|
1313 node.before(node.children).remove
@@ -86,6 +86,14 @@ module Docs · 72 unmodified lines
8686 at_css('h1').content = subpath.remove('api/')
8787 end
8888
89+ css('th h3').each do |node|
90+ node.name = 'span'
91+ end
92+
93+ css('code code').each do |node|
94+ node.before(node.children).remove
95+ end
96+
8997 doc
9098 end
9199 end
lib/docs/filters/angular/entries.rb+2−0
@@ -16,6 +16,8 @@ module Docs · 15 unmodified lines
1616 node.next_element.content.remove('@angular/')
1717 elsif at_css('.api-type-label.module')
1818 name.split('/').first
19+ elsif slug.start_with?('api/')
20+ slug.split('/').second
1921 else
2022 'Miscellaneous'
2123 end
lib/docs/filters/ansible/clean_html.rb+2−2
@@ -4,8 +4,8 @@ module Docs · 3 unmodified lines
44 def call
55 @doc = at_css('#page-content')
66
7− css('.__cf_email__').each do |node|
8− node.replace(decode_cloudflare_email(node['data-cfemail']))
7+ css('font').each do |node|
8+ node.before(node.children).remove
99 end
1010
1111 doc
lib/docs/filters/ansible/entries.rb+27−3
@@ -11,14 +11,38 @@ module Docs · 10 unmodified lines
1111 end
1212
1313 def get_type
14− if slug.include?('module')
15− if name =~ /\A[a-z]/ && node = css('.toctree-l2.current').last
16− "Modules: #{node.content.remove(' Modules')}"
14+ if version == '2.4'
15+ if slug.include?('module')
16+ if name =~ /\A[a-z]/ && node = css('.toctree-l2.current').last
17+ return "Modules: #{node.content.remove(' Modules')}"
18+ else
19+ return 'Modules'
20+ end
21+ end
22+ end
23+
24+ if slug =~ /\Acli\//
25+ 'CLI Reference'
26+ elsif slug =~ /\Anetwork\//
27+ 'Network'
28+ elsif slug =~ /\Aplugins\//
29+ if name =~ /\A[a-z]/ && node = css('.toctree-l3.current').last
30+ "Plugins: #{node.content.sub(/ Plugins.*/, '')}"
31+ else
32+ 'Plugins'
33+ end
34+ elsif slug =~ /\Amodules\//
35+ if slug =~ /\Amodules\/list_/ || slug=~ /_maintained\z/
36+ 'Modules: Categories'
1737 else
1838 'Modules'
1939 end
2040 elsif slug.include?('playbook')
2141 'Playbooks'
42+ elsif slug =~ /\Auser_guide\//
43+ 'Guides: User'
44+ elsif slug =~ /\Ascenario_guides\//
45+ 'Guides: Scenarios'
2246 elsif slug.include?('guide')
2347 'Guides'
2448 else
lib/docs/filters/babel/clean_html.rb+38−0
@@ -0,0 +1,38 @@
1+module Docs
2+ class Babel
3+ class CleanHtmlFilter < Filter
4+ def call
5+ if root_page?
6+ doc.inner_html = '<h1>Babel</h1>'
7+ return doc
8+ end
9+
10+ header = at_css('.docs-header .col-md-12')
11+ @doc = at_css('.docs-content')
12+ doc.prepend_child(header)
13+
14+ css('.btn-clipboard', '.package-links').remove
15+
16+ css('.col-md-12', 'h1 a', 'h2 a', 'h3 a', 'h4 a', 'h5 a', 'h5 a').each do |node|
17+ node.before(node.children).remove
18+ end
19+
20+ css('div.highlighter-rouge').each do |node|
21+ pre = node.at_css('pre')
22+
23+ lang = node['class'][/language-(\w+)/, 1]
24+ lang = 'bash' if lang == 'sh'
25+ pre['data-language'] = lang
26+
27+ pre.remove_attribute('class')
28+ pre.content = pre.content
29+ node.replace(pre)
30+ end
31+
32+ css('code').remove_attr('class')
33+
34+ doc
35+ end
36+ end
37+ end
38+end
lib/docs/filters/babel/entries.rb+40−0
@@ -0,0 +1,40 @@
1+module Docs
2+ class Babel
3+ class EntriesFilter < Docs::EntriesFilter
4+ def get_name
5+ at_css('h1').content
6+ end
7+
8+ def get_type
9+ if subpath.start_with?('plugins/preset')
10+ 'Presets'
11+ elsif subpath.start_with?('plugins/transform')
12+ 'Transform Plugins'
13+ elsif subpath.start_with?('plugins/minify')
14+ 'Minification'
15+ elsif subpath.start_with?('plugins/syntax')
16+ 'Syntax Plugins'
17+ elsif subpath.start_with?('plugins')
18+ 'Plugins'
19+ elsif subpath.start_with?('usage/')
20+ 'Usage'
21+ elsif subpath.start_with?('core-packages/')
22+ 'Core Packages'
23+ else
24+ 'Miscellaneous'
25+ end
26+ end
27+
28+ def additional_entries
29+ return [] unless slug.include?('api')
30+
31+ css('h2').each_with_object [] do |node, entries|
32+ name = node.content.strip
33+ next unless name.start_with?('babel.')
34+ name.sub! %r{\(.*}, '()'
35+ entries << [name, node['id']]
36+ end
37+ end
38+ end
39+ end
40+end
lib/docs/filters/bash/clean_html.rb+74−0
@@ -0,0 +1,74 @@
1+module 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
74+end
lib/docs/filters/bash/entries.rb+56−0
@@ -0,0 +1,56 @@
1+module 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
56+end
lib/docs/filters/bluebird/clean_html.rb+36−0
@@ -0,0 +1,36 @@
1+module Docs
2+ class Bluebird
3+ class CleanHtmlFilter < Filter
4+ def call
5+ @doc = at_css('.post')
6+
7+ css('hr').remove
8+
9+ css('.api-code-section').each do |node|
10+ node.previous_element.remove
11+ end
12+
13+ css('.post-header', '.post-content', '.api-reference-menu', '.api-code-section', 'markdown', '.highlight', 'code code').each do |node|
14+ node.before(node.children).remove
15+ end
16+
17+ at_css('> h2:first-child').name = 'h1' unless at_css('h1')
18+
19+ css('.header-anchor[name]').each do |node|
20+ node.parent['id'] = node['name']
21+ end
22+
23+ css('pre').each do |node|
24+ node.content = node.content
25+ node['data-language'] = 'javascript'
26+ end
27+
28+ css('.info-box').each do |node|
29+ node.name = 'blockquote'
30+ end
31+
32+ doc
33+ end
34+ end
35+ end
36+end
lib/docs/filters/bluebird/entries.rb+48−0
@@ -0,0 +1,48 @@
1+module Docs
2+ class Bluebird
3+ class EntriesFilter < Docs::EntriesFilter
4+ TYPE_MAP = {
5+ Core: %w(new-promise then spread catch error finally bind promise.join
6+ promise.try promise.method promise.resolve promise.reject core
7+ promise.bind),
8+ 'Synchronous inspection': %w(promiseinspection isfulfilled isrejected
9+ ispending iscancelled value reason),
10+ Collections: %w(promise.all promise.props promise.any promise.some
11+ promise.map promise.reduce promise.filter promise.each
12+ promise.mapseries promise.race all props any some map reduce filter
13+ each mapseries),
14+ 'Resource management': %w(promise.using disposer),
15+ Promisification: %w(promise.promisify promise.promisifyall
16+ promise.fromcallback ascallback),
17+ Timers: %w(delay timeout promise.delay),
18+ Cancellation: %w(cancel),
19+ Generators: %w(promise.coroutine promise.coroutine.addyieldhandler),
20+ Utility: %w(tap tapcatch call get return throw catchreturn catchthrow
21+ reflect promise.getnewlibrarycopy promise.noconflict
22+ promise.setscheduler),
23+ 'Built-in error types': %w(operationalerror timeouterror
24+ cancellationerror aggregateerror),
25+ Configuration: %w(global-rejection-events local-rejection-events
26+ done promise.config suppressunhandledrejections
27+ promise.onpossiblyunhandledrejection promise.bind
28+ promise.onunhandledrejectionhandled),
29+ }
30+
31+ def get_name
32+ name = at_css('h1').content.strip
33+ name << '()' if doc.to_html.include?("#{name}(")
34+ name
35+ end
36+
37+ def get_type
38+ if slug.start_with?('api')
39+ TYPE_MAP.each do |key, value|
40+ return key.to_s if value.include?(slug.remove('api/'))
41+ end
42+ else
43+ 'Guides'
44+ end
45+ end
46+ end
47+ end
48+end
lib/docs/filters/bootstrap/clean_html_v3.rb+1−5
@@ -27,7 +27,7 @@ module Docs · 26 unmodified lines
2727 node.remove_attribute('data-example-id')
2828 prev = node.previous_element
2929 prev = prev.previous_element until prev['id']
30− node.inner_html = %(<a href="#{current_url}/##{prev['id']}">Open example on getbootstrap.com</a>)
30+ node.inner_html = %(<a href="#{current_url}##{prev['id']}">Open example on getbootstrap.com</a>)
3131 end
3232 end
3333
@@ -39,10 +39,6 @@ module Docs · 5 unmodified lines
3939 node['class'] = 'col'
4040 end
4141
42− css('.__cf_email__').each do |node|
43− node.replace(decode_cloudflare_email(node['data-cfemail']))
44− end
45−
4642 css('figure.highlight').each do |node|
4743 code = node.at_css('code')
4844 node['data-language'] = code['data-lang']
lib/docs/filters/bootstrap/clean_html_v4.rb+3−5
@@ -17,6 +17,8 @@ module Docs · 16 unmodified lines
1717 end
1818
1919 css('.bd-example', '.responsive-utilities-test').each do |node|
20+ next unless node.previous_element
21+
2022 if node.previous_element['class'].try(:include?, 'bd-example')
2123 node.remove
2224 else
@@ -26,7 +28,7 @@ module Docs · 3 unmodified lines
2628 node.remove_attribute('data-example-id')
2729 prev = node.previous_element
2830 prev = prev.previous_element until prev['id']
29− node.inner_html = %(<a href="#{current_url}/##{prev['id']}">Open example on getbootstrap.com</a>)
31+ node.inner_html = %(<a href="#{current_url}##{prev['id']}">Open example on getbootstrap.com</a>)
3032 end
3133 end
3234
@@ -38,10 +40,6 @@ module Docs · 5 unmodified lines
3840 node['class'] = 'col'
3941 end
4042
41− css('.__cf_email__').each do |node|
42− node.replace(decode_cloudflare_email(node['data-cfemail']))
43− end
44−
4543 css('.highlight').each do |node|
4644 code = node.at_css('code')
4745 node['data-language'] = code['data-lang']
lib/docs/filters/c/clean_html.rb+11−2
@@ -4,12 +4,17 @@ module Docs · 3 unmodified lines
44 def call
55 css('h1').remove if root_page?
66
7+ css('.t-dcl-rev-aux td[rowspan]').each do |node|
8+ rowspan = node['rowspan'].to_i
9+ node['rowspan'] = node.ancestors('tbody').css('tr').length if rowspan > 3
10+ end
11+
712 css('#siteSub', '#contentSub', '.printfooter', '.t-navbar', '.editsection', '#toc',
813 '.t-dsc-sep', '.t-dcl-sep', '#catlinks', '.ambox-notice', '.mw-cite-backlink',
914 '.t-sdsc-sep:first-child:last-child', '.t-example-live-link',
1015 '.t-dcl-rev-num > .t-dcl-rev-aux ~ tr:not(.t-dcl-rev-aux) > td:nth-child(2)').remove
1116
12− css('#bodyContent', '.mw-content-ltr', 'span[style]', 'div[class^="t-ref"]',
17+ css('#bodyContent', '.mw-content-ltr', 'span[style]', 'div[class^="t-ref"]', '.t-image',
1318 'th > div', 'td > div', '.t-dsc-see', '.mainpagediv', 'code > b', 'tbody').each do |node|
1419 node.before(node.children).remove
1520 end
@@ -78,7 +83,7 @@ module Docs · 62 unmodified lines
7883 end
7984 node.inner_html = node.inner_html.strip
8085 node << '.' if node.content =~ /[a-zA-Z0-9\)]\z/
81− node.remove if node.content.blank?
86+ node.remove if node.content.blank? && !node.at_css('img')
8287 end
8388
8489 css('pre').each do |node|
@@ -100,6 +105,10 @@ module Docs · 15 unmodified lines
100105 node.content = node.content
101106 end
102107
108+ css('img').each do |node|
109+ node['src'] = node['src'].sub! %r{http://en.cppreference.com/common/([^"']+?)\.svg}, 'http://upload.cppreference.com/mwiki/\1.svg'
110+ end
111+
103112 doc
104113 end
105114 end
lib/docs/filters/cmake/entries.rb+1−0
@@ -20,6 +20,7 @@ module Docs · 19 unmodified lines
2020 'prop_sf' => 'Properties: Source Files',
2121 'prop_test' => 'Properties: Tests',
2222 'prop_tgt' => 'Properties: Targets',
23+ 'envvar' => 'Environment Variables',
2324 'variable' => 'Variables' }
2425
2526 def get_name
lib/docs/filters/codeception/clean_html.rb+6−0
@@ -4,6 +4,12 @@ module Docs · 3 unmodified lines
44 def call
55 @doc = doc.at_css('#page')
66
7+ css('.algolia__search-content').remove
8+
9+ css('.algolia__initial-content').each do |node|
10+ node.before(node.children).remove
11+ end
12+
713 while doc.element_children.length == 1
814 doc.first_element_child.before(doc.first_element_child.children).remove
915 end
lib/docs/filters/codeception/entries.rb+1−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class Codeception
33 class EntriesFilter < Docs::EntriesFilter
44 def get_name
5− name = (at_css('h1') || at_css('h2')).content
5+ name = (at_css('.js-algolia__initial-content h1') || at_css('.js-algolia__initial-content h2')).content
66
77 if number = subpath[/\A\d+/]
88 name.prepend "#{number.to_i}. "
lib/docs/filters/codeceptjs/entries.rb+2−2
@@ -20,8 +20,8 @@ module Docs · 19 unmodified lines
2020 def additional_entries
2121 return [] unless subpath.start_with?('helpers') && subpath != 'helpers/'
2222
23− css('h2').each_with_object [] do |node, entries|
24− next if node['id'] == 'access-from-helpers'
23+ css('h2, h3').each_with_object [] do |node, entries|
24+ next if node['id'] == 'access-from-helpers' || node.content !~ /\s*[a-z_]/
2525 entries << ["#{node.content} (#{name})", node['id']]
2626 end
2727 end
lib/docs/filters/cordova/clean_html.rb+1−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class Cordova
33 class CleanHtmlFilter < Filter
44 def call
5− @doc = at_css('.page-content > div')
5+ @doc = at_css('#page-toc-source') || at_css('.page-content > div')
66
77 at_css('h1').content = 'Apache Cordova' if root_page?
88
lib/docs/filters/core/parse_cf_email.rb+21−0
@@ -0,0 +1,21 @@
1+# frozen_string_literal: true
2+
3+module Docs
4+ class ParseCfEmailFilter < Filter
5+ def call
6+ css('.__cf_email__').each do |node|
7+ str = node['data-cfemail']
8+ mask = "0x#{str[0..1]}".hex | 0
9+ result = ''
10+
11+ str.chars.drop(2).each_slice(2) do |slice|
12+ result += "%" + "0#{("0x#{slice.join}".hex ^ mask).to_s(16)}"[-2..-1]
13+ end
14+
15+ node.replace(URI.decode(result))
16+ end
17+
18+ doc
19+ end
20+ end
21+end
lib/docs/filters/cpp/entries.rb+2−0
@@ -31,6 +31,8 @@ module Docs · 30 unmodified lines
3131 'Keywords'
3232 elsif subpath.start_with?('experimental')
3333 'Experimental libraries'
34+ elsif subpath.start_with?('language/')
35+ 'Language'
3436 elsif type = at_css('.t-navbar > div:nth-child(4) > :first-child').try(:content)
3537 type.strip!
3638 type.remove! ' library'
lib/docs/filters/cpp/fix_urls.rb+0−1
@@ -4,7 +4,6 @@ module Docs · 3 unmodified lines
44 def call
55 html.gsub! File.join(Cpp.base_url, Cpp.root_path), Cpp.base_url[0..-2]
66 html.gsub! %r{#{Cpp.base_url}([^"']+?)\.html}, "#{Cpp.base_url}\\1"
7− html.gsub! %r{http://en.cppreference.com/common/([^"']+?)\.svg}, 'http://upload.cppreference.com/mwiki/\1.svg'
87 html
98 end
109 end
lib/docs/filters/crystal/clean_html.rb+1−1
@@ -16,7 +16,7 @@ module Docs · 15 unmodified lines
1616 end
1717
1818 def api
19− @doc = at_css('#main-content')
19+ @doc = at_css('.main-content')
2020
2121 at_css('h1 + p').remove if root_page?
2222
lib/docs/filters/crystal/entries.rb+1−1
@@ -32,7 +32,7 @@ module Docs · 31 unmodified lines
3232 if hierarchy && hierarchy.content.include?('Exception')
3333 'Exceptions'
3434 else
35− type = at_css('#types-list > ul > .current > a').content
35+ type = at_css('.types-list > ul > .current > a').content
3636 type = 'Float' if type.start_with?('Float')
3737 type = 'Int' if type.start_with?('Int')
3838 type = 'UInt' if type.start_with?('UInt')
lib/docs/filters/css/entries.rb+23−12
@@ -6,13 +6,16 @@ module Docs · 5 unmodified lines
66 'CSS_Background_and_Borders' => 'Backgrounds & Borders',
77 'CSS_Columns' => 'Multi-column Layout',
88 'CSS_Flexible_Box_Layout' => 'Flexible Box Layout',
9+ 'CSS_Fonts' => 'Fonts',
910 'CSS_Grid_Layout' => 'Grid Layout',
10− 'CSS_Images' => 'Image Values',
11− 'CSS_Lists_and_Counters' => 'Lists & Counters',
11+ 'CSS_Images' => 'Images',
12+ 'CSS_Lists_and_Counters' => 'Lists',
1213 'CSS_Transforms' => 'Transforms',
1314 'Media_Queries' => 'Media Queries',
15+ 'filter-function' => 'Filter Effects',
1416 'transform-function' => 'Transforms',
1517 '@media' => 'Media Queries',
18+ 'overscroll' => 'Overscroll',
1619 'text-size-adjust' => 'Miscellaneous',
1720 'resolved_value' => 'Miscellaneous',
1821 'touch-action' => 'Miscellaneous',
@@ -34,7 +37,7 @@ module Docs · 15 unmodified lines
3437 "#{super}()"
3538 elsif slug =~ /\A[a-z]+_/i
3639 slug.to_s.gsub('_', ' ').gsub('/', ': ')
37− elsif slug.start_with?('transform-function')
40+ elsif slug.start_with?('transform-function') || slug.start_with?('filter-function')
3841 slug.split('/').last + '()'
3942 else
4043 super
@@ -42,7 +45,7 @@ module Docs · 1 unmodified line
4245 end
4346
4447 def get_type
45− if slug.include?('-webkit') || slug.include?('-moz')
48+ if slug.include?('-webkit') || slug.include?('-moz') || slug.include?('-ms')
4649 'Extensions'
4750 elsif type = TYPE_BY_PATH[slug.split('/').first]
4851 type
@@ -51,6 +54,7 @@ module Docs · 2 unmodified lines
5154 type.remove! ' Module'
5255 type.remove! %r{ Level \d\z}
5356 type.remove! %r{\(.*\)}
57+ type.remove! %r{ \d\z}
5458 type.sub! 'and', '&'
5559 type.strip!
5660 type = 'Grid Layout' if type.include?('Grid Layout')
@@ -65,18 +69,22 @@ module Docs · 8 unmodified lines
6569 'Pseudo-Elements'
6670 elsif name.start_with?(':')
6771 'Selectors'
72+ elsif name.start_with?('display-')
73+ 'Display'
6874 else
6975 'Miscellaneous'
7076 end
7177 end
7278
7379 STATUSES = {
74− 'spec-Living' => 0,
75− 'spec-REC' => 1,
76− 'spec-CR' => 2,
77− 'spec-LC' => 3,
78− 'spec-WD' => 4,
79− 'spec-ED' => 5
80+ 'spec-Living' => 0,
81+ 'spec-REC' => 1,
82+ 'spec-CR' => 2,
83+ 'spec-PR' => 3,
84+ 'spec-LC' => 4,
85+ 'spec-WD' => 5,
86+ 'spec-ED' => 6,
87+ 'spec-Obsolete' => 7
8088 }
8189
8290 PRIORITY_STATUSES = %w(spec-REC spec-CR)
@@ -84,11 +92,14 @@ module Docs · 1 unmodified line
8492
8593 def get_spec
8694 return unless table = at_css('#Specifications + table') || css('.standard-table').last
95+
8796 specs = table.css('tbody tr').to_a
8897 # [link, span]
8998 specs.map! { |node| [node.at_css('> td:nth-child(1) > a'), node.at_css('> td:nth-child(2) > span')] }
9099 # ignore non-CSS specs
91100 specs.select! { |pair| pair.first && pair.first['href'] =~ /css|fxtf|fullscreen|svg/i && !pair.first['href'].include?('compat.spec') }
101+ # ignore specs with no status
102+ specs.select! { |pair| pair.second }
92103 # ["Spec", "spec-REC"]
93104 specs.map! { |pair| [pair.first.child.content, pair.second['class']] }
94105 # sort by status
@@ -106,8 +117,8 @@ module Docs · 11 unmodified lines
106117 'shape' => [
107118 %w(rect() Syntax) ],
108119 'timing-function' => [
109− %w(cubic-bezier() The_cubic-bezier()_class_of_timing-functions),
110− %w(steps() The_steps()_class_of_timing-functions),
120+ %w(cubic-bezier()),
121+ %w(steps()),
111122 %w(linear linear),
112123 %w(ease ease),
113124 %w(ease-in ease-in),
lib/docs/filters/d/entries.rb+2−0
@@ -19,6 +19,8 @@ module Docs · 18 unmodified lines
1919 name.split('.')[0..2].join('.')
2020 elsif name.start_with?('ddmd')
2121 'ddmd'
22+ elsif name.start_with?('rt')
23+ 'rt'
2224 else
2325 name.split('.')[0..1].join('.')
2426 end
lib/docs/filters/dart/clean_html.rb+66−0
@@ -0,0 +1,66 @@
1+module 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
66+end
lib/docs/filters/dart/entries.rb+42−0
@@ -0,0 +1,42 @@
1+module 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
42+end
lib/docs/filters/dom/clean_html.rb+1−1
@@ -30,7 +30,7 @@ module Docs · 29 unmodified lines
3030 end
3131
3232 # 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|
3434 node.parent.replace(node)
3535 end
3636
lib/docs/filters/dom/entries.rb+28−3
@@ -6,10 +6,13 @@ module Docs · 5 unmodified lines
66 'EXT_' => 'WebGL',
77 'OES_' => 'WebGL',
88 'WEBGL_' => 'WebGL',
9+ 'Sensor API' => 'Sensors',
910 'Ambient Light' => 'Ambient Light',
1011 'Audio' => 'Audio',
1112 'Battery Status' => 'Battery Status',
1213 'Canvas ' => 'Canvas',
14+ 'Clipboard' => 'Clipboard',
15+ 'Content Security' => 'Content Security Policy',
1316 'Cooperative Scheduling' => 'Scheduling',
1417 'CSS Font Loading' => 'CSS',
1518 'CSS Object Model' => 'CSS',
@@ -20,10 +23,13 @@ module Docs · 4 unmodified lines
2023 'Encrypted Media Extensions' => 'Encrypted Media',
2124 'Fetch' => 'Fetch',
2225 'File API' => 'File',
26+ 'Fullscreen' => 'Fullscreen',
2327 'Geolocation' => 'Geolocation',
2428 'Geometry' => 'Geometry',
2529 'High Resolution Time' => 'Performance',
2630 'Intersection' => 'Intersection Observer',
31+ 'Keyboard' => 'Keyboard',
32+ 'Media Capabilities' => 'Media',
2733 'Media Capture' => 'Media',
2834 'Media Session' => 'Media',
2935 'Media Source' => 'Media',
@@ -32,6 +38,7 @@ module Docs · 2 unmodified lines
3238 'MIDI' => 'Audio',
3339 'Navigation Timing' => 'Performance',
3440 'Network Information' => 'Network Information',
41+ 'Orientation Sensor' => 'Sensors',
3542 'Payment' => 'Payments',
3643 'Performance Timeline' => 'Performance',
3744 'Pointer Events' => 'Pointer Events',
@@ -45,15 +52,22 @@ module Docs · 7 unmodified lines
4552 'Stream API' => 'Media Streams',
4653 'Streams' => 'Media Streams',
4754 'Touch Events' => 'Touch Events',
55+ 'Visual Viewport' => 'Visual Viewport',
4856 'Web Animations' => 'Animation',
4957 'Web App Manifest' => 'Web App Manifest',
5058 'Budget' => 'Budget',
59+ 'Web Authentication' => 'Authentication',
60+ 'Web Locks' => 'Locks',
5161 'Web Workers' => 'Web Workers',
5262 'WebGL' => 'WebGL',
5363 'WebRTC' => 'WebRTC',
54− 'WebVR' => 'WebVR' }
64+ 'WebUSB' => 'WebUSB',
65+ 'WebVR' => 'WebVR',
66+ 'WebVTT' => 'WebVTT' }
5567
5668 TYPE_BY_NAME_STARTS_WITH = {
69+ 'AbortController' => 'Fetch',
70+ 'AbortSignal' => 'Fetch',
5771 'Ambient' => 'Ambient Light',
5872 'Attr' => 'Nodes',
5973 'Audio' => 'Audio',
@@ -61,6 +75,7 @@ module Docs · 1 unmodified line
6175 'Broadcast' => 'Broadcast Channel',
6276 'Budget' => 'Budget',
6377 'Canvas' => 'Canvas',
78+ 'Clipboard' => 'Clipboard',
6479 'CSS' => 'CSS',
6580 'CharacterData' => 'Nodes',
6681 'ChildNode' => 'Nodes',
@@ -69,6 +84,7 @@ module Docs · 2 unmodified lines
6984 'CustomElement' => 'Custom Elements',
7085 'DataTransfer' => 'Drag & Drop',
7186 'document' => 'Document',
87+ 'Document Object' => 'DOM',
7288 'DocumentFragment' => 'DocumentFragment',
7389 'DocumentType' => 'Nodes',
7490 'DOM' => 'DOM',
@@ -79,10 +95,12 @@ module Docs · 4 unmodified lines
7995 'Fetch' => 'Fetch',
8096 'File' => 'File',
8197 'GlobalEventHandlers' => 'GlobalEventHandlers',
98+ 'HMDVR' => 'WebVR',
8299 'history' => 'History',
83100 'HTML Drag' => 'Drag & Drop',
84101 'HTML' => 'Elements',
85102 'IDB' => 'IndexedDB',
103+ 'Keyboard' => 'Keyboard',
86104 'location' => 'Location',
87105 'navigator' => 'Navigator',
88106 'MediaKeySession' => 'Encrypted Media',
@@ -90,6 +108,7 @@ module Docs · 1 unmodified line
90108 'MediaSession' => 'Media Session',
91109 'MediaTrack' => 'Media Streams',
92110 'Message' => 'Channel Messaging',
111+ 'Mutation' => 'DOM',
93112 'NamedNode' => 'Nodes',
94113 'Node' => 'Nodes',
95114 'Notification' => 'Notification',
@@ -105,9 +124,13 @@ module Docs · 9 unmodified lines
105124 'screen' => 'Screen',
106125 'Selection' => 'Selection',
107126 'Shadow' => 'Shadow DOM',
127+ 'StaticRange' => 'Range',
128+ 'Streams' => 'Media Streams',
129+ 'StyleProperty' => 'CSS',
108130 'StyleSheet' => 'CSS',
109131 'Stylesheet' => 'CSS',
110132 'SVG' => 'SVG',
133+ 'TextTrack' => 'WebVTT',
111134 'TimeRanges' => 'Media',
112135 'timing' => 'Performance',
113136 'Timing' => 'Performance',
@@ -116,6 +139,7 @@ module Docs · 2 unmodified lines
116139 'URL' => 'URL',
117140 'VR' => 'WebVR',
118141 'WebSocket' => 'Web Sockets',
142+ 'USB' => 'WebUSB',
119143 'window' => 'Window',
120144 'Window' => 'Window',
121145 'XMLHttpRequest' => 'XMLHTTPRequest' }
@@ -145,6 +169,7 @@ module Docs · 23 unmodified lines
145169 'timing' => 'Performance',
146170 'Timing' => 'Performance',
147171 'udio' => 'Audio',
172+ 'VRDevice' => 'WebVR',
148173 'WebGL' => 'WebGL',
149174 'WEBGL' => 'WebGL',
150175 'WebRTC' => 'WebRTC',
@@ -164,7 +189,6 @@ module Docs · 13 unmodified lines
164189
165190 CLEANUP_NAMES = %w(
166191 CSS\ Object\ Model.
167− Document\ Object\ Model.
168192 Tutorial.
169193 XMLHttpRequest.
170194 ANGLE\ instanced\ arrays.)
@@ -172,6 +196,7 @@ module Docs · 1 unmodified line
172196 def get_name
173197 name = super
174198 CLEANUP_NAMES.each { |str| name.remove!(str) }
199+ name.sub! %r{Document\ Object\ Model\.}i, 'Document Object Model: '
175200 name.sub! 'Input.', 'HTMLInputElement.'
176201 name.sub! 'window.navigator', 'navigator'
177202 name.sub! 'API.', 'API: '
@@ -224,7 +249,7 @@ module Docs · 46 unmodified lines
224249
225250 def include_default_entry?
226251 return true if type == 'Console'
227− return true unless node = doc.at_css('.overheadIndicator')
252+ return true unless node = doc.at_css('.overheadIndicator, .blockIndicator')
228253 node = node.parent while node.parent != doc
229254 return true if node.previous_element.try(:name).in?(%w(h2 h3))
230255 content = node.content
lib/docs/filters/electron/clean_html.rb+10−2
@@ -4,7 +4,7 @@ module Docs · 3 unmodified lines
44 def call
55 css('.header-link', 'hr + .text-center', 'hr').remove
66
7− css('.grid', '.row', '.col-ms-12', 'ul.docs-list > ul.docs-list').each do |node|
7+ css('.grid', '.row', '.col-ms-12', 'ul.docs-list > ul.docs-list', '.sub-section').each do |node|
88 node.before(node.children).remove
99 end
1010
@@ -16,18 +16,26 @@ module Docs · 5 unmodified lines
1616 end
1717 end
1818
19− at_css('h2').name = 'h1' unless at_css('h1')
19+ at_css('h2').name = 'h1' if !at_css('h1') && at_css('h2')
2020
2121 css('h3', 'h4', 'h5').each do |node|
2222 node.name = node.name.sub(/\d/) { |i| i.to_i - 1 } unless node.name == 'h3' && node.at_css('code')
2323 end if !at_css('h2') && at_css('h4')
2424
25+ css('h1 > a', 'h2 > a', 'h3 > a', 'h4 > a').each do |node|
26+ node.before(node.children).remove
27+ end
28+
2529 css('div.highlighter-rouge').each do |node|
2630 node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']
2731 node.content = node.content.strip
2832 node.name = 'pre'
2933 end
3034
35+ css('pre > code.hljs').each do |node|
36+ node.parent['data-language'] = node['class'][/language-(\w+)/, 1]
37+ end
38+
3139 css('.highlighter-rouge').remove_attr('class')
3240
3341 css('pre').each do |node|
lib/docs/filters/electron/entries.rb+8−8
@@ -2,6 +2,8 @@ module Docs · 1 unmodified line
22 class Electron
33 class EntriesFilter < Docs::EntriesFilter
44 def get_name
5+ return 'API' if subpath == '/api'
6+
57 name = at_css('h1, h2').content
68 name.remove! 'Class: '
79 name.remove! ' Object'
@@ -12,11 +14,13 @@ module Docs · 4 unmodified lines
1214 end
1315
1416 def get_type
15− if subpath.start_with?('tutorial') || slug.in?(%w(glossary/ faq/))
17+ return 'API' if subpath == '/api'
18+
19+ if subpath.start_with?('/tutorial') || subpath.in?(%w(/glossary /faq))
1620 'Guides'
17− elsif subpath.start_with?('development')
21+ elsif subpath.start_with?('/development')
1822 'Guides: Development'
19− elsif slug.in?(%w(api/synopsis/ api/chrome-command-line-switches/))
23+ elsif subpath.in?(%w(/api/synopsis /api/chrome-command-line-switches))
2024 'API'
2125 elsif at_css('h1, h2').content.include?(' Object')
2226 'API: Objects'
@@ -26,7 +30,7 @@ module Docs · 3 unmodified lines
2630 end
2731
2832 def additional_entries
29− return [] unless slug.start_with?('api/')
33+ return [] unless subpath.start_with?('/api')
3034
3135 css('h3 > code', 'h4 > code').each_with_object [] do |node, entries|
3236 next if node.previous.try(:content).present? || node.next.try(:content).present?
@@ -37,10 +41,6 @@ module Docs · 4 unmodified lines
3741 entries << [name, node.parent['id']] unless name == self.name
3842 end
3943 end
40−
41− def include_default_entry?
42− slug != 'api/'
43− end
4444 end
4545 end
4646 end
lib/docs/filters/elixir/clean_html.rb+6−0
@@ -57,8 +57,14 @@ module Docs · 56 unmodified lines
5757 node.parent.after(node)
5858 end
5959
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+
6065 css('pre').each do |node|
6166 node['data-language'] = 'elixir'
67+ node.content = node.content
6268 end
6369 end
6470 end
lib/docs/filters/eslint/clean_html.rb+28−0
@@ -0,0 +1,28 @@
1+module Docs
2+ class Eslint
3+ class CleanHtmlFilter < Filter
4+ def call
5+ @doc = at_css('.doc') if at_css('.doc')
6+
7+ css('.glyphicon').remove
8+ css('hr', 'colgroup', 'td:empty').remove
9+
10+ css('.container').each do |node|
11+ node.before(node.children).remove
12+ end
13+
14+ css('div.highlighter-rouge').each do |node|
15+ lang = node['class'][/language-(\w+)/, 1]
16+ node['data-language'] = lang if lang
17+ node.content = node.content.strip
18+ node.name = 'pre'
19+ node.remove_attribute('class')
20+ end
21+
22+ css('code', 'p').remove_attr('class')
23+
24+ doc
25+ end
26+ end
27+ end
28+end
lib/docs/filters/eslint/entries.rb+25−0
@@ -0,0 +1,25 @@
1+module Docs
2+ class Eslint
3+ class EntriesFilter < Docs::EntriesFilter
4+ def get_name
5+ name = at_css('h1').content.strip
6+
7+ if subpath.start_with?('rules/') && subpath != 'rules/'
8+ name = name[/\(([\w\-]+?)\)\z/, 1] || name[/\A([\w\-]+?):/, 1]
9+ end
10+
11+ name
12+ end
13+
14+ def get_type
15+ if subpath.include?('developer-guide')
16+ 'Developer Guide'
17+ elsif subpath.include?('guide')
18+ 'Guide'
19+ elsif subpath.start_with?('rules')
20+ 'Rules'
21+ end
22+ end
23+ end
24+ end
25+end
lib/docs/filters/express/clean_html.rb+5−0
@@ -42,6 +42,11 @@ module Docs · 41 unmodified lines
4242 node.parent.content = node.parent.content
4343 end
4444
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+
4550 doc
4651 end
4752 end
lib/docs/filters/flow/clean_html.rb+0−4
@@ -45,10 +45,6 @@ module Docs · 44 unmodified lines
4545 end
4646 end
4747
48− css('.__cf_email__').each do |node|
49− node.replace(decode_cloudflare_email(node['data-cfemail']))
50− end
51−
5248 css('.editor').each do |node|
5349 pre = node.at_css('.editor-code > pre')
5450 pre['data-language'] = 'javascript'
lib/docs/filters/flow/entries.rb+1−0
@@ -6,6 +6,7 @@ module Docs · 5 unmodified lines
66 end
77
88 def get_type
9+ return 'React' if slug.start_with?('react')
910 type = at_css('.guide-nav .nav-item').content.strip
1011 type.remove! %r{ \(.*}
1112 type
lib/docs/filters/github/clean_html.rb+1−1
@@ -9,7 +9,7 @@ module Docs · 8 unmodified lines
99
1010 css('.highlight > pre').each do |node|
1111 node['data-language'] = node.parent['class'][/highlight-source-(\w+)/, 1]
12− node.content = node.content.strip_heredoc.gsub(' ', ' ')
12+ node.content = node.content.strip_heredoc
1313 node.parent.replace(node)
1414 end
1515
lib/docs/filters/go/clean_html.rb+1−0
@@ -17,6 +17,7 @@ module Docs · 16 unmodified lines
1717 css('#plusone', '#nav', '.pkgGopher', '#footer', '.collapsed', '.permalink', '#pkg-callgraph').remove
1818
1919 css('span[style]', '.toggleVisible', '.expanded', 'div.toggle').each do |node|
20+ node.first_element_child['id'] = node['id'] if node['id']
2021 node.before(node.children).remove
2122 end
2223
lib/docs/filters/graphite/clean_html.rb+27−0
@@ -0,0 +1,27 @@
1+module 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
27+end
lib/docs/filters/graphite/entries.rb+46−0
@@ -0,0 +1,46 @@
1+module 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
46+end
lib/docs/filters/handlebars/clean_html.rb+44−0
@@ -0,0 +1,44 @@
1+module 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
44+end
lib/docs/filters/handlebars/entries.rb+19−0
@@ -0,0 +1,19 @@
1+module 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
19+end
lib/docs/filters/haskell/clean_html.rb+4−0
@@ -65,6 +65,10 @@ module Docs · 64 unmodified lines
6565 end
6666 end
6767
68+ css('pre code').each do |node|
69+ node.before(node.children).remove
70+ end
71+
6872 doc
6973 end
7074 end
lib/docs/filters/haskell/entries.rb+2−2
@@ -52,7 +52,7 @@ module Docs · 51 unmodified lines
5252 return [] if subpath.start_with?('users_guide')
5353 return [] if IGNORE_ENTRIES_PATHS.include?(subpath.split('/').last)
5454
55− css('#synopsis > ul > li').each_with_object [] do |node, entries|
55+ css('#synopsis > details > ul > li').each_with_object [] do |node, entries|
5656 link = node.at_css('a')
5757 name = node.content.strip
5858 name.remove! %r{\A(?:module|data|newtype|class|type family m|type)\s+}
@@ -75,7 +75,7 @@ module Docs · 16 unmodified lines
7575 end
7676
7777 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')
7979 end
8080 end
8181 end
lib/docs/filters/homebrew/clean_html.rb+24−0
@@ -0,0 +1,24 @@
1+module Docs
2+ class Homebrew
3+ class CleanHtmlFilter < Filter
4+ def call
5+ css('hr')
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+
12+ css('div.highlighter-rouge').each do |node|
13+ lang = node['class'][/language-(\w+)/, 1]
14+ node['data-language'] = lang if lang
15+ node.content = node.content.strip
16+ node.name = 'pre'
17+ node.remove_attribute('class')
18+ end
19+
20+ doc
21+ end
22+ end
23+ end
24+end
lib/docs/filters/homebrew/entries.rb+34−0
@@ -0,0 +1,34 @@
1+module Docs
2+ class Homebrew
3+ class EntriesFilter < Docs::EntriesFilter
4+ def get_name
5+ header = at_css('h1')
6+ name = header.nil? ? current_url.normalized_path[1..-1].gsub(/-/, ' ') : header.content.strip
7+ name.remove! %r{\(.*}
8+ name
9+ end
10+
11+ CONTRIBUTOR_SLUGS = %w(
12+ How-To-Open-a-Homebrew-Pull-Request
13+ Formula-Cookbook
14+ Acceptable-Formulae
15+ Versions
16+ Node-for-Formula-Authors
17+ Python-for-Formula-Authors
18+ Migrating-A-Formula-To-A-Tap
19+ Rename-A-Formula
20+ Building-Against-Non-Homebrew-Dependencies
21+ How-to-Create-and-Maintain-a-Tap
22+ Brew-Test-Bot
23+ Prose-Style-Guidelines)
24+
25+ def get_type
26+ if CONTRIBUTOR_SLUGS.include?(slug)
27+ 'Contributors'
28+ else
29+ 'Users'
30+ end
31+ end
32+ end
33+ end
34+end
lib/docs/filters/html/entries.rb+1−1
@@ -28,7 +28,7 @@ module Docs · 27 unmodified lines
2828
2929 def include_default_entry?
3030 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')
3232 end
3333
3434 def additional_entries
lib/docs/filters/javascript/clean_html.rb+2−2
@@ -11,12 +11,12 @@ module Docs · 10 unmodified lines
1111
1212 def other
1313 # Remove "style" attribute
14− css('.inheritsbox', '.overheadIndicator').each do |node|
14+ css('.inheritsbox', '.overheadIndicator', '.blockIndicator').each do |node|
1515 node.remove_attribute 'style'
1616 end
1717
1818 # 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|
2020 node.parent.replace(node)
2121 end
2222 end
lib/docs/filters/javascript/entries.rb+2−2
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class Javascript
33 class EntriesFilter < Docs::EntriesFilter
44 TYPES = %w(Array ArrayBuffer Atomics Boolean DataView Date Function
5− Generator Intl JSON Map Math Number Object Promise Reflect RegExp
5+ Generator Intl JSON Map Math Number Object PluralRules Promise Reflect RegExp
66 Set SharedArrayBuffer SIMD String Symbol TypedArray WeakMap WeakSet)
77 INTL_OBJECTS = %w(Collator DateTimeFormat NumberFormat)
88
@@ -88,7 +88,7 @@ module Docs · 79 unmodified lines
8888 end
8989
9090 def include_default_entry?
91− node = doc.at_css '.overheadIndicator, .warning'
91+ node = doc.at_css '.blockIndicator, .warning'
9292
9393 # Can't use :first-child because #doc is a DocumentFragment
9494 return true unless node && node.parent == doc && !node.previous_element
lib/docs/filters/jekyll/clean_html.rb+43−0
@@ -0,0 +1,43 @@
1+module Docs
2+ class Jekyll
3+ class CleanHtmlFilter < Filter
4+ def call
5+ @doc = at_css('article')
6+
7+ at_css('h1').content = 'Jekyll' if root_page?
8+
9+ css('.improve, .section-nav').remove
10+
11+ css('div.highlighter-rouge').each do |node|
12+ pre = node.at_css('pre')
13+
14+ lang = node['class'][/language-(\w+)/, 1]
15+ # HACK: Prism shell highlighting highlights `|`,
16+ # which makes the tree on this page look terrible
17+ unless slug.include?('structure') && lang == 'sh'
18+ lang = 'bash' if lang == 'sh'
19+ pre['data-language'] = lang
20+ end
21+
22+ pre.remove_attribute('class')
23+ pre.content = pre.content
24+ node.replace(pre)
25+ end
26+
27+ css('code').remove_attr('class')
28+
29+ css('.note').each do |node|
30+ node.name = 'blockquote'
31+
32+ # <div class="note">...<br>...</div> -> <div class="note">...</div>
33+ (node > 'br').each(&:remove)
34+ # <div class="note">...<p>...<br><br>...</p>...</div> ->
35+ # <div class="note">...<p>...<br>...</p>...</div>
36+ node.css('br + br').each(&:remove)
37+ end
38+
39+ doc
40+ end
41+ end
42+ end
43+end
lib/docs/filters/jekyll/entries.rb+28−0
@@ -0,0 +1,28 @@
1+module Docs
2+ class Jekyll
3+ class EntriesFilter < Docs::EntriesFilter
4+ def get_name
5+ at_css('h1').content
6+ end
7+
8+ def get_type
9+ if slug.include?('continuous-integration')
10+ 'Deployment'
11+ else
12+ nav_link = doc.document # document
13+ .at_css('aside li.current') # item in navbar
14+
15+ if nav_link
16+ nav_link
17+ .parent # <ul> in navbar
18+ .previous_element # header before <ul>
19+ .content # category
20+ else
21+ 'Miscellaneous'
22+ end
23+ end
24+ end
25+
26+ end
27+ end
28+end
lib/docs/filters/jest/clean_html.rb+4−3
@@ -12,13 +12,14 @@ module Docs · 11 unmodified lines
1212 node.before(node.children).remove
1313 end
1414
15− css('.anchor[name]').each do |node|
16− node.parent['id'] = node['name']
15+ css('.anchor').each do |node|
16+ node.parent['id'] = node['id']
1717 node.remove
1818 end
1919
2020 css('pre').each do |node|
21− node['data-language'] = node['class'][/language-(\w+)/, 1]
21+ node['data-language'] = 'js'
22+ node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']
2223 node.content = node.content
2324 end
2425
lib/docs/filters/jest/entries.rb+1−1
@@ -6,7 +6,7 @@ module Docs · 5 unmodified lines
66 end
77
88 def get_type
9− type = at_css('.navItemActive').ancestors('.navGroup').first.at_css('h3').content
9+ type = at_css('.navListItemActive').ancestors('.navGroup').first.at_css('h3').content
1010
1111 if type == 'Introduction'
1212 'Guides: Introduction'
lib/docs/filters/jsdoc/clean_html.rb+26−0
@@ -0,0 +1,26 @@
1+module Docs
2+ class Jsdoc
3+ class CleanHtmlFilter < Filter
4+ def call
5+ at_css('h1').content = 'JSDoc' if root_page?
6+
7+ css('.prettyprint').each do |node|
8+ node.content = node.content
9+ node['data-language'] = node['class'][/lang-(\w+)/, 1]
10+ node.remove_attribute('class')
11+ end
12+
13+ css('figcaption').each do |node|
14+ node.name = 'div'
15+ node['class'] = '_pre-heading'
16+ end
17+
18+ css('figure').each do |node|
19+ node.before(node.children).remove
20+ end
21+
22+ doc
23+ end
24+ end
25+ end
26+end
lib/docs/filters/jsdoc/entries.rb+28−0
@@ -0,0 +1,28 @@
1+module Docs
2+ class Jsdoc
3+ class EntriesFilter < Docs::EntriesFilter
4+ def get_name
5+ name = at_css('h1').content
6+ name.prepend 'JSDoc: ' if !name.include?('@') && !name.include?('JSDoc')
7+ name
8+ end
9+
10+ def get_type
11+ case slug
12+ when /^about-/
13+ 'Getting Started'
14+ when /^plugins-/
15+ 'Plugins'
16+ when /^howto-/
17+ 'Examples'
18+ when /^tags-inline-/
19+ 'Inline Tags'
20+ when /^tags-/
21+ 'Tags'
22+ else
23+ 'Miscellaneous' # Only shown if a new category gets added in the upstream docs
24+ end
25+ end
26+ end
27+ end
28+end
lib/docs/filters/koa/clean_html.rb+42−0
@@ -0,0 +1,42 @@
1+# frozen_string_literal: true
2+
3+module Docs
4+ class Koa
5+ class CleanHtmlFilter < Filter
6+ def call
7+ fix_homepage if slug.start_with? 'api/index'
8+
9+ css('[data-language=shell]').each do |node|
10+ node['data-language'] = 'bash'
11+ end
12+
13+ doc
14+ end
15+
16+ def fix_homepage
17+ # Shrink the headers
18+ for n in (1..5).to_a.reverse
19+ css("h#{n}").each do |header|
20+ header.name = "h#{n+1}"
21+ end
22+ end
23+
24+ # Add an introduction
25+ doc.children.before <<-HTML.strip_heredoc
26+ <h1>Koa</h1>
27+ <!-- https://github.com/koajs/koa/blob/841844e/Readme.md -->
28+ <h2 id="introduction">Introduction</h2>
29+ <p>
30+ Expressive HTTP middleware framework for node.js to make web applications and APIs more enjoyable to write. Koa's middleware stack flows in a stack-like manner, allowing you to perform actions downstream then filter and manipulate the response upstream.
31+ </p>
32+ <p>
33+ Only methods that are common to nearly all HTTP servers are integrated directly into Koa's small ~570 SLOC codebase. This includes things like content negotiation, normalization of node inconsistencies, redirection, and a few others.
34+ </p>
35+ <p>
36+ Koa is not bundled with any middleware.
37+ </p>
38+ HTML
39+ end
40+ end
41+ end
42+end
lib/docs/filters/koa/entries.rb+36−0
@@ -0,0 +1,36 @@
1+module Docs
2+ class Koa
3+ class EntriesFilter < Docs::EntriesFilter
4+ @root_type = 'Koa'
5+ def get_name
6+ at_css('h1').content
7+ end
8+
9+ def additional_entries
10+ return [] unless slug.match?(/^api/)
11+ type = get_name
12+ css('h2, h3').to_a
13+ .delete_if do |node|
14+ node.content == 'API' ||
15+ (slug.include?('index') && !node.content.include?('.'))
16+ end
17+ .map do |node|
18+ name = node.content.strip.sub(/\(.*\)\z/, '()')
19+ type = 'API' if type == @root_type && name.include?('.')
20+ [name, node['id'], type]
21+ end
22+ end
23+
24+ def get_type
25+ case slug
26+ when /^api\/index/
27+ 'API'
28+ when /^api/
29+ get_name
30+ else
31+ 'Guides'
32+ end
33+ end
34+ end
35+ end
36+end
lib/docs/filters/kotlin/clean_html.rb+7−0
@@ -46,6 +46,13 @@ module Docs · 45 unmodified lines
4646 parent.content = parent.content
4747 parent['data-language'] = 'kotlin'
4848 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
4956 end
5057 end
5158 end
lib/docs/filters/kotlin/entries.rb+3−1
@@ -5,7 +5,9 @@ module Docs · 4 unmodified lines
55 if subpath.start_with?('api')
66 breadcrumbs[1..-1].join('.')
77 else
8− (at_css('h1') || at_css('h2')).content
8+ 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')
911 end
1012 end
1113
lib/docs/filters/laravel/clean_html.rb+0−4
@@ -8,10 +8,6 @@ module Docs · 7 unmodified lines
88 other
99 end
1010
11− css('a.__cf_email__').each do |node|
12− node.replace(decode_cloudflare_email(node['data-cfemail']))
13− end
14−
1511 # Remove code highlighting
1612 css('pre').each do |node|
1713 node.content = node.content
lib/docs/filters/leaflet/clean_html.rb+40−0
@@ -0,0 +1,40 @@
1+module Docs
2+ class Leaflet
3+ class CleanHtmlFilter < Filter
4+ def call
5+ css('#toc', '.expander', '.footer').remove
6+
7+ css('h1').each do |node|
8+ node.name = 'h2'
9+ end
10+
11+ at_css('> h2:first-child').name = 'h1'
12+
13+ # remove "This reference reflects Leaflet 1.2.0."
14+ css('h1 ~ p').each do |node|
15+ node.remove
16+ break
17+ end
18+
19+ css('section', 'code b', '.accordion', '.accordion-overflow', '.accordion-content').each do |node|
20+ node.before(node.children).remove
21+ end
22+
23+ css('pre > code').each do |node|
24+ node['class'] ||= ''
25+ lang = if node['class'].include?('lang-html') || node.content =~ /\A</
26+ 'html'
27+ elsif node['class'].include?('lang-css')
28+ 'css'
29+ elsif node['class'].include?('lang-js') || node['class'].include?('lang-javascript')
30+ 'javascript'
31+ end
32+ node.parent['data-language'] = lang if lang
33+ node.before(node.children).remove
34+ end
35+
36+ doc
37+ end
38+ end
39+ end
40+end
lib/docs/filters/leaflet/entries.rb+33−0
@@ -0,0 +1,33 @@
1+module Docs
2+ class Leaflet
3+ class EntriesFilter < Docs::EntriesFilter
4+ def additional_entries
5+ entries = []
6+ type = nil
7+ subtype = nil
8+
9+ css('*').each do |node|
10+ if node.name == 'h2' && node['id']
11+ type = node.content
12+ subtype = nil
13+ entries << [type, node['id'], type]
14+ elsif node.name == 'h3'
15+ subtype = node.content
16+ elsif node.name == 'tr' && node['id']
17+ value = node.css('td > code > b').first.content
18+ if subtype && subtype.end_with?(' options')
19+ name = "#{subtype}: #{value}"
20+ elsif subtype
21+ name = "#{type} #{subtype.downcase}: #{value}"
22+ else
23+ name = "#{type}: #{value}"
24+ end
25+ entries << [name, node['id'], type]
26+ end
27+ end
28+
29+ entries
30+ end
31+ end
32+ end
33+end
lib/docs/filters/mdn/clean_html.rb+90−0
@@ -14,6 +14,8 @@ module Docs · 13 unmodified lines
1414 '#Quick_Links',
1515 'hr']
1616
17+ BROWSER_UNNECESSARY_CLASS_REGEX = /\s*bc-browser[\w_-]+/
18+
1719 def call
1820 css(*REMOVE_NODES).remove
1921
@@ -61,6 +63,94 @@ module Docs · 41 unmodified lines
6163 node.previous_element << node
6264 end
6365
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+
64154 doc
65155 end
66156 end
lib/docs/filters/nginx/clean_html.rb+4−0
@@ -30,6 +30,10 @@ module Docs · 29 unmodified lines
3030 node['data-language'] = 'nginx'
3131 end
3232
33+ css('code code').each do |node|
34+ node.before(node.children).remove
35+ end
36+
3337 doc
3438 end
3539 end
lib/docs/filters/nginx/entries.rb+1−1
@@ -20,7 +20,7 @@ module Docs · 19 unmodified lines
2020 def additional_entries
2121 css('h1 + ul a').each_with_object [] do |node, entries|
2222 name = node.content.strip
23− next if name =~ /\A[A-Z]/
23+ next if name =~ /\A[A-Z]/ || name.start_with?('/')
2424
2525 id = node['href'].remove('#')
2626 next if id.blank?
lib/docs/filters/node/clean_html.rb+1−5
@@ -16,16 +16,12 @@ module Docs · 15 unmodified lines
1616
1717 css('pre').each do |node|
1818 if lang = node.at_css('code')['class']
19− node['data-language'] = lang.remove('lang-')
19+ node['data-language'] = lang.remove(%r{lang(uage)?-})
2020 end
2121
2222 node.content = node.content
2323 end
2424
25− css('.__cf_email__').each do |node|
26− node.replace(decode_cloudflare_email(node['data-cfemail']))
27− end
28−
2925 doc
3026 end
3127 end
lib/docs/filters/node/entries.rb+2−0
@@ -26,6 +26,7 @@ module Docs · 25 unmodified lines
2626
2727 def get_type
2828 type = at_css('h1').content.strip
29+ type.remove! %r{\[.*\]}
2930 REPLACE_TYPES[type] || "#{type.first.upcase}#{type[1..-1]}"
3031 end
3132
@@ -40,6 +41,7 @@ module Docs · 8 unmodified lines
4041
4142 klass = nil if node.name == 'h2'
4243 name = node.content.strip
44+ name.remove! %r{\s*\[src\]}
4345
4446 # Skip constructors
4547 if name.start_with? 'new '
lib/docs/filters/npm/clean_html.rb+1−1
@@ -9,7 +9,7 @@ module Docs · 8 unmodified lines
99 css('meta', '.colophon').remove
1010 end
1111
12− css('a.deep-link[id]').each do |node|
12+ css('a.deep-link[id]', 'a.anchor[id]').each do |node|
1313 node.parent['id'] = node['id']
1414 node.remove
1515 end
lib/docs/filters/npm/entries.rb+2−2
@@ -28,11 +28,11 @@ module Docs · 27 unmodified lines
2828 when 'files/package.json'
2929 css('#page > h2').each_with_object [] do |node, entries|
3030 next if node.content =~ /\A[A-Z]/
31− entries << ["package.json: #{node.content}", node.at_css('.deep-link')['id']]
31+ entries << ["package.json: #{node.content}", node.at_css('.anchor')['id']]
3232 end
3333 when 'misc/config'
3434 css('h2:contains("Config Settings") ~ h3').map do |node|
35− ["config: #{node.content}", node.at_css('.deep-link')['id']]
35+ ["config: #{node.content}", node.at_css('.anchor')['id']]
3636 end
3737 else
3838 []
lib/docs/filters/pandas/entries.rb+5−3
@@ -2,8 +2,10 @@ module Docs · 1 unmodified line
22 class Pandas
33 class EntriesFilter < Docs::EntriesFilter
44 def get_name
5− if subpath.start_with?('generated')
6− name = at_css('dt').content.strip
5+ if subpath.start_with?('generated') || (subpath.include?('reference') && !subpath.include?('reference/index'))
6+ name_node = at_css('dt')
7+ name_node = at_css('h1') if name_node.nil?
8+ name = name_node.content.strip
79 name.sub! %r{\(.*}, '()'
810 name.remove! %r{\s=.*}
911 name.remove! %r{\A(class(method)?) (pandas\.)?}
@@ -16,7 +18,7 @@ module Docs · 6 unmodified lines
1618 end
1719
1820 def get_type
19− if subpath.start_with?('generated')
21+ if subpath.start_with?('generated') || (subpath.include?('reference') && !subpath.include?('reference/index'))
2022 css('.toctree-l2.current > a').last.content.remove(/\s\(.+?\)/)
2123 else
2224 'Manual'
lib/docs/filters/postgresql/clean_html.rb+34−5
@@ -13,18 +13,22 @@ module Docs · 12 unmodified lines
1313 def other
1414 @doc = at_css('#docContent')
1515
16− css('.NAVHEADER', 'hr', '.NAVFOOTER a[accesskey="H"]').remove
16+ css('.navheader', 'hr', '.navfooter a[accesskey="H"]').remove
17+
18+ unless at_css('h1')
19+ at_css('.refnamediv h2, .titlepage h2').name = 'h1'
20+ end
1721
1822 css('a[name]').each do |node|
1923 node.parent['id'] = node['name']
2024 node.before(node.children).remove
2125 end
2226
23− css('div.SECT1', 'pre > kbd', 'tt > code', 'h1 > tt', '> .CHAPTER', 'div.NOTE', '.APPENDIX').each do |node|
27+ css('div.sect1', '.refentry', '.refnamediv', '.refentrytitle', '.refsynopsisdiv', 'pre > kbd', 'tt > code', 'h1 > tt', '> .chapter', '.appendix', '.titlepage', 'div:not([class]):not([id])', 'br', 'a.indexterm', 'acronym', '.productname', 'div.itemizedlist', 'span.sect2', 'span.application', 'em.replaceable', 'span.term').each do |node|
2428 node.before(node.children).remove
2529 end
2630
27− css('div.CAUTION table.CAUTION').each do |node|
31+ css('div.caution table.caution').each do |node|
2832 parent = node.parent
2933 title = node.at_css('.c2, .c3, .c4, .c5').content
3034 node.replace(node.css('p'))
@@ -43,11 +47,27 @@ module Docs · 12 unmodified lines
4347 node.remove_attribute 'valign'
4448 end
4549
50+ css('.sect2 > h3').each do |node|
51+ node.name = 'h2'
52+ end
53+
54+ css('.sect3 > h4').each do |node|
55+ node.name = 'h3'
56+ end
57+
4658 css('tt').each do |node|
4759 node.name = 'code'
4860 end
4961
50− css('.REFSYNOPSISDIV > p').each do |node|
62+ css('div.note', 'div.important', 'div.tip', 'div.caution').each do |node|
63+ if node.at_css('blockquote')
64+ node.before(node.children).remove
65+ else
66+ node.name = 'blockquote'
67+ end
68+ end
69+
70+ css('.refsynopsisdiv > p').each do |node|
5171 node.name = 'pre'
5272 node.content = node.content
5373 end
@@ -56,9 +76,18 @@ module Docs · 2 unmodified lines
5676 node.before(node.children).remove
5777 end
5878
59− css('pre.SYNOPSIS', 'pre.PROGRAMLISTING').each do |node|
79+ css('code').each do |node|
80+ node.inner_html = node.inner_html.gsub(/\s*\n\s*/, ' ')
81+ end
82+
83+ css('pre.synopsis', 'pre.programlisting').each do |node|
6084 node['data-language'] = 'sql'
6185 end
86+
87+ css('h1', 'ul', 'li', 'pre').each do |node|
88+ node.remove_attribute 'class'
89+ node.remove_attribute 'style'
90+ end
6291 end
6392 end
6493 end
lib/docs/filters/postgresql/entries.rb+23−21
@@ -90,33 +90,33 @@ module Docs · 89 unmodified lines
9090 return config_additional_entries if type && type.include?('Configuration')
9191 return data_types_additional_entries if type == 'Data Types'
9292 return command_additional_entries if type == 'Commands'
93− return get_heading_entries('h3[id]') if slug == 'functions-xml'
93+ return get_heading_entries('h3[id], .sect3[id] > h3:first-child') if slug == 'functions-xml'
9494
95− entries = get_heading_entries('h2[id]')
95+ entries = get_heading_entries('h2[id], .sect2[id] > h2:first-child')
9696
9797 case slug
9898 when 'queries-union'
99− entries.concat get_custom_entries('p > .LITERAL:first-child')
99+ entries.concat get_custom_entries('p > .literal:first-child')
100100 when 'queries-table-expressions'
101− entries.concat get_heading_entries('h3[id]')
102− entries.concat get_custom_entries('dt > .LITERAL:first-child')
101+ entries.concat get_heading_entries('h3[id], .sect3[id] > h3:first-child')
102+ entries.concat get_custom_entries('dt > .literal:first-child')
103103 when 'functions-logical'
104104 entries.concat get_custom_entries('> table td:first-child > code')
105105 when 'functions-formatting'
106106 entries.concat get_custom_entries('#FUNCTIONS-FORMATTING-TABLE td:first-child > code')
107107 when 'functions-admin'
108− entries.concat get_custom_entries('.TABLE td:first-child > code')
108+ entries.concat get_custom_entries('.table td:first-child > code')
109109 when 'functions-string'
110110 entries.concat get_custom_entries('> div[id^="FUNC"] td:first-child > code')
111111 entries.concat get_custom_entries('> div[id^="FORMAT"] td:first-child > code')
112112 else
113113 if type && type.start_with?('Functions')
114− entries.concat get_custom_entries('> .TABLE td:first-child > code.LITERAL:first-child')
115− entries.concat get_custom_entries('> .TABLE td:first-child > code.FUNCTION:first-child')
116− entries.concat get_custom_entries('> .TABLE td:first-child > code:not(.LITERAL):first-child + code.LITERAL')
117− entries.concat get_custom_entries('> .TABLE td:first-child > p > code.LITERAL:first-child')
118− entries.concat get_custom_entries('> .TABLE td:first-child > p > code.FUNCTION:first-child')
119− entries.concat get_custom_entries('> .TABLE td:first-child > p > code:not(.LITERAL):first-child + code.LITERAL')
114+ entries.concat get_custom_entries('> .table td:first-child > code.literal:first-child')
115+ entries.concat get_custom_entries('> .table td:first-child > code.function:first-child')
116+ entries.concat get_custom_entries('> .table td:first-child > code:not(.literal):first-child + code.literal')
117+ entries.concat get_custom_entries('> .table td:first-child > p > code.literal:first-child')
118+ entries.concat get_custom_entries('> .table td:first-child > p > code.function:first-child')
119+ entries.concat get_custom_entries('> .table td:first-child > p > code:not(.literal):first-child + code.literal')
120120 if slug == 'functions-comparison' && !at_css('#FUNCTIONS-COMPARISON-PRED-TABLE') # before 9.6
121121 entries.concat %w(IS NULL BETWEEN DISTINCT\ FROM).map { |name| ["#{self.name}: #{name}"] }
122122 end
@@ -127,8 +127,8 @@ module Docs · 4 unmodified lines
127127 end
128128
129129 def config_additional_entries
130− css('.VARIABLELIST dt[id]').map do |node|
131− name = node.at_css('.VARNAME').content
130+ css('.variablelist dt[id]').map do |node|
131+ name = node.at_css('.varname').content
132132 ["Config: #{name}", node['id']]
133133 end
134134 end
@@ -136,27 +136,27 @@ module Docs · 1 unmodified line
136136 def data_types_additional_entries
137137 selector = case slug
138138 when 'rangetypes'
139− 'li > p > .TYPE:first-child'
139+ 'li > p > .type:first-child'
140140 when 'datatype-textsearch'
141− '.SECT2 > .TYPE'
141+ '.title > .type, .sect2 > .type'
142142 else
143− '.CALSTABLE td:first-child > .TYPE'
143+ '.table-contents td:first-child > .type, .calstable td:first-child > .type'
144144 end
145145 get_custom_entries(selector)
146146 end
147147
148148 def command_additional_entries
149− css('.REFSECT2[id^="SQL"]').each_with_object([]) do |node, entries|
149+ css('.refsect2[id^="SQL"]').each_with_object([]) do |node, entries|
150150 next unless heading = node.at_css('h3')
151151 next unless heading.content.strip =~ /[A-Z_\-]+ Clause/
152− name = heading.at_css('.LITERAL').content
152+ name = heading.at_css('.literal').content
153153 name.prepend "#{self.name} ... "
154154 entries << [name, node['id']]
155155 end
156156 end
157157
158158 def include_default_entry?
159− !initial_page? && !at_css('.TOC') && type
159+ !initial_page? && (!at_css('.toc') || at_css('.sect2, .variablelist, .refsect1')) && type
160160 end
161161
162162 SKIP_ENTRIES_SLUGS = [
@@ -199,7 +199,9 @@ module Docs · 36 unmodified lines
199199 css(selector).each_with_object([]) do |node, entries|
200200 name = node.content
201201 clean_heading_name(name)
202− entries << ["#{additional_entry_prefix}: #{name}", node['id']] unless skip_heading?(name)
202+ id = node['id'] || node.parent['id']
203+ raise "missing ids for selector #{selector}" unless id
204+ entries << ["#{additional_entry_prefix}: #{name}", id] unless skip_heading?(name)
203205 end
204206 end
205207
lib/docs/filters/postgresql/extract_metadata.rb+10−8
@@ -8,19 +8,21 @@ module Docs · 7 unmodified lines
88 end
99
1010 def extract_up_path
11− if node = at_css('.NAVHEADER a[accesskey="U"]')
11+ if node = at_css('.navheader a[accesskey="u"], .navheader a[accesskey="U"]')
1212 result[:pg_up_path] = node['href']
1313 end
1414 end
1515
1616 def extract_chapter
17− return unless text = at_css('.NAVHEADER td[align="center"]').content
18− if match = text.match(/\AChapter (\d+)\. (.+)\z/)
19− result[:pg_chapter] = match[1].to_i
20− result[:pg_chapter_name] = match[2].strip
21− elsif match = text.match(/\AAppendix ([A-Z])\. (.+)\z/)
22− result[:pg_appendix] = match[1]
23− result[:pg_appendix_name] = match[2].strip
17+ css('.navheader td[align="center"], .navheader th[align="center"]').each do |node|
18+ text = node.content.strip
19+ if match = text.match(/\AChapter (\d+)\. (.+)\z/)
20+ result[:pg_chapter] = match[1].to_i
21+ result[:pg_chapter_name] = match[2].strip
22+ elsif match = text.match(/\AAppendix ([A-Z])\. (.+)\z/)
23+ result[:pg_appendix] = match[1]
24+ result[:pg_appendix_name] = match[2].strip
25+ end
2426 end
2527 end
2628 end
lib/docs/filters/postgresql/normalize_class_names.rb+13−0
@@ -0,0 +1,13 @@
1+module Docs
2+ class Postgresql
3+ class NormalizeClassNamesFilter < Filter
4+ def call
5+ doc.css('*').each do |node|
6+ node['class'] = node['class'].downcase if node['class'].present?
7+ end
8+
9+ doc
10+ end
11+ end
12+ end
13+end
lib/docs/filters/puppeteer/clean_html.rb+24−0
@@ -0,0 +1,24 @@
1+module 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
24+end
lib/docs/filters/puppeteer/entries.rb+41−0
@@ -0,0 +1,41 @@
1+module 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
41+end
lib/docs/filters/pygame/clean_html.rb+102−0
@@ -0,0 +1,102 @@
1+module 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
102+end
lib/docs/filters/pygame/entries.rb+29−0
@@ -0,0 +1,29 @@
1+module 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
29+end
lib/docs/filters/python/clean_html.rb+5−3
@@ -15,15 +15,17 @@ module Docs · 14 unmodified lines
1515
1616 def other
1717 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|
1919 rgx = /\A#{str}/
2020 @levelRegexp = @levelRegexp ? Regexp.union(@levelRegexp, rgx) : rgx
2121 ''
2222 end
2323 end
2424
25− css('h2', 'h3', 'h4').each do |node|
26− node.inner_html = node.inner_html.remove @levelRegexp
25+ unless @levelRegexp.nil?
26+ css('h2', 'h3', 'h4').each do |node|
27+ node.inner_html = node.inner_html.remove @levelRegexp
28+ end
2729 end
2830 end
2931 end
lib/docs/filters/python/entries_v2.rb+1−7
@@ -57,16 +57,10 @@ module Docs · 56 unmodified lines
5757 clean_id_attributes
5858 entries = []
5959
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|
6161 entries << [node['id'], node['id']]
6262 end
6363
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−
7064 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|
7165 entries << [node['id'] + '()', node['id']]
7266 end
lib/docs/filters/python/entries_v3.rb+15−17
@@ -2,16 +2,18 @@ module Docs · 1 unmodified line
22 class Python
33 class EntriesV3Filter < Docs::EntriesFilter
44 REPLACE_TYPES = {
5− 'Cryptographic' => 'Cryptography',
6− 'Custom Interpreters' => 'Interpreters',
7− 'Data Compression & Archiving' => 'Data Compression',
8− 'Generic Operating System' => 'Operating System',
9− 'Graphical User Interfaces with Tk' => 'Tk',
10− 'Internet Data Handling' => 'Internet Data',
11− 'Internet Protocols & Support' => 'Internet',
12− 'Interprocess Communication & Networking' => 'Networking',
13− 'Program Frameworks' => 'Frameworks',
14− 'Structured Markup Processing Tools' => 'Structured Markup' }
5+ 'contextvars — Context Variables' => 'Context Variables',
6+ 'Cryptographic' => 'Cryptography',
7+ 'Custom Interpreters' => 'Interpreters',
8+ 'Data Compression & Archiving' => 'Data Compression',
9+ 'email — An email & MIME handling package' => 'Email',
10+ 'Generic Operating System' => 'Operating System',
11+ 'Graphical User Interfaces with Tk' => 'Tk',
12+ 'Internet Data Handling' => 'Internet Data',
13+ 'Internet Protocols & Support' => 'Internet',
14+ 'Interprocess Communication & Networking' => 'Networking',
15+ 'Program Frameworks' => 'Frameworks',
16+ 'Structured Markup Processing Tools' => 'Structured Markup' }
1517
1618 def get_name
1719 name = at_css('h1').content
@@ -25,6 +27,7 @@ module Docs · 7 unmodified lines
2527
2628 def get_type
2729 return 'Logging' if slug.start_with? 'library/logging'
30+ return 'Asynchronous I/O' if slug.start_with? 'library/asyncio'
2831
2932 type = at_css('.related a[accesskey="U"]').content
3033
@@ -45,6 +48,7 @@ module Docs · 14 unmodified lines
4548 end
4649
4750 def include_default_entry?
51+ return true if slug == 'library/asyncio'
4852 !at_css('.body > .section:only-child > .toctree-wrapper:last-child') && !type.in?(%w(Superseded))
4953 end
5054
@@ -53,16 +57,10 @@ module Docs · 2 unmodified lines
5357 clean_id_attributes
5458 entries = []
5559
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|
5761 entries << [node['id'], node['id']]
5862 end
5963
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−
6664 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|
6765 entries << [node['id'] + '()', node['id']]
6866 end
lib/docs/filters/qt/clean_html.rb+32−0
@@ -0,0 +1,32 @@
1+module 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
32+end
lib/docs/filters/qt/entries.rb+142−0
@@ -0,0 +1,142 @@
1+# frozen_string_literal: true
2+
3+module 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
142+end
lib/docs/filters/ramda/clean_html.rb+1−1
@@ -11,7 +11,7 @@ module Docs · 10 unmodified lines
1111 css('.card').each do |card|
1212 title = card.at_css('h2')
1313 added_in = card.at_css('small')
14− added_in.parent = title
14+ added_in.parent = title if added_in.present?
1515 end
1616
1717 css('.params').each do |node|
lib/docs/filters/rdoc/clean_html.rb+4−2
@@ -37,8 +37,10 @@ module Docs · 36 unmodified lines
3737 end
3838
3939 # Add class to differentiate Ruby code from C code
40− css('.method-source-code > pre').each do |node|
41− node['class'] = node.at_css('.ruby-keyword') ? 'ruby' : 'c'
40+ css('.method-source-code').each do |node|
41+ node.parent.prepend_child(node)
42+ pre = node.at_css('pre')
43+ pre['class'] = pre.at_css('.ruby-keyword') ? 'ruby' : 'c'
4244 end
4345
4446 # Remove code highlighting
lib/docs/filters/react/entries.rb+7−2
@@ -6,8 +6,11 @@ module Docs · 5 unmodified lines
66 end
77
88 def get_type
9− link = at_css("nav a[href='#{result[:path].split('/').last}']")
10− link.ancestors('ul').last.previous_element.content
9+ link = css("nav a[href='#{result[:path].split('/').last}']").last
10+ return 'Miscellaneous' unless link
11+ type = link.ancestors('ul').last.previous_element.content
12+ type.remove! %r{\s*\(.*\)}
13+ type
1114 end
1215
1316 def additional_entries
@@ -24,6 +27,8 @@ module Docs · 10 unmodified lines
2427 'Reference: Component'
2528 elsif slug == 'react-api'
2629 'Reference: React'
30+ elsif slug == 'hooks-reference'
31+ 'Hooks'
2732 else
2833 'Reference'
2934 end
lib/docs/filters/react_native/clean_html.rb+7−2
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class ReactNative
33 class CleanHtmlFilter < Filter
44 def call
5− @doc = at_css('.inner-content, article.withtoc')
5+ @doc = at_css('.post')
66
77 if root_page?
88 at_css('h1').content = 'React Native Documentation'
@@ -38,6 +38,11 @@ module Docs · 29 unmodified lines
3838 node.content = node.content
3939 end
4040
41+ css('pre > code.hljs').each do |node|
42+ node.parent['data-language'] = 'jsx'
43+ node.before(node.children).remove
44+ end
45+
4146 css('blockquote > p:first-child').each do |node|
4247 node.remove if node.content.strip == 'Note:'
4348 end
@@ -45,7 +50,7 @@ module Docs · 1 unmodified line
4550 css('h3#props', 'h3#methods').each { |node| node.name = 'h2' }
4651 css('h4.propTitle').each { |node| node.name = 'h3' }
4752
48− css('> div > div', '> div', 'div > span', '.props', '.prop').each do |node|
53+ css('> div > div', '> div', 'div > span', '.props', '.prop', '> article', '.postHeader', '.web-player').each do |node|
4954 node.before(node.children).remove
5055 end
5156
lib/docs/filters/react_native/entries.rb+9−11
@@ -12,9 +12,9 @@ module Docs · 11 unmodified lines
1212 end
1313
1414 def get_type
15− link = at_css('.nav-docs-section .active, .toc .active')
15+ link = at_css('.navListItemActive')
1616 return 'Miscellaneous' unless link
17− section = link.ancestors('.nav-docs-section, section').first
17+ section = link.ancestors('.navGroup').first
1818 type = section.at_css('h3').content.strip
1919 type = REPLACE_TYPES[type] || type
2020 type += ": #{name}" if type == 'Components'
@@ -22,15 +22,13 @@ module Docs · 1 unmodified line
2222 end
2323
2424 def additional_entries
25− css('.props > .prop > .propTitle', '.props > .prop > .methodTitle').each_with_object [] do |node, entries|
26− name = node.children.find(&:text?).try(:content)
27− next if name.blank?
28− sep = node.content.include?('static') ? '.' : '#'
29− name.prepend(self.name + sep)
30− name << '()' if node['class'].include?('methodTitle')
31− name.remove! %r{\??\:\s*\z}
32− id = node.at_css('.anchor')['name']
33− entries << [name, id]
25+ css('.mainContainer h3').each_with_object [] do |node, entries|
26+ subname = node.text
27+ next if subname.blank? || node.css('code').empty?
28+ sep = subname.include?('()') ? '.' : '#'
29+ subname.prepend(name + sep)
30+ id = node.at_css('.anchor')['id']
31+ entries << [subname, id]
3432 end
3533 end
3634 end
lib/docs/filters/redis/entries.rb+2−1
@@ -12,7 +12,7 @@ module Docs · 11 unmodified lines
1212 when 'HDEL' then 'Hashes'
1313 when 'BLPOP' then 'Lists'
1414 when 'SADD' then 'Sets'
15− when 'ZADD' then 'Sorted Sets'
15+ when 'BZPOPMAX' then 'Sorted Sets'
1616 when 'PSUBSCRIBE' then 'Pub/Sub'
1717 when 'DISCARD' then 'Transactions'
1818 when 'EVAL' then 'Scripting'
@@ -21,6 +21,7 @@ module Docs · 2 unmodified lines
2121 when 'PFADD' then 'HyperLogLog'
2222 when 'CLUSTER ADDSLOTS' then 'Cluster'
2323 when 'GEOADD' then 'Geo'
24+ when 'XACK' then 'Stream'
2425 else 'Miscellaneous'
2526 end
2627 end
lib/docs/filters/rust/clean_html.rb+39−6
@@ -1,11 +1,11 @@
1+# frozen_string_literal: true
2+
13 module Docs
24 class Rust
35 class CleanHtmlFilter < Filter
46 def call
5− puts subpath if at_css('#versioninfo')
6−
77 if slug.start_with?('book') || slug.start_with?('reference')
8− @doc = at_css('#content')
8+ @doc = at_css('#content main')
99 elsif slug == 'error-index'
1010 css('.error-undescribed').remove
1111
@@ -29,12 +29,16 @@ module Docs · 17 unmodified lines
2929
3030 css('.rusttest', '.test-arrow', 'hr').remove
3131
32+ css('.docblock.attributes').each do |node|
33+ node.remove if node.content.include?('#[must_use]')
34+ end
35+
3236 css('a.header').each do |node|
33− node.first_element_child['id'] = node['name']
37+ node.first_element_child['id'] = node['name'] || node['id']
3438 node.before(node.children).remove
3539 end
3640
37− css('.docblock > h1').each { |node| node.name = 'h4' }
41+ css('.docblock > h1:not(.section-header)').each { |node| node.name = 'h4' }
3842 css('h2.section-header').each { |node| node.name = 'h3' }
3943 css('h1.section-header').each { |node| node.name = 'h2' }
4044
@@ -44,7 +48,7 @@ module Docs · 3 unmodified lines
4448 end
4549 end
4650
47− css('> .impl-items', '> .docblock', 'pre > pre').each do |node|
51+ css('> .impl-items', '> .docblock', 'pre > pre', '.tooltiptext', '.tooltip').each do |node|
4852 node.before(node.children).remove
4953 end
5054
@@ -62,6 +66,35 @@ module Docs · 11 unmodified lines
6266 node['data-language'] = 'rust' if node['class'] && node['class'].include?('rust')
6367 end
6468
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+
6598 doc
6699 end
67100 end
lib/docs/filters/rust/entries.rb+7−5
@@ -42,11 +42,13 @@ module Docs · 41 unmodified lines
4242 entries << [node.content, node['id']] unless node.content.include?('Note:')
4343 end
4444 else
45− css('#methods + * + div > .method', '#required-methods + div > .method', '#provided-methods + div > .method').map do |node|
46− name = node.at_css('.fnname').content
47− name.prepend "#{self.name}::"
48− [name, node['id']]
49− end
45+ css('.method')
46+ .each_with_object({}) { |node, entries|
47+ name = node.at_css('.fnname').try(:content)
48+ next unless name
49+ name.prepend "#{self.name}::"
50+ entries[name] ||= [name, node['id']]
51+ }.values
5052 end
5153 end
5254 end
lib/docs/filters/scikit_learn/entries.rb+1−0
@@ -27,6 +27,7 @@ module Docs · 26 unmodified lines
2727 type = at_css('dt > .descclassname').content.strip
2828 type.remove! 'sklearn.'
2929 type.remove! %r{\.\z}
30+ type = 'sklearn' if type.blank?
3031 type
3132 elsif subpath.start_with?('tutorial')
3233 'Tutorials'
lib/docs/filters/sqlite/entries.rb+2−1
@@ -34,7 +34,8 @@ module Docs · 33 unmodified lines
3434 'rtree' => 'R*Tree Module',
3535 'rbu' => 'RBU Extension',
3636 'limits' => 'Limits',
37− 'howtocorrupt' => 'How To Corrupt'
37+ 'howtocorrupt' => 'How To Corrupt',
38+ 'geopoly' => 'Geopoly'
3839 }
3940
4041 def get_type
lib/docs/filters/svg/entries.rb+2−2
@@ -3,8 +3,8 @@ module Docs · 2 unmodified lines
33 class EntriesFilter < Docs::EntriesFilter
44 def get_name
55 name = super
6− name.gsub!('Element.', '').try(:downcase!)
7− name.gsub!('Attribute.', '').try(:downcase!)
6+ name.gsub!('Element.', '')
7+ name.gsub!('Attribute.', '')
88 name.gsub!('Tutorial.', '')
99 name.gsub!('_', '')
1010
lib/docs/filters/terraform/clean_html.rb+33−0
@@ -0,0 +1,33 @@
1+module Docs
2+ class Terraform
3+ class CleanHtmlFilter < Filter
4+ def call
5+ @doc = at_css('#inner')
6+
7+ css('hr', 'a.anchor').remove
8+
9+ css('.alert').each do |node|
10+ node.name = 'blockquote'
11+ end
12+
13+ css('pre').each do |node|
14+ if language = node['class'][/(json|shell|ruby)/, 1]
15+ node['data-language'] = language
16+ end
17+ # HCL isn't currently supported by Prism, Ruby syntax does an acceptable job for now
18+ if language = node['class'][/(hcl)/, 1]
19+ node['data-language'] = 'ruby'
20+ end
21+ node.content = node.content
22+ node.remove_attribute('class')
23+ end
24+
25+ css('.highlight').each do |node|
26+ node.before(node.children).remove
27+ end
28+
29+ doc
30+ end
31+ end
32+ end
33+end
lib/docs/filters/terraform/entries.rb+76−0
@@ -0,0 +1,76 @@
1+module Docs
2+ class Terraform
3+ class EntriesFilter < Docs::EntriesFilter
4+
5+ # Some providers have non-trivial mappings between the directory they live in and their name
6+ # Anything *not* in this list will be capitalized instead.
7+ PROVIDER_NAME_MAP = {
8+ 'aws' => 'AWS',
9+ 'azure' => 'Azure (Legacy)',
10+ 'azurerm' => 'Azure',
11+ 'centurylinkcloud' => 'CenturyLinkCloud',
12+ 'cloudscale' => 'CloudScale.ch',
13+ 'cloudstack' => 'CloudStack',
14+ 'dme' => 'DNSMadeEasy',
15+ 'dns' => 'DNS',
16+ 'dnsimple' => 'DNSimple',
17+ 'do' => 'DigitalOcean',
18+ 'github' => 'GitHub',
19+ 'google' => 'Google Cloud',
20+ 'http' => 'HTTP',
21+ 'mysql' => 'MySQL',
22+ 'newrelic' => 'New Relic',
23+ 'oneandone' => '1&1',
24+ 'opentelekomcloud' => 'OpenTelekomCloud',
25+ 'opsgenie' => 'OpsGenie',
26+ 'opc' => 'Oracle Public Cloud',
27+ 'oraclepaas' => 'Oracle Cloud Platform',
28+ 'ovh' => 'OVH',
29+ 'pagerduty' => 'PagerDuty',
30+ 'panos' => 'Palo Alto Networks',
31+ 'postgresql' => 'PostgreSQL',
32+ 'powerdns' => 'PowerDNS',
33+ 'profitbricks' => 'ProfitBricks',
34+ 'rabbitmq' => 'RabbitMQ',
35+ 'softlayer' => 'SoftLayer',
36+ 'statuscake' => 'StatusCake',
37+ 'tls' => 'TLS',
38+ 'ultradns' => 'UltraDNS',
39+ 'vcd' => 'VMware vCloud Director',
40+ 'nsxt' => 'VMware NSX-T',
41+ 'vsphere' => 'VMware vSphere',
42+ }
43+
44+ # Some providers have a lot (> 100) entries, which makes browsing them unwieldy.
45+ # Any present in the list below will have an extra set of types added, breaking the pages out into the different
46+ # products they offer.
47+ LARGE_PROVIDERS = {
48+ "aws" => true,
49+ "azurerm" => true,
50+ "google" => true,
51+ }
52+
53+
54+ def get_name
55+ name ||= at_css('#inner h1').content
56+ name.remove! "» "
57+ name.remove! "Data Source: "
58+ name
59+ end
60+
61+ def get_type
62+ category, subcategory, subfolder, page = *slug.split('/')
63+ provider = page ? subcategory : category
64+ nice_provider = PROVIDER_NAME_MAP[provider] || provider.capitalize
65+
66+ if LARGE_PROVIDERS[provider]
67+ category_node = at_css('ul > li > ul > li.active')
68+ parent_node = category_node.parent.previous_element if category_node
69+ nice_provider = nice_provider + ": #{parent_node.content}" if category_node
70+ end
71+
72+ nice_provider
73+ end
74+ end
75+ end
76+end
lib/docs/filters/typescript/entries.rb+1−0
@@ -9,6 +9,7 @@ module Docs · 8 unmodified lines
99
1010 def get_type
1111 return 'Declaration Files' if subpath.include?('declaration-files')
12+ return 'Project Configuration' if slug == 'handbook/configuring-watch'
1213 type = at_css('#main-nav a.active').ancestors('.panel').first.at_css('> a').content
1314 type = name if type == 'Handbook'
1415 type
lib/docs/filters/vagrant/entries.rb+1−0
@@ -29,6 +29,7 @@ module Docs · 28 unmodified lines
2929 if node.name == 'h2'
3030 h2 = node['id']
3131 elsif h2 == 'available-settings' && (code = node.at_css('code')) && (name = code.content) && name.start_with?('config.')
32+ name.sub! %r{\s+=.*}, '='
3233 id = code.parent['id'] = name.parameterize
3334 entries << [name, id, 'Config']
3435 end
lib/docs/filters/vue/clean_html.rb+0−4
@@ -9,10 +9,6 @@ module Docs · 8 unmodified lines
99
1010 css('.demo', '.guide-links', '.footer', '#ad').remove
1111
12− css('.__cf_email__').each do |node|
13− node.replace(decode_cloudflare_email(node['data-cfemail']))
14− end
15−
1612 # Remove code highlighting
1713 css('figure').each do |node|
1814 node.name = 'pre'
lib/docs/filters/webpack/entries.rb+2−2
@@ -26,9 +26,9 @@ module Docs · 25 unmodified lines
2626 next if node.previous.try(:content).present?
2727 entries << [node.content, node.parent['id']]
2828 end
29− elsif slug.start_with?('api') && slug != 'api/plugins/parser'
29+ elsif slug.start_with?('api') && slug != 'api/parser'
3030 css('.header[id] code').each_with_object [] do |node, entries|
31− next if node.previous.try(:content).present?
31+ next if node.previous.try(:content).present? || node.next.try(:content).present?
3232 name = node.content.sub(/\(.*\)/, '()')
3333 name.prepend "#{self.name.split(':').first}: "
3434 entries << [name, node.parent['id']]
lib/docs/filters/yarn/clean_html.rb+0−4
@@ -43,10 +43,6 @@ module Docs · 42 unmodified lines
4343 end
4444 end
4545
46− css('.__cf_email__').each do |node|
47− node.replace(decode_cloudflare_email(node['data-cfemail']))
48− end
49−
5046 css('div.highlighter-rouge').each do |node|
5147 node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']
5248 node.content = node.content.strip
lib/docs/scrapers/angular.rb+73−21
@@ -11,33 +11,17 @@ module Docs · 10 unmodified lines
1111 options[:max_image_size] = 256_000
1212
1313 options[:attribution] = <<-HTML
14− &copy; 2010&ndash;2017 Google, Inc.<br>
14+ &copy; 2010&ndash;2018 Google, Inc.<br>
1515 Licensed under the Creative Commons Attribution License 4.0.
1616 HTML
1717
18− version do
19− self.release = '4.4.4'
20− self.base_url = 'https://angular.io/'
21− self.root_path = 'docs'
22−
23− html_filters.push 'angular/clean_html', 'angular/entries'
24−
25− options[:follow_links] = false
26− options[:only_patterns] = [/\Aguide/, /\Atutorial/, /\Aapi/]
27− options[:fix_urls_before_parse] = ->(url) do
28− url.sub! %r{\Aguide/}, '/guide/'
29− url.sub! %r{\Atutorial/}, '/tutorial/'
30− url.sub! %r{\Aapi/}, '/api/'
31− url.sub! %r{\Agenerated/}, '/generated/'
32− url
33− end
34−
18+ module Common
3519 private
3620
3721 def initial_urls
3822 initial_urls = []
3923
40− Request.run 'https://angular.io/generated/navigation.json' do |response|
24+ Request.run "#{self.class.base_url}generated/navigation.json" do |response|
4125 data = JSON.parse(response.body)
4226 dig = ->(entry) do
4327 initial_urls << url_for("generated/docs/#{entry['url']}.json") if entry['url'] && entry['url'] != 'api'
@@ -46,7 +30,7 @@ module Docs · 2 unmodified lines
4630 data['SideNav'].each(&dig)
4731 end
4832
49− Request.run 'https://angular.io/generated/docs/api/api-list.json' do |response|
33+ Request.run "#{self.class.base_url}generated/docs/api/api-list.json" do |response|
5034 data = JSON.parse(response.body)
5135 dig = ->(entry) do
5236 initial_urls << url_for("generated/docs/#{entry['path']}.json") if entry['path']
@@ -61,7 +45,11 @@ module Docs · 8 unmodified lines
6145
6246 def handle_response(response)
6347 if response.mime_type.include?('json')
64− response.options[:response_body] = JSON.parse(response.body)['contents']
48+ begin
49+ response.options[:response_body] = JSON.parse(response.body)['contents']
50+ rescue JSON::ParserError
51+ response.options[:response_body] = ''
52+ end
6553 response.headers['Content-Type'] = 'text/html'
6654 response.url.path = response.url.path.sub('/generated/docs/', '/').remove('.json')
6755 response.effective_url.path = response.effective_url.path.sub('/generated/docs/', '/').remove('.json')
@@ -70,6 +58,66 @@ module Docs · 2 unmodified lines
7058 end
7159 end
7260
61+ version do
62+ self.release = '6.0.4'
63+ self.base_url = 'https://angular.io/'
64+ self.root_path = 'docs'
65+
66+ html_filters.push 'angular/clean_html', 'angular/entries'
67+
68+ options[:follow_links] = false
69+ options[:only_patterns] = [/\Aguide/, /\Atutorial/, /\Aapi/]
70+ options[:fix_urls_before_parse] = ->(url) do
71+ url.sub! %r{\Aguide/}, '/guide/'
72+ url.sub! %r{\Atutorial/}, '/tutorial/'
73+ url.sub! %r{\Aapi/}, '/api/'
74+ url.sub! %r{\Agenerated/}, '/generated/'
75+ url
76+ end
77+
78+ include Docs::Angular::Common
79+ end
80+
81+ version '5' do
82+ self.release = '5.2.11'
83+ self.base_url = 'https://v5.angular.io/'
84+ self.root_path = 'docs'
85+
86+ html_filters.push 'angular/clean_html', 'angular/entries'
87+
88+ options[:follow_links] = false
89+ options[:only_patterns] = [/\Aguide/, /\Atutorial/, /\Aapi/]
90+ options[:fix_urls_before_parse] = ->(url) do
91+ url.sub! %r{\Aguide/}, '/guide/'
92+ url.sub! %r{\Atutorial/}, '/tutorial/'
93+ url.sub! %r{\Aapi/}, '/api/'
94+ url.sub! %r{\Agenerated/}, '/generated/'
95+ url
96+ end
97+
98+ include Docs::Angular::Common
99+ end
100+
101+ version '4' do
102+ self.release = '4.4.6'
103+ self.base_url = 'https://v4.angular.io/'
104+ self.root_path = 'docs'
105+
106+ html_filters.push 'angular/clean_html', 'angular/entries'
107+
108+ options[:follow_links] = false
109+ options[:only_patterns] = [/\Aguide/, /\Atutorial/, /\Aapi/]
110+ options[:fix_urls_before_parse] = ->(url) do
111+ url.sub! %r{\Aguide/}, '/guide/'
112+ url.sub! %r{\Atutorial/}, '/tutorial/'
113+ url.sub! %r{\Aapi/}, '/api/'
114+ url.sub! %r{\Agenerated/}, '/generated/'
115+ url
116+ end
117+
118+ include Docs::Angular::Common
119+ end
120+
73121 version '2' do
74122 self.release = '2.4.10'
75123 self.base_url = 'https://v2.angular.io/docs/ts/latest/'
@@ -107,6 +155,10 @@ module Docs · 31 unmodified lines
107155 end
108156 end
109157
158+ def get_latest_version(opts)
159+ get_npm_version('@angular/core', opts)
160+ end
161+
110162 private
111163
112164 def parse(response)
lib/docs/scrapers/angularjs.rb+16−3
@@ -31,10 +31,14 @@ module Docs · 30 unmodified lines
3131 end
3232
3333 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+ ]
3539
3640 options[:attribution] = <<-HTML
37− &copy; 2010&ndash;2017 Google, Inc.<br>
41+ &copy; 2010&ndash;2018 Google, Inc.<br>
3842 Licensed under the Creative Commons Attribution License 4.0.
3943 HTML
4044
@@ -45,8 +49,13 @@ module Docs · 4 unmodified lines
4549 capybara.execute_script("return document.querySelector('.side-navigation').innerHTML")
4650 end
4751
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+
4857 version '1.6' do
49− self.release = '1.6.5'
58+ self.release = '1.6.9'
5059 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
5160 end
5261
@@ -69,5 +78,9 @@ module Docs · 16 unmodified lines
6978 self.release = '1.2.32'
7079 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
7180 end
81+
82+ def get_latest_version(opts)
83+ get_npm_version('angular', opts)
84+ end
7285 end
7386 end
lib/docs/scrapers/ansible.rb+57−13
@@ -2,27 +2,71 @@ module Docs · 1 unmodified line
22 class Ansible < UrlScraper
33 self.name = 'Ansible'
44 self.type = 'sphinx'
5− self.release = '2.4.0'
6− self.base_url = 'https://docs.ansible.com/ansible/latest/'
75 self.links = {
86 home: 'https://www.ansible.com/',
97 code: 'https://github.com/ansible/ansible'
108 }
119
12− html_filters.push 'ansible/entries', 'ansible/clean_html', 'sphinx/clean_html'
13−
14− options[:skip] = %w(
15− glossary.html
16− faq.html
17− community.html
18− tower.html
19− quickstart.html
20− list_of_all_modules.html)
10+ html_filters.push 'ansible/entries', 'sphinx/clean_html', 'ansible/clean_html'
2111
2212 options[:attribution] = <<-HTML
23− &copy; 2012&ndash;2017 Michael DeHaan<br>
24− &copy; 2017 Red Hat, Inc.<br>
13+ &copy; 2012&ndash;2018 Michael DeHaan<br>
14+ &copy; 2018&ndash;2019 Red Hat, Inc.<br>
2515 Licensed under the GNU General Public License version 3.
2616 HTML
17+
18+ options[:skip] = %w(
19+ installation_guide/index.html
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)
26+
27+ options[:skip_patterns] = [
28+ /\Acommunity.*/i,
29+ /\Adev_guide.*/i,
30+ /\Aroadmap.*/i,
31+ ]
32+
33+ version '2.8' do
34+ self.release = '2.8.3'
35+ self.base_url = 'https://docs.ansible.com/ansible/2.7/'
36+ end
37+
38+ version '2.7' do
39+ self.release = '2.7.12'
40+ self.base_url = 'https://docs.ansible.com/ansible/2.7/'
41+ end
42+
43+ version '2.6' do
44+ self.release = '2.6.18'
45+ self.base_url = 'https://docs.ansible.com/ansible/2.6/'
46+ end
47+
48+ version '2.5' do
49+ self.release = '2.5.15'
50+ self.base_url = 'https://docs.ansible.com/ansible/2.5/'
51+ end
52+
53+ version '2.4' do
54+ self.release = '2.4.6'
55+ self.base_url = 'https://docs.ansible.com/ansible/2.4/'
56+
57+ options[:skip] = %w(
58+ glossary.html
59+ faq.html
60+ community.html
61+ tower.html
62+ quickstart.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
70+ end
2771 end
2872 end
lib/docs/scrapers/apache.rb+7−2
@@ -3,7 +3,7 @@ module Docs · 2 unmodified lines
33 self.name = 'Apache HTTP Server'
44 self.slug = 'apache_http_server'
55 self.type = 'apache'
6− self.release = '2.4.27'
6+ self.release = '2.4.37'
77 self.base_url = 'https://httpd.apache.org/docs/2.4/en/'
88 self.links = {
99 home: 'https://httpd.apache.org/'
@@ -30,8 +30,13 @@ module Docs · 20 unmodified lines
3030 /\Adeveloper\// ]
3131
3232 options[:attribution] = <<-HTML
33− &copy; 2017 The Apache Software Foundation<br>
33+ &copy; 2018 The Apache Software Foundation<br>
3434 Licensed under the Apache License, Version 2.0.
3535 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
3641 end
3742 end
lib/docs/scrapers/apache_pig.rb+6−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class ApachePig < UrlScraper
33 self.name = 'Apache Pig'
44 self.slug = 'apache_pig'
5− self.type = 'apache_pig'
5+ self.type = 'simple'
66 self.links = {
77 home: 'https://pig.apache.org/'
88 }
@@ -43,5 +43,10 @@ module Docs · 34 unmodified lines
4343 self.base_url = "https://pig.apache.org/docs/r#{release}/"
4444 end
4545
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
4651 end
4752 end
lib/docs/scrapers/async.rb+7−2
@@ -1,7 +1,7 @@
11 module Docs
22 class Async < UrlScraper
33 self.type = 'async'
4− self.release = '2.5.0'
4+ self.release = '2.6.1'
55 self.base_url = 'https://caolan.github.io/async/'
66 self.root_path = 'docs.html'
77 self.links = {
@@ -14,8 +14,13 @@ module Docs · 6 unmodified lines
1414 options[:skip_links] = true
1515
1616 options[:attribution] = <<-HTML
17− &copy; 2010&ndash;2017 Caolan McMahon<br>
17+ &copy; 2010&ndash;2018 Caolan McMahon<br>
1818 Licensed under the MIT License.
1919 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
2025 end
2126 end
lib/docs/scrapers/babel.rb+31−0
@@ -0,0 +1,31 @@
1+module Docs
2+ class Babel < UrlScraper
3+ self.type = 'simple'
4+ self.base_url = 'http://babeljs.io/docs/'
5+ self.release = '6.26.1'
6+ self.initial_paths = %w(core-packages/)
7+ self.links = {
8+ home: 'https://babeljs.io/',
9+ code: 'https://github.com/babel/babel'
10+ }
11+
12+ html_filters.push 'babel/clean_html', 'babel/entries'
13+
14+ options[:trailing_slash] = true
15+ options[:skip] = %w{setup/ editors/ community/videos/}
16+
17+ options[:attribution] = <<-HTML
18+ &copy; 2018 Sebastian McKenzie<br>
19+ Licensed under the MIT License.
20+ HTML
21+
22+ stub '' do
23+ '<div></div>'
24+ 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
30+ end
31+end
lib/docs/scrapers/backbone.rb+5−0
@@ -20,5 +20,10 @@ module Docs · 19 unmodified lines
2020 &copy; 2010&ndash;2016 Jeremy Ashkenas, DocumentCloud<br>
2121 Licensed under the MIT License.
2222 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
2328 end
2429 end
lib/docs/scrapers/bash.rb+26−0
@@ -0,0 +1,26 @@
1+module 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
26+end
lib/docs/scrapers/bluebird.rb+26−0
@@ -0,0 +1,26 @@
1+module Docs
2+ class Bluebird < UrlScraper
3+ self.type = 'simple'
4+ self.release = '3.5.1'
5+ self.base_url = 'http://bluebirdjs.com/docs/'
6+ self.root_path = 'api-reference.html'
7+ self.force_gzip = true
8+ self.links = {
9+ home: 'http://bluebirdjs.com/',
10+ code: 'https://github.com/petkaantonov/bluebird/'
11+ }
12+
13+ html_filters.push 'bluebird/clean_html', 'bluebird/entries'
14+
15+ options[:skip] = %w(support.html download-api-reference.html contribute.html)
16+
17+ options[:attribution] = <<-HTML
18+ &copy; 2013&ndash;2017 Petka Antonov<br>
19+ Licensed under the MIT License.
20+ HTML
21+
22+ def get_latest_version(opts)
23+ get_npm_version('bluebird', opts)
24+ end
25+ end
26+end
lib/docs/scrapers/bootstrap.rb+10−4
@@ -8,16 +8,17 @@ module Docs · 7 unmodified lines
88
99 options[:trailing_slash] = true
1010
11+ # https://github.com/twbs/bootstrap/blob/master/LICENSE
1112 options[:attribution] = <<-HTML
12− &copy; 2011&ndash;2017 Twitter, Inc.<br>
13− &copy; 2011&ndash;2017 The Bootstrap Authors<br>
13+ &copy; 2011&ndash;2019 Twitter, Inc.<br>
14+ &copy; 2011&ndash;2019 The Bootstrap Authors<br>
1415 Code licensed under the MIT License.<br>
1516 Documentation licensed under the Creative Commons Attribution License v3.0.
1617 HTML
1718
1819 version '4' do
19− self.release = '4.0.0-beta'
20− self.base_url = 'https://getbootstrap.com/docs/4.0/'
20+ self.release = '4.3.1'
21+ self.base_url = 'https://getbootstrap.com/docs/4.3/'
2122 self.root_path = 'getting-started/introduction/'
2223
2324 html_filters.push 'bootstrap/entries_v4', 'bootstrap/clean_html_v4'
@@ -34,5 +35,10 @@ module Docs · 10 unmodified lines
3435
3536 options[:only] = %w(getting-started/ css/ components/ javascript/)
3637 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
3743 end
3844 end
lib/docs/scrapers/bottle.rb+6−0
@@ -27,5 +27,11 @@ module Docs · 26 unmodified lines
2727 self.release = '0.11.7'
2828 self.base_url = "https://bottlepy.org/docs/#{self.version}/"
2929 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
3036 end
3137 end
lib/docs/scrapers/bower.rb+7−3
@@ -1,8 +1,8 @@
11 module Docs
22 class Bower < UrlScraper
33 self.name = 'Bower'
4− self.type = 'bower'
5− self.release = '1.8.2'
4+ self.type = 'simple'
5+ self.release = '1.8.4'
66 self.base_url = 'https://bower.io/docs/'
77 self.root_path = 'api'
88 self.links = {
@@ -16,8 +16,12 @@ module Docs · 7 unmodified lines
1616 options[:skip] = %w(tools about)
1717
1818 options[:attribution] = <<-HTML
19− &copy; 2017 Bower contributors<br>
19+ &copy; 2018 Bower contributors<br>
2020 Licensed under the MIT License.
2121 HTML
22+
23+ def get_latest_version(opts)
24+ get_npm_version('bower', opts)
25+ end
2226 end
2327 end
lib/docs/scrapers/c.rb+14−1
@@ -1,7 +1,6 @@
11 module Docs
22 class C < FileScraper
33 self.type = 'c'
4− self.dir = '/Users/Thibaut/DevDocs/Docs/C/c'
54 self.base_url = 'http://en.cppreference.com/w/c/'
65 self.root_path = 'header.html'
76
@@ -9,6 +8,7 @@ module Docs · 1 unmodified line
98 html_filters.push 'c/entries', 'c/clean_html', 'title'
109 text_filters.push 'c/fix_urls'
1110
11+ options[:decode_and_clean_paths] = true
1212 options[:container] = '#content'
1313 options[:title] = false
1414 options[:root_title] = 'C Programming Language'
@@ -25,5 +25,18 @@ module Docs · 10 unmodified lines
2525 &copy; cppreference.com<br>
2626 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
2727 HTML
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+
36+ private
37+
38+ def file_path_for(*)
39+ URI.unescape(super)
40+ end
2841 end
2942 end
lib/docs/scrapers/cakephp.rb+12−2
@@ -15,14 +15,19 @@ module Docs · 14 unmodified lines
1515 options[:skip_patterns] = [/\Asource-/]
1616
1717 options[:attribution] = <<-HTML
18− &copy; 2005&ndash;2017 The Cake Software Foundation, Inc.<br>
18+ &copy; 2005&ndash;2018 The Cake Software Foundation, Inc.<br>
1919 Licensed under the MIT License.<br>
2020 CakePHP is a registered trademark of Cake Software Foundation, Inc.<br>
2121 We are not endorsed by or affiliated with CakePHP.
2222 HTML
2323
24+ version '3.6' do
25+ self.release = '3.6.2'
26+ self.base_url = 'https://api.cakephp.org/3.6/'
27+ end
28+
2429 version '3.5' do
25− self.release = '3.5.2'
30+ self.release = '3.5.15'
2631 self.base_url = 'https://api.cakephp.org/3.5/'
2732 end
2833
@@ -66,6 +71,11 @@ module Docs · 37 unmodified lines
6671 self.base_url = 'https://api.cakephp.org/2.7/'
6772 end
6873
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+
6979 private
7080
7181 def parse(response)
lib/docs/scrapers/chai.rb+5−1
@@ -1,7 +1,7 @@
11 module Docs
22 class Chai < UrlScraper
33 self.name = 'Chai'
4− self.type = 'chai'
4+ self.type = 'simple'
55 self.release = '4.1.0'
66 self.base_url = 'http://chaijs.com'
77 self.root_path = '/api/'
@@ -23,5 +23,9 @@ module Docs · 15 unmodified lines
2323 &copy; 2016 Chai.js Assertion Library<br>
2424 Licensed under the MIT License.
2525 HTML
26+
27+ def get_latest_version(opts)
28+ get_npm_version('chai', opts)
29+ end
2630 end
2731 end
lib/docs/scrapers/chef.rb+5−0
@@ -47,5 +47,10 @@ module Docs · 46 unmodified lines
4747
4848 options[:only_patterns] = [/\A#{client_path}\//, /\A#{server_path}\//]
4949 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
5055 end
5156 end
lib/docs/scrapers/clojure.rb+18−3
@@ -13,14 +13,29 @@ module Docs · 12 unmodified lines
1313 Licensed under the Eclipse Public License 1.0.
1414 HTML
1515
16− version '1.8' do
17− self.release = '1.8'
16+ version '1.10' do
17+ self.release = '1.10 (stable)'
1818 self.base_url = 'https://clojure.github.io/clojure/'
1919 end
2020
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+
26+ version '1.8' do
27+ self.release = '1.8 (legacy)'
28+ self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.8.0/'
29+ end
30+
2131 version '1.7' do
22− self.release = '1.7'
32+ self.release = '1.7 (legacy)'
2333 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'
2434 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
2540 end
2641 end
lib/docs/scrapers/cmake.rb+44−8
@@ -13,36 +13,72 @@ module Docs · 12 unmodified lines
1313 options[:title] = false
1414 options[:root_title] = 'CMake Reference Documentation'
1515 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/]
1717
1818 options[:attribution] = <<-HTML
19− &copy; 2000&ndash;2017 Kitware, Inc. and Contributors<br>
19+ &copy; 2000&ndash;2019 Kitware, Inc. and Contributors<br>
2020 Licensed under the BSD 3-clause License.
2121 HTML
2222
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+
48+ version '3.10' do
49+ self.release = '3.10.3'
50+ self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
51+ end
52+
2353 version '3.9' do
24− self.release = '3.9.0'
25− self.base_url = 'https://cmake.org/cmake/help/v3.9/'
54+ self.release = '3.9.6'
55+ self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
2656 end
2757
2858 version '3.8' do
2959 self.release = '3.8.2'
30− self.base_url = 'https://cmake.org/cmake/help/v3.8/'
60+ self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
3161 end
3262
3363 version '3.7' do
3464 self.release = '3.7.2'
35− self.base_url = 'https://cmake.org/cmake/help/v3.7/'
65+ self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
3666 end
3767
3868 version '3.6' do
3969 self.release = '3.6.3'
40− self.base_url = 'https://cmake.org/cmake/help/v3.6/'
70+ self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
4171 end
4272
4373 version '3.5' do
4474 self.release = '3.5.2'
45− 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]
4682 end
4783 end
4884 end
lib/docs/scrapers/codeception.rb+9−4
@@ -2,11 +2,11 @@ module Docs · 1 unmodified line
22 class Codeception < UrlScraper
33 self.name = 'Codeception'
44 self.type = 'codeception'
5− self.release = '2.3.5'
6− self.base_url = 'http://codeception.com/docs/'
5+ self.release = '2.5.1'
6+ self.base_url = 'https://codeception.com/docs/'
77 self.root_path = 'index.html'
88 self.links = {
9− home: 'http://codeception.com/',
9+ home: 'https://codeception.com/',
1010 code: 'https://github.com/codeception/codeception'
1111 }
1212
@@ -15,8 +15,13 @@ module Docs · 2 unmodified lines
1515 options[:skip_patterns] = [/install/]
1616
1717 options[:attribution] = <<-HTML
18− &copy; 2011&ndash;2017 Michael Bodnarchuk and contributors<br>
18+ &copy; 2011 Michael Bodnarchuk and contributors<br>
1919 Licensed under the MIT License.
2020 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
2126 end
2227 end
lib/docs/scrapers/codeceptjs.rb+8−4
@@ -1,12 +1,12 @@
11 module Docs
22 class Codeceptjs < UrlScraper
33 self.name = 'CodeceptJS'
4− self.type = 'codeceptjs'
4+ self.type = 'simple'
55 self.root_path = 'index.html'
6− self.release = '1.0.1'
7− self.base_url = 'http://codecept.io/'
6+ self.release = '1.4.4'
7+ self.base_url = 'https://codecept.io/'
88 self.links = {
9− home: 'http://codecept.io/',
9+ home: 'https://codecept.io/',
1010 code: 'https://github.com/codeception/codeceptjs'
1111 }
1212
@@ -21,5 +21,9 @@ module Docs · 8 unmodified lines
2121 &copy; 2015 DavertMik &lt;davert@codegyre.com&gt; (http://codegyre.com)<br>
2222 Licensed under the MIT License.
2323 HTML
24+
25+ def get_latest_version(opts)
26+ get_npm_version('codeceptjs', opts)
27+ end
2428 end
2529 end
lib/docs/scrapers/codeigniter.rb+8−2
@@ -31,12 +31,18 @@ module Docs · 30 unmodified lines
3131 ]
3232
3333 options[:attribution] = <<-HTML
34− &copy; 2014&ndash;2017 British Columbia Institute of Technology<br>
34+ &copy; 2014&ndash;2018 British Columbia Institute of Technology<br>
3535 Licensed under the MIT License.
3636 HTML
3737
3838 version '3' do
39− self.release = '3.1.5'
39+ self.release = '3.1.8'
40+ 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]
4046 end
4147 end
4248 end
lib/docs/scrapers/coffeescript.rb+6−2
@@ -11,12 +11,12 @@ module Docs · 10 unmodified lines
1111 options[:skip_links] = true
1212
1313 options[:attribution] = <<-HTML
14− &copy; 2009&ndash;2017 Jeremy Ashkenas<br>
14+ &copy; 2009&ndash;2018 Jeremy Ashkenas<br>
1515 Licensed under the MIT License.
1616 HTML
1717
1818 version '2' do
19− self.release = '2.0.1'
19+ self.release = '2.3.0'
2020 self.base_url = 'http://coffeescript.org/'
2121
2222 html_filters.push 'coffeescript/entries', 'coffeescript/clean_html', 'title'
@@ -30,5 +30,9 @@ module Docs · 7 unmodified lines
3030
3131 options[:container] = '.container'
3232 end
33+
34+ def get_latest_version(opts)
35+ get_npm_version('coffeescript', opts)
36+ end
3337 end
3438 end
lib/docs/scrapers/cordova.rb+17−2
@@ -24,12 +24,17 @@ module Docs · 23 unmodified lines
2424 end
2525
2626 options[:attribution] = <<-HTML
27− &copy; 2012&ndash;2017 The Apache Software Foundation<br>
27+ &copy; 2012&ndash;2018 The Apache Software Foundation<br>
2828 Licensed under the Apache License 2.0.
2929 HTML
3030
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+
3136 version '7' do
32− self.release = '7.0.1'
37+ self.release = '7.1.0'
3338 self.base_url = 'https://cordova.apache.org/docs/en/7.x/'
3439 end
3540
@@ -37,5 +42,15 @@ module Docs · 1 unmodified line
3742 self.release = '6.5.0'
3843 self.base_url = 'https://cordova.apache.org/docs/en/6.x/'
3944 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
4055 end
4156 end
lib/docs/scrapers/cpp.rb+15−1
@@ -3,7 +3,6 @@ module Docs · 2 unmodified lines
33 self.name = 'C++'
44 self.slug = 'cpp'
55 self.type = 'c'
6− self.dir = '/Users/Thibaut/DevDocs/Docs/C/cpp'
76 self.base_url = 'http://en.cppreference.com/w/cpp/'
87 self.root_path = 'header.html'
98
@@ -11,6 +10,7 @@ module Docs · 1 unmodified line
1110 html_filters.push 'cpp/entries', 'c/clean_html', 'title'
1211 text_filters.push 'cpp/fix_urls'
1312
13+ options[:decode_and_clean_paths] = true
1414 options[:container] = '#content'
1515 options[:title] = false
1616 options[:root_title] = 'C++ Programming Language'
@@ -33,5 +33,19 @@ module Docs · 16 unmodified lines
3333 &copy; cppreference.com<br>
3434 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
3535 HTML
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+
45+ private
46+
47+ def file_path_for(*)
48+ URI.unescape(super)
49+ end
3650 end
3751 end
lib/docs/scrapers/crystal.rb+7−2
@@ -1,7 +1,7 @@
11 module Docs
22 class Crystal < UrlScraper
33 self.type = 'crystal'
4− self.release = '0.23.0'
4+ self.release = '0.27.0'
55 self.base_url = 'https://crystal-lang.org/'
66 self.root_path = "api/#{release}/index.html"
77 self.initial_paths = %w(docs/index.html)
@@ -29,10 +29,15 @@ module Docs · 21 unmodified lines
2929 HTML
3030 else
3131 <<-HTML
32− &copy; 2012&ndash;2017 Manas Technology Solutions.<br>
32+ &copy; 2012&ndash;2018 Manas Technology Solutions.<br>
3333 Licensed under the Apache License, Version 2.0.
3434 HTML
3535 end
3636 }
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
3742 end
3843 end
lib/docs/scrapers/d.rb+7−2
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class D < UrlScraper
33 include MultipleBaseUrls
44
5− self.release = '2.076.0'
5+ self.release = '2.083.0'
66 self.type = 'd'
77 self.base_urls = ['https://dlang.org/phobos/', 'https://dlang.org/spec/']
88 self.root_path = 'index.html'
@@ -19,12 +19,17 @@ module Docs · 10 unmodified lines
1919 options[:title] = false
2020
2121 options[:attribution] = <<-HTML
22− &copy; 1999&ndash;2017 The D Language Foundation<br>
22+ &copy; 1999&ndash;2018 The D Language Foundation<br>
2323 Licensed under the Boost License 1.0.
2424 HTML
2525
2626 def initial_urls
2727 %w(https://dlang.org/phobos/index.html https://dlang.org/spec/intro.html)
2828 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
2934 end
3035 end
lib/docs/scrapers/d3.rb+22−2
@@ -12,12 +12,28 @@ module Docs · 11 unmodified lines
1212 options[:container] = '.markdown-body'
1313
1414 options[:attribution] = <<-HTML
15− &copy; 2010&ndash;2017 Michael Bostock<br>
15+ &copy; 2010&ndash;2018 Michael Bostock<br>
1616 Licensed under the BSD License.
1717 HTML
1818
19+ version '5' do
20+ self.release = '5.7.0'
21+ self.base_url = 'https://github.com/d3/'
22+ self.root_path = 'd3/blob/master/API.md'
23+
24+ html_filters.push 'd3/clean_html', 'd3/entries_v4'
25+
26+ options[:only_patterns] = [/\Ad3[\-\w]+\z/, /\Ad3\/blob\/master\/changes\.md\z/i]
27+ options[:skip_patterns] = [/3\.x-api-reference/]
28+
29+ options[:fix_urls] = ->(url) do
30+ url.sub! %r{/blob/master/readme.md}i, ''
31+ url
32+ end
33+ end
34+
1935 version '4' do
20− self.release = '4.11.0'
36+ self.release = '4.12.2'
2137 self.base_url = 'https://github.com/d3/'
2238 self.root_path = 'd3/blob/master/API.md'
2339
@@ -42,5 +58,9 @@ module Docs · 18 unmodified lines
4258 options[:root_title] = 'D3.js'
4359 options[:only_patterns] = [/\.md\z/]
4460 end
61+
62+ def get_latest_version(opts)
63+ get_npm_version('d3', opts)
64+ end
4565 end
4666 end
lib/docs/scrapers/dart.rb+41−0
@@ -0,0 +1,41 @@
1+module 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
41+end
lib/docs/scrapers/django.rb+21−5
@@ -34,28 +34,44 @@ module Docs · 33 unmodified lines
3434 Licensed under the BSD License.
3535 HTML
3636
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+
47+ version '2.0' do
48+ self.release = '2.0.13'
49+ self.base_url = 'https://docs.djangoproject.com/en/2.0/'
50+ end
51+
3752 version '1.11' do
38− self.release = '1.11.5'
39− self.dir = '/Users/Thibaut/DevDocs/Docs/Django111'
53+ self.release = '1.11.23'
4054 self.base_url = 'https://docs.djangoproject.com/en/1.11/'
4155 end
4256
4357 version '1.10' do
4458 self.release = '1.10.8'
45− self.dir = '/Users/Thibaut/DevDocs/Docs/Django110'
4659 self.base_url = 'https://docs.djangoproject.com/en/1.10/'
4760 end
4861
4962 version '1.9' do
5063 self.release = '1.9.13'
51− self.dir = '/Users/Thibaut/DevDocs/Docs/Django19'
5264 self.base_url = 'https://docs.djangoproject.com/en/1.9/'
5365 end
5466
5567 version '1.8' do
5668 self.release = '1.8.18'
57− self.dir = '/Users/Thibaut/DevDocs/Docs/Django18'
5869 self.base_url = 'https://docs.djangoproject.com/en/1.8/'
5970 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
6076 end
6177 end
lib/docs/scrapers/docker.rb+154−27
@@ -1,6 +1,6 @@
11 module Docs
22 class Docker < UrlScraper
3− self.type = 'docker'
3+ self.type = 'simple'
44 self.links = {
55 home: 'https://docker.com/',
66 code: 'https://github.com/docker/docker'
@@ -15,15 +15,15 @@ module Docs · 8 unmodified lines
1515 end
1616
1717 options[:attribution] = <<-HTML
18− &copy; 2017 Docker, Inc.<br>
18+ &copy; 2019 Docker, Inc.<br>
1919 Licensed under the Apache License, Version 2.0.<br>
2020 Docker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries.<br>
2121 Docker, Inc. and other parties may also have trademark rights in other terms used herein.
2222 HTML
2323
24− version '17' do
25− self.release = '17.06'
26− self.base_url = 'https://docs.docker.com/'
24+ version '19' do
25+ self.release = '19.03'
26+ self.base_url = "https://docs.docker.com/"
2727
2828 html_filters.push 'docker/entries', 'docker/clean_html'
2929
@@ -33,29 +33,150 @@ module Docs · 3 unmodified lines
3333 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]
3434
3535 options[:replace_paths] = {
36− 'engine/installation/linux/docker-ee/linux-postinstall/' => 'engine/installation/linux/linux-postinstall/',
37− 'engine/installation/linux/docker-ee/' => 'engine/installation/',
38− 'engine/installation/linux/docker-ce/' => 'engine/installation/',
39− 'engine/installation/linux/' => 'engine/installation/',
40− 'engine/installation/windows/' => 'engine/installation/',
41− 'engine/userguide/intro/' => 'engine/userguide/',
42− 'engine/tutorials/dockervolumes/' => 'engine/admin/volumes/volumes/',
43− 'engine/getstarted/' => 'get-started/',
44− 'engine/tutorials/dockerimages/' => 'get-started/',
45− 'engine/tutorials/dockerrepos/' => 'get-started/',
46− 'engine/admin/host_integration/' => 'engine/admin/start-containers-automatically/',
47− 'engine/installation/linux/rhel/' => 'engine/installation/linux/docker-ee/rhel/',
48− 'engine/installation/linux/ubuntulinux/' => 'engine/installation/linux/docker-ee/ubuntu/',
49− 'engine/installation/linux/suse/' => 'engine/installation/linux/docker-ee/suse/',
50− 'engine/admin/logging/' => 'engine/admin/logging/view_container_logs/',
51− 'engine/swarm/how-swarm-mode-works/' => 'engine/swarm/how-swarm-mode-works/nodes/',
52− 'engine/installation/binaries/' => 'engine/installation/linux/docker-ce/binaries/',
36+ 'install/linux/ubuntu/' => 'install/linux/docker-ce',
37+ 'get-started/part1' => 'get-started',
38+ 'engine/installation/' => 'install',
39+ 'engine/installation/linux/linux-postinstall/' => 'install/linux',
40+ 'compose/overview/' => 'compose',
41+ 'docker-cloud/' => 'docker-hub',
42+ 'datacenter/install/linux/' => 'ee',
43+ 'engine/userguide/' => 'config/daemon',
44+ 'engine/admin/' => 'config/daemon',
45+ 'opensource/get-help/' => 'opensource',
46+ 'engine/tutorials/dockerimages/' => 'get-started',
47+ 'engine/admin/volumes/bind-mounts/' => 'storage',
48+ 'engine/tutorials/dockervolumes/' => 'storage',
49+ 'engine/admin/volumes/volumes/' => 'storage',
50+ 'engine/userguide/labels-custom-metadata/' => 'config',
51+ 'engine/userguide/eng-image/multistage-build/' => 'develop/develop-images',
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',
5374 'engine/reference/commandline/' => 'engine/reference/commandline/docker/',
54− 'engine/reference/api/' => 'engine/api/',
55− 'engine/userguide/dockervolumes/' => 'engine/admin/volumes/volumes/',
56− 'engine/understanding-docker/' => 'engine/docker-overview/',
57− 'engine/reference/commandline/swarm_join_token/' => 'engine/reference/commandline/swarm_join-token/',
58− 'engine/api/getting-started/' => 'engine/api/get-started/',
75+ }
76+ end
77+
78+ version '18' do
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',
59180 }
60181 end
61182
@@ -137,5 +258,11 @@ module Docs · 75 unmodified lines
137258 options[:container] = '#docs'
138259 options[:only_patterns] << /\Aswarm\//
139260 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
140267 end
141268 end
lib/docs/scrapers/dojo.rb+5−0
@@ -36,6 +36,11 @@ module Docs · 35 unmodified lines
3636 urls.map { |url| "<a href='#{url}'>#{url}</a>" }.join
3737 end
3838
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+
3944 private
4045
4146 def get_url_list(json, set = Set.new)
lib/docs/scrapers/drupal.rb+5−0
@@ -98,5 +98,10 @@ module Docs · 97 unmodified lines
9898 /\A[\w\-\.]+\.php\/7\.x\z/
9999 ]
100100 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
101106 end
102107 end
lib/docs/scrapers/electron.rb+14−9
@@ -1,26 +1,31 @@
11 module Docs
22 class Electron < UrlScraper
3− self.type = 'electron'
4− self.base_url = 'https://electron.atom.io/docs/'
5− self.release = '1.7.5'
3+ self.type = 'simple'
4+ self.base_url = 'https://electronjs.org/docs'
5+ self.release = '2.0.2'
66 self.links = {
7− home: 'https://electron.atom.io/',
7+ home: 'https://electronjs.org/',
88 code: 'https://github.com/electron/electron'
99 }
1010
1111 html_filters.push 'electron/clean_html', 'electron/entries'
1212
13− options[:trailing_slash] = true
13+ options[:trailing_slash] = false
1414 options[:container] = '.page-section > .container, .page-section > .container-narrow'
15− options[:skip] = %w(guides/ development/ tutorial/ versions/ all/)
15+ options[:skip] = %w(guides development tutorial versions all)
16+ options[:skip_patterns] = [/\/history\z/]
1617 options[:replace_paths] = {
17− 'api/web-view-tag/' => 'api/webview-tag/',
18− 'api/web-view-tag' => 'api/webview-tag/'
18+ 'api/web-view-tag' => 'api/webview-tag'
1919 }
2020
2121 options[:attribution] = <<-HTML
22− &copy; 2013&ndash;2017 GitHub Inc.<br>
22+ &copy; 2013&ndash;2018 GitHub Inc.<br>
2323 Licensed under the MIT license.
2424 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
2530 end
2631 end
lib/docs/scrapers/elixir.rb+59−2
@@ -19,7 +19,7 @@ module Docs · 18 unmodified lines
1919 options[:root_title] = 'Elixir'
2020
2121 options[:attribution] = <<-HTML
22− &copy; 2012&ndash;2017 Plataformatec<br>
22+ &copy; 2012 Plataformatec<br>
2323 Licensed under the Apache License, Version 2.0.
2424 HTML
2525
@@ -33,8 +33,60 @@ module Docs · 7 unmodified lines
3333 "https://elixir-lang.org/getting-started/introduction.html" ]
3434 end
3535
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+
75+ version '1.6' do
76+ self.release = '1.6.6'
77+ self.base_urls = [
78+ "https://hexdocs.pm/elixir/#{release}/",
79+ "https://hexdocs.pm/eex/#{release}/",
80+ "https://hexdocs.pm/ex_unit/#{release}/",
81+ "https://hexdocs.pm/iex/#{release}/",
82+ "https://hexdocs.pm/logger/#{release}/",
83+ "https://hexdocs.pm/mix/#{release}/",
84+ 'https://elixir-lang.org/getting-started/'
85+ ]
86+ end
87+
3688 version '1.5' do
37− self.release = '1.5.2'
89+ self.release = '1.5.3'
3890 self.base_urls = [
3991 "https://hexdocs.pm/elixir/#{release}/",
4092 "https://hexdocs.pm/eex/#{release}/",
@@ -71,5 +123,10 @@ module Docs · 30 unmodified lines
71123 'https://elixir-lang.org/getting-started/'
72124 ]
73125 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
74131 end
75132 end
lib/docs/scrapers/ember.rb+5−0
@@ -56,5 +56,10 @@ module Docs · 55 unmodified lines
5656 https://emberjs.com/api/ember-data/2.14/classes/DS
5757 )
5858 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
5964 end
6065 end
lib/docs/scrapers/erlang.rb+10−4
@@ -40,19 +40,25 @@ module Docs · 39 unmodified lines
4040 Licensed under the Apache License, Version 2.0.
4141 HTML
4242
43+ version '21' do
44+ self.release = '21.0'
45+ end
46+
4347 version '20' do
44− self.release = '20.1'
45− self.dir = '/Users/Thibaut/DevDocs/Docs/Erlang20'
48+ self.release = '20.3'
4649 end
4750
4851 version '19' do
4952 self.release = '19.3'
50− self.dir = '/Users/Thibaut/DevDocs/Docs/Erlang19'
5153 end
5254
5355 version '18' do
5456 self.release = '18.3'
55− 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 /, '')
5662 end
5763 end
5864 end
lib/docs/scrapers/eslint.rb+28−0
@@ -0,0 +1,28 @@
1+module Docs
2+ class Eslint < UrlScraper
3+ self.name = 'ESLint'
4+ self.type = 'simple'
5+ self.release = '5.8.0'
6+ self.base_url = 'https://eslint.org/docs/'
7+ self.root_path = 'user-guide/getting-started'
8+ self.links = {
9+ home: 'https://eslint.org/',
10+ code: 'https://github.com/eslint/eslint'
11+ }
12+
13+ html_filters.push 'eslint/entries', 'eslint/clean_html'
14+
15+ options[:skip_patterns] = [/maintainer-guide/]
16+ options[:skip] = %w(about about/ rules)
17+ options[:replace_paths] = { 'user-guide' => 'user-guide/' }
18+
19+ options[:attribution] = <<-HTML
20+ &copy; JS Foundation and other contributors<br>
21+ Licensed under the MIT License.
22+ HTML
23+
24+ def get_latest_version(opts)
25+ get_npm_version('eslint', opts)
26+ end
27+ end
28+end
lib/docs/scrapers/express.rb+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class Express < UrlScraper
33 self.name = 'Express'
44 self.type = 'express'
5− self.release = '4.16.1'
5+ self.release = '4.16.3'
66 self.base_url = 'http://expressjs.com/en/'
77 self.root_path = '4x/api.html'
88 self.initial_paths = %w(
@@ -28,5 +28,9 @@ module Docs · 19 unmodified lines
2828 &copy; 2017 StrongLoop, IBM, and other expressjs.com contributors.<br>
2929 Licensed under the Creative Commons Attribution-ShareAlike License v3.0.
3030 HTML
31+
32+ def get_latest_version(opts)
33+ get_npm_version('express', opts)
34+ end
3135 end
3236 end
lib/docs/scrapers/falcon.rb+11−1
@@ -15,10 +15,15 @@ module Docs · 14 unmodified lines
1515 options[:skip_patterns] = [/\Achanges/, /\A_modules/, /\Acommunity/]
1616
1717 options[:attribution] = <<-HTML
18− &copy; 2012&ndash;2016 by Rackspace Hosting, Inc. and other contributors<br>
18+ &copy; 2012&ndash;2017 by Rackspace Hosting, Inc. and other contributors<br>
1919 Licensed under the Apache License, Version 2.0.
2020 HTML
2121
22+ version '1.4' do
23+ self.release = '1.4.1'
24+ self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"
25+ end
26+
2227 version '1.3' do
2328 self.release = '1.3.0'
2429 self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"
@@ -28,5 +33,10 @@ module Docs · 3 unmodified lines
2833 self.release = '1.2.0'
2934 self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"
3035 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
3141 end
3242 end
lib/docs/scrapers/fish.rb+17−1
@@ -1,7 +1,7 @@
11 module Docs
22 class Fish < UrlScraper
33 self.name = 'Fish'
4− self.type = 'fish'
4+ self.type = 'simple'
55 self.root_path = 'index.html'
66 self.links = {
77 home: 'https://fishshell.com/',
@@ -12,11 +12,22 @@ module Docs · 4 unmodified lines
1212
1313 options[:skip] = %w(design.html license.html)
1414
15+ # https://fishshell.com/docs/current/license.html
1516 options[:attribution] = <<-HTML
1617 &copy; 2005&ndash;2009 Axel Liljencrantz<br>
1718 Licensed under the GNU General Public License, version 2.
1819 HTML
1920
21+ version '3.0' do
22+ self.release = '3.0.1'
23+ self.base_url = "https://fishshell.com/docs/#{version}/"
24+ end
25+
26+ version '2.7' do
27+ self.release = '2.7.1'
28+ self.base_url = "https://fishshell.com/docs/#{version}/"
29+ end
30+
2031 version '2.6' do
2132 self.release = '2.6.0'
2233 self.base_url = "https://fishshell.com/docs/#{version}/"
@@ -41,5 +52,10 @@ module Docs · 18 unmodified lines
4152 self.release = '2.2.0'
4253 self.base_url = "https://fishshell.com/docs/#{version}/"
4354 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
4460 end
4561 end
lib/docs/scrapers/flow.rb+7−3
@@ -1,7 +1,7 @@
11 module Docs
22 class Flow < UrlScraper
3− self.type = 'flow'
4− self.release = '0.56.0'
3+ self.type = 'simple'
4+ self.release = '0.85.0'
55 self.base_url = 'https://flow.org/en/docs/'
66 self.links = {
77 home: 'https://flow.org/',
@@ -16,7 +16,11 @@ module Docs · 8 unmodified lines
1616
1717 options[:attribution] = <<-HTML
1818 &copy; 2013&ndash;present Facebook Inc.<br>
19− Licensed under the BSD License.
19+ Licensed under the MIT License.
2020 HTML
21+
22+ def get_latest_version(opts)
23+ get_npm_version('flow-bin', opts)
24+ end
2125 end
2226 end
lib/docs/scrapers/git.rb+7−2
@@ -1,7 +1,7 @@
11 module Docs
22 class Git < UrlScraper
33 self.type = 'git'
4− self.release = '2.14.1'
4+ self.release = '2.19.1'
55 self.base_url = 'https://git-scm.com/docs'
66 self.initial_paths = %w(/git.html)
77 self.links = {
@@ -16,8 +16,13 @@ module Docs · 8 unmodified lines
1616 options[:skip] = %w(/howto-index.html)
1717
1818 options[:attribution] = <<-HTML
19− &copy; 2005&ndash;2017 Linus Torvalds and others<br>
19+ &copy; 2005&ndash;2018 Linus Torvalds and others<br>
2020 Licensed under the GNU General Public License version 2.
2121 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
2227 end
2328 end
lib/docs/scrapers/gnu.rb+1−1
@@ -1,6 +1,6 @@
11 module Docs
22 class Gnu < FileScraper
3− self.type = 'gnu'
3+ self.type = 'simple'
44 self.root_path = 'index.html'
55 self.abstract = 'true'
66
lib/docs/scrapers/gnu/gcc.rb+8−10
@@ -47,14 +47,12 @@ module Docs · 46 unmodified lines
4747 }
4848
4949 version '7' do
50− self.release = '7.2.0'
51− self.dir = '/Users/Thibaut/DevDocs/Docs/gcc7'
50+ self.release = '7.3.0'
5251 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
5352 end
5453
5554 version '7 CPP' do
56− self.release = '7.2.0'
57− self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp7'
55+ self.release = '7.3.0'
5856 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
5957
6058 options[:replace_paths] = CPP_PATHS
@@ -62,7 +60,6 @@ module Docs · 1 unmodified line
6260
6361 version '6' do
6462 self.release = '6.4.0'
65− self.dir = '/Users/Thibaut/DevDocs/Docs/gcc6'
6663 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
6764
6865 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -70,7 +67,6 @@ module Docs · 1 unmodified line
7067
7168 version '6 CPP' do
7269 self.release = '6.4.0'
73− self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp6'
7470 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
7571
7672 options[:replace_paths] = CPP_PATHS
@@ -78,7 +74,6 @@ module Docs · 1 unmodified line
7874
7975 version '5' do
8076 self.release = '5.4.0'
81− self.dir = '/Users/Thibaut/DevDocs/Docs/gcc5'
8277 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
8378
8479 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -86,7 +81,6 @@ module Docs · 1 unmodified line
8681
8782 version '5 CPP' do
8883 self.release = '5.4.0'
89− self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp5'
9084 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
9185
9286 options[:replace_paths] = CPP_PATHS
@@ -94,7 +88,6 @@ module Docs · 1 unmodified line
9488
9589 version '4' do
9690 self.release = '4.9.3'
97− self.dir = '/Users/Thibaut/DevDocs/Docs/gcc4'
9891 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
9992
10093 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -102,10 +95,15 @@ module Docs · 1 unmodified line
10295
10396 version '4 CPP' do
10497 self.release = '4.9.3'
105− self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp4'
10698 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
10799
108100 options[:replace_paths] = CPP_PATHS
109101 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
110108 end
111109 end
lib/docs/scrapers/gnu/gnu_fortran.rb+7−5
@@ -7,27 +7,29 @@ module Docs · 6 unmodified lines
77 }
88
99 version '7' do
10− self.release = '7.2.0'
11− self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran7'
10+ self.release = '7.3.0'
1211 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
1312 end
1413
1514 version '6' do
1615 self.release = '6.4.0'
17− self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran6'
1816 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
1917 end
2018
2119 version '5' do
2220 self.release = '5.4.0'
23− self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran5'
2421 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
2522 end
2623
2724 version '4' do
2825 self.release = '4.9.3'
29− self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran4'
3026 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
3127 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
3234 end
3335 end
lib/docs/scrapers/go.rb+11−1
@@ -1,7 +1,7 @@
11 module Docs
22 class Go < UrlScraper
33 self.type = 'go'
4− self.release = '1.9.0'
4+ self.release = '1.12'
55 self.base_url = 'https://golang.org/pkg/'
66 self.links = {
77 home: 'https://golang.org/',
@@ -13,12 +13,22 @@ module Docs · 5 unmodified lines
1313 options[:trailing_slash] = true
1414 options[:container] = '#page .container'
1515 options[:skip] = %w(runtime/msan/)
16+ options[:skip_patterns] = [/\/\//]
17+
18+ options[:fix_urls] = ->(url) do
19+ url.sub 'https://golang.org/pkg//', 'https://golang.org/pkg/'
20+ end
1621
1722 options[:attribution] = <<-HTML
1823 &copy; Google, Inc.<br>
1924 Licensed under the Creative Commons Attribution License 3.0.
2025 HTML
2126
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+
2232 private
2333
2434 def parse(response) # Hook here because Nokogori removes whitespace from textareas
lib/docs/scrapers/godot.rb+12−2
@@ -17,20 +17,30 @@ module Docs · 16 unmodified lines
1717 options[:attribution] = ->(filter) do
1818 if filter.subpath.start_with?('classes')
1919 <<-HTML
20− &copy; 2014&ndash;2017 Juan Linietsky, Ariel Manzur, Godot Engine contributors<br>
20+ &copy; 2014&ndash;2018 Juan Linietsky, Ariel Manzur, Godot Engine contributors<br>
2121 Licensed under the MIT License.
2222 HTML
2323 else
2424 <<-HTML
25− &copy; 2014&ndash;2017 Juan Linietsky, Ariel Manzur and the Godot community<br>
25+ &copy; 2014&ndash;2018 Juan Linietsky, Ariel Manzur and the Godot community<br>
2626 Licensed under the Creative Commons Attribution Unported License v3.0.
2727 HTML
2828 end
2929 end
3030
31+ version '3.0' do
32+ self.release = '3.0.6'
33+ self.base_url = "http://docs.godotengine.org/en/#{self.version}/"
34+ end
35+
3136 version '2.1' do
3237 self.release = '2.1'
3338 self.base_url = "http://docs.godotengine.org/en/#{self.version}/"
3439 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
3545 end
3646 end
lib/docs/scrapers/graphite.rb+26−0
@@ -0,0 +1,26 @@
1+module 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
26+end
lib/docs/scrapers/grunt.rb+5−1
@@ -1,7 +1,7 @@
11 module Docs
22 class Grunt < UrlScraper
33 self.name = 'Grunt'
4− self.type = 'grunt'
4+ self.type = 'simple'
55 self.release = '1.0.1'
66 self.base_url = 'https://gruntjs.com/'
77 self.root_path = 'getting-started'
@@ -26,5 +26,9 @@ module Docs · 18 unmodified lines
2626 &copy; GruntJS Team<br>
2727 Licensed under the MIT License.
2828 HTML
29+
30+ def get_latest_version(opts)
31+ get_npm_version('grunt-cli', opts)
32+ end
2933 end
3034 end
lib/docs/scrapers/handlebars.rb+27−0
@@ -0,0 +1,27 @@
1+module 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
27+end
lib/docs/scrapers/haskell.rb+9−2
@@ -10,7 +10,7 @@ module Docs · 9 unmodified lines
1010
1111 html_filters.push 'haskell/entries', 'haskell/clean_html'
1212
13− options[:container] = ->(filter) { filter.subpath.start_with?('users_guide') ? '.body' : '#content' }
13+ options[:container] = ->(filter) {filter.subpath.start_with?('users_guide') ? '.body' : '#content'}
1414
1515 options[:only_patterns] = [/\Alibraries\//, /\Ausers_guide\//]
1616 options[:skip_patterns] = [
@@ -57,7 +57,7 @@ module Docs · 40 unmodified lines
5757 end
5858
5959 version '8' do
60− self.release = '8.2.1'
60+ self.release = '8.6.1'
6161 self.base_url = "https://downloads.haskell.org/~ghc/#{release}/docs/html/"
6262 end
6363
@@ -68,5 +68,12 @@ module Docs · 4 unmodified lines
6868
6969 options[:only_patterns] = [/\Alibraries\//]
7070 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
7178 end
7279 end
lib/docs/scrapers/haxe.rb+32−14
@@ -1,58 +1,76 @@
11 module Docs
22 class Haxe < UrlScraper
33 self.name = 'Haxe'
4− self.type = 'haxe'
5− self.release = '3.4.1'
6− self.base_url = 'http://api.haxe.org/'
4+ self.type = 'simple'
5+ self.release = '3.4.7'
6+ self.base_url = 'https://api.haxe.org/'
77
88 html_filters.push 'haxe/clean_html', 'haxe/entries'
99
1010 options[:container] = '.span9'
1111
1212 options[:attribution] = <<-HTML
13− &copy; 2005&ndash;2016 Haxe Foundation<br>
13+ &copy; 2005&ndash;2018 Haxe Foundation<br>
1414 Licensed under a MIT license.
1515 HTML
1616
1717 version do
1818 self.links = {
19− home: 'http://haxe.org',
19+ home: 'https://haxe.org',
2020 code: 'https://github.com/HaxeFoundation/haxe'
2121 }
2222
23− options[:skip_patterns] = [/\A(?:cpp|cs|flash|java|js|neko|php|python)/i]
23+ options[:skip_patterns] = [/\A(?:cpp|cs|flash|java|js|neko|php|python|lua|hl|sys)/i]
2424 end
2525
2626 version 'C++' do
27− self.base_url = 'http://api.haxe.org/cpp/'
27+ self.base_url = 'https://api.haxe.org/cpp/'
2828 end
2929
3030 version 'C#' do
31− self.base_url = 'http://api.haxe.org/cs/'
31+ self.base_url = 'https://api.haxe.org/cs/'
3232 end
3333
3434 version 'Flash' do
35− self.base_url = 'http://api.haxe.org/flash/'
35+ self.base_url = 'https://api.haxe.org/flash/'
3636 end
3737
3838 version 'Java' do
39− self.base_url = 'http://api.haxe.org/java/'
39+ self.base_url = 'https://api.haxe.org/java/'
4040 end
4141
4242 version 'JavaScript' do
43− self.base_url = 'http://api.haxe.org/js/'
43+ self.base_url = 'https://api.haxe.org/js/'
4444 end
4545
4646 version 'Neko' do
47− self.base_url = 'http://api.haxe.org/neko/'
47+ self.base_url = 'https://api.haxe.org/neko/'
4848 end
4949
5050 version 'PHP' do
51− self.base_url = 'http://api.haxe.org/php/'
51+ self.base_url = 'https://api.haxe.org/php/'
52+ end
53+
54+ version 'Lua' do
55+ self.base_url = 'https://api.haxe.org/lua/'
56+ end
57+
58+ version 'HashLink' do
59+ self.base_url = 'https://api.haxe.org/hl/'
60+ end
61+
62+ version 'Sys' do
63+ self.base_url = 'https://api.haxe.org/sys/'
5264 end
5365
5466 version 'Python' do
55− self.base_url = 'http://api.haxe.org/python/'
67+ self.base_url = 'https://api.haxe.org/python/'
68+ 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 /, '')
5674 end
5775 end
5876 end
lib/docs/scrapers/homebrew.rb+27−0
@@ -0,0 +1,27 @@
1+module Docs
2+ class Homebrew < UrlScraper
3+ self.name = 'Homebrew'
4+ self.type = 'simple'
5+ self.release = '2.1.9'
6+ self.base_url = 'https://docs.brew.sh/'
7+ self.links = {
8+ home: 'https://brew.sh',
9+ code: 'https://github.com/Homebrew/brew'
10+ }
11+
12+ html_filters.push 'homebrew/entries', 'homebrew/clean_html'
13+
14+ options[:container] = ->(filter) { filter.root_page? ? '#home' : '#page' }
15+
16+ options[:skip_patterns] = [/maintainer/i, /core\-contributor/i, /kickstarter/i]
17+
18+ options[:attribution] = <<-HTML
19+ &copy; 2009&ndash;present Homebrew contributors<br>
20+ Licensed under the BSD 2-Clause License.
21+ HTML
22+
23+ def get_latest_version(opts)
24+ get_latest_github_release('Homebrew', 'brew', opts)
25+ end
26+ end
27+end
lib/docs/scrapers/http.rb+3−0
@@ -7,10 +7,13 @@ module Docs · 6 unmodified lines
77
88 html_filters.push 'http/clean_html', 'http/entries', 'title'
99
10+ options[:mdn_tag] = 'HTTP'
11+
1012 options[:root_title] = 'HTTP'
1113 options[:title] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? false : filter.default_title }
1214 options[:container] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? '.content' : nil }
1315 options[:skip_links] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? true : false }
16+ options[:replace_paths] = { '/Access_control_CORS' => '/CORS' }
1417 options[:fix_urls] = ->(url) do
1518 url.sub! %r{(Status/\d\d\d)_[A-Z].+}, '\1'
1619 url
lib/docs/scrapers/immutable.rb+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class Immutable < UrlScraper
33 self.name = 'Immutable.js'
44 self.slug = 'immutable'
5− self.type = 'immutable'
5+ self.type = 'simple'
66 self.release = '3.8.1'
77 self.base_url = 'https://facebook.github.io/immutable-js/docs/'
88 self.links = {
@@ -54,5 +54,9 @@ module Docs · 45 unmodified lines
5454 JS
5555 capybara.html
5656 end
57+
58+ def get_latest_version(opts)
59+ get_npm_version('immutable', opts)
60+ end
5761 end
5862 end
lib/docs/scrapers/influxdata.rb+7−1
@@ -1,7 +1,7 @@
11 module Docs
22 class Influxdata < UrlScraper
33 self.name = 'InfluxData'
4− self.type = 'influxdata'
4+ self.type = 'simple'
55 self.release = '1.3'
66 self.base_url = 'https://docs.influxdata.com/'
77 self.links = {
@@ -46,5 +46,11 @@ module Docs · 38 unmodified lines
4646 &copy; 2015 InfluxData, Inc.<br>
4747 Licensed under the MIT license.
4848 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
4955 end
5056 end
lib/docs/scrapers/jasmine.rb+7−3
@@ -1,8 +1,8 @@
11 module Docs
22 class Jasmine < UrlScraper
3− self.type = 'jasmine'
4− self.release = '2.8.0'
5− self.base_url = 'https://jasmine.github.io/api/2.7/'
3+ self.type = 'simple'
4+ self.release = '3.3.0'
5+ self.base_url = 'https://jasmine.github.io/api/3.2/'
66 self.root_path = 'index.html'
77 self.links = {
88 home: 'https://jasmine.github.io/',
@@ -17,5 +17,9 @@ module Docs · 8 unmodified lines
1717 &copy; 2008&ndash;2017 Pivotal Labs<br>
1818 Licensed under the MIT License.
1919 HTML
20+
21+ def get_latest_version(opts)
22+ get_latest_github_release('jasmine', 'jasmine', opts)
23+ end
2024 end
2125 end
lib/docs/scrapers/jekyll.rb+37−0
@@ -0,0 +1,37 @@
1+module Docs
2+ class Jekyll < UrlScraper
3+ self.type = 'jekyll'
4+ self.release = '3.7.2'
5+ self.base_url = 'https://jekyllrb.com/docs/'
6+ self.root_path = 'home/'
7+ self.links = {
8+ home: 'https://jekyllrb.com/',
9+ code: 'https://github.com/jekyll/jekyll'
10+ }
11+
12+ html_filters.push 'jekyll/clean_html', 'jekyll/entries'
13+
14+ options[:trailing_slash] = true
15+ options[:skip] = %w(sites/ upgrading/)
16+ options[:skip_patterns] = [
17+ /conduct/,
18+ /history/,
19+ /maintaining/,
20+ /contributing/,
21+ ]
22+ options[:replace_paths] = {
23+ '' => 'home/',
24+ '/' => 'home/'
25+ }
26+
27+ options[:attribution] = <<-HTML
28+ &copy; 2008&ndash;2018 Tom Preston-Werner and Jekyll contributors<br>
29+ Licensed under the MIT license.
30+ 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
36+ end
37+end
lib/docs/scrapers/jest.rb+9−4
@@ -1,9 +1,9 @@
11 module Docs
22 class Jest < UrlScraper
3− self.type = 'jest'
4− self.release = '21.2.1'
5− self.base_url = 'https://facebook.github.io/jest/docs/en/'
6− self.root_path = 'getting-started.html'
3+ self.type = 'simple'
4+ self.release = '23.5.0'
5+ self.base_url = 'https://jestjs.io/docs/en/'
6+ self.root_path = 'getting-started'
77 self.links = {
88 home: 'https://facebook.github.io/jest/',
99 code: 'https://github.com/facebook/jest'
@@ -17,5 +17,10 @@ module Docs · 7 unmodified lines
1717 &copy; 2014&ndash;present Facebook Inc.<br>
1818 Licensed under the BSD License.
1919 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
2025 end
2126 end
lib/docs/scrapers/jquery/jquery_core.rb+9−1
@@ -1,9 +1,13 @@
11 module Docs
22 class JqueryCore < Jquery
33 self.name = 'jQuery'
4− self.release = 'up to 3.2.1'
4+ self.release = '3.3.1'
55 self.base_url = 'https://api.jquery.com/'
66 self.initial_paths = %w(/index/index)
7+ self.links = {
8+ home: 'https://jquery.com/',
9+ code: 'https://github.com/jquery/jquery'
10+ }
711
812 html_filters.insert_before 'jquery/clean_html', 'jquery_core/entries'
913
@@ -18,5 +22,9 @@ module Docs · 8 unmodified lines
1822 /Selectors\/odd/i,
1923 /index/i
2024 ]
25+
26+ def get_latest_version(opts)
27+ get_npm_version('jquery', opts)
28+ end
2129 end
2230 end
lib/docs/scrapers/jquery/jquery_mobile.rb+5−0
@@ -16,5 +16,10 @@ module Docs · 15 unmodified lines
1616 options[:fix_urls] = ->(url) do
1717 url.sub! 'http://api.jquerymobile.com/', 'https://api.jquerymobile.com/'
1818 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
1924 end
2025 end
lib/docs/scrapers/jquery/jquery_ui.rb+4−0
@@ -15,5 +15,9 @@ module Docs · 14 unmodified lines
1515 options[:fix_urls] = ->(url) do
1616 url.sub! 'http://api.jqueryui.com/', 'https://api.jqueryui.com/'
1717 end
18+
19+ def get_latest_version(opts)
20+ get_npm_version('jquery-ui', opts)
21+ end
1822 end
1923 end
lib/docs/scrapers/jsdoc.rb+29−0
@@ -0,0 +1,29 @@
1+module Docs
2+ class Jsdoc < UrlScraper
3+ self.name = 'JSDoc'
4+ self.type = 'simple'
5+ self.release = '3.5.5'
6+ self.base_url = 'http://usejsdoc.org/'
7+ self.root_path = 'index.html'
8+ self.links = {
9+ home: 'http://usejsdoc.org/',
10+ code: 'https://github.com/jsdoc3/jsdoc'
11+ }
12+
13+ html_filters.push 'jsdoc/clean_html', 'jsdoc/entries'
14+
15+ options[:trailing_slash] = false
16+ options[:container] = 'article'
17+ options[:skip] = [
18+ 'about-license-jsdoc3.html'
19+ ]
20+ options[:attribution] = <<-HTML
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.
23+ HTML
24+
25+ def get_latest_version(opts)
26+ get_latest_github_release('jsdoc3', 'jsdoc', opts)
27+ end
28+ end
29+end
lib/docs/scrapers/julia.rb+28−4
@@ -8,13 +8,33 @@ module Docs · 7 unmodified lines
88 options[:only_patterns] = [/\Amanual\//, /\Astdlib\//]
99
1010 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>
1212 Licensed under the MIT License.
1313 HTML
1414
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+
1535 version '0.6' do
16− self.release = '0.6.0'
17− self.base_url = 'https://docs.julialang.org/en/release-0.6/'
36+ self.release = '0.6.2'
37+ self.base_url = "https://docs.julialang.org/en/v#{release}/"
1838 self.type = 'julia'
1939
2040 html_filters.push 'julia/entries', 'julia/clean_html'
@@ -24,10 +44,14 @@ module Docs · 3 unmodified lines
2444
2545 version '0.5' do
2646 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}/"
2848 self.type = 'sphinx_simple'
2949
3050 html_filters.push 'julia/entries_sphinx', 'julia/clean_html_sphinx', 'sphinx/clean_html'
3151 end
52+
53+ def get_latest_version(opts)
54+ get_latest_github_release('JuliaLang', 'julia', opts)
55+ end
3256 end
3357 end
lib/docs/scrapers/knockout.rb+4−0
@@ -33,5 +33,9 @@ module Docs · 32 unmodified lines
3333 &copy; Steven Sanderson, the Knockout.js team, and other contributors<br>
3434 Licensed under the MIT License.
3535 HTML
36+
37+ def get_latest_version(opts)
38+ get_latest_github_release('knockout', 'knockout', opts)
39+ end
3640 end
3741 end
lib/docs/scrapers/koa.rb+42−0
@@ -0,0 +1,42 @@
1+# frozen_string_literal: true
2+
3+module Docs
4+ class Koa < Github
5+ self.base_url = 'https://github.com/koajs/koa/blob/master/docs/'
6+ self.release = '2.6.1'
7+
8+ self.root_path = 'api/index.md'
9+ self.initial_paths = %w[
10+ error-handling
11+ faq
12+ guide
13+ koa-vs-express
14+ migration
15+ troubleshooting
16+ api/index
17+ api/context
18+ api/request
19+ api/response
20+ ].map { |name| name + '.md' }
21+
22+ self.links = {
23+ home: 'https://koajs.com/',
24+ code: 'https://github.com/koajs/koa'
25+ }
26+
27+ html_filters.push 'koa/clean_html', 'koa/entries'
28+
29+ options[:skip] = %w[middleware.gif]
30+ options[:trailing_slash] = false
31+ options[:container] = '.markdown-body'
32+
33+ options[:attribution] = <<-HTML
34+ &copy; 2018 Koa contributors<br>
35+ Licensed under the MIT License.
36+ HTML
37+
38+ def get_latest_version(opts)
39+ get_npm_version('koa', opts)
40+ end
41+ end
42+end
lib/docs/scrapers/kotlin.rb+15−3
@@ -1,7 +1,7 @@
11 module Docs
22 class Kotlin < UrlScraper
33 self.type = 'kotlin'
4− self.release = '1.1.4'
4+ self.release = '1.3.41'
55 self.base_url = 'https://kotlinlang.org/'
66 self.root_path = 'api/latest/jvm/stdlib/index.html'
77 self.links = {
@@ -22,11 +22,23 @@ module Docs · 14 unmodified lines
2222 docs/events.html
2323 docs/resources.html
2424 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+ }
2634
2735 options[:attribution] = <<-HTML
28− &copy; 2010&ndash;2017 JetBrains s.r.o.<br>
36+ &copy; 2010&ndash;2019 JetBrains s.r.o.<br>
2937 Licensed under the Apache License, Version 2.0.
3038 HTML
39+
40+ def get_latest_version(opts)
41+ get_latest_github_release('JetBrains', 'kotlin', opts)
42+ end
3143 end
3244 end
lib/docs/scrapers/laravel.rb+31−1
@@ -29,8 +29,34 @@ module Docs · 28 unmodified lines
2929 Laravel is a trademark of Taylor Otwell.
3030 HTML
3131
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+
45+ version '5.6' do
46+ self.release = '5.6.33'
47+ self.root_path = '/api/5.6/index.html'
48+ self.initial_paths = %w(/docs/5.6/installation /api/5.6/classes.html)
49+
50+ options[:only_patterns] = [%r{\A/api/5\.6/}, %r{\A/docs/5\.6/}]
51+
52+ options[:fix_urls] = ->(url) do
53+ url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\/(?!\d)}, "#{Laravel.base_url}/docs/5.6/"
54+ url
55+ end
56+ end
57+
3258 version '5.5' do
33− self.release = '5.5.0'
59+ self.release = '5.5.28'
3460 self.root_path = '/api/5.5/index.html'
3561 self.initial_paths = %w(/docs/5.5/installation /api/5.5/classes.html)
3662
@@ -107,5 +133,9 @@ module Docs · 70 unmodified lines
107133 url
108134 end
109135 end
136+
137+ def get_latest_version(opts)
138+ get_latest_github_release('laravel', 'laravel', opts)
139+ end
110140 end
111141 end
lib/docs/scrapers/leaflet.rb+58−0
@@ -0,0 +1,58 @@
1+module Docs
2+ class Leaflet < UrlScraper
3+ self.name = 'Leaflet'
4+ self.type = 'simple'
5+ self.slug = 'leaflet'
6+ self.links = {
7+ home: 'https://leafletjs.com/',
8+ code: 'https://github.com/Leaflet/Leaflet'
9+ }
10+
11+ html_filters.push 'leaflet/entries', 'leaflet/clean_html', 'title'
12+
13+ options[:container] = '.container'
14+ options[:skip_links] = true
15+
16+ options[:attribution] = <<-HTML
17+ &copy; 2010&ndash;2019 Vladimir Agafonkin<br>
18+ &copy; 2010&ndash;2011, CloudMade<br>
19+ Maps &copy; OpenStreetMap contributors.
20+ HTML
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+
32+ version '1.3' do
33+ self.release = '1.3.4'
34+ self.base_url = "https://leafletjs.com/reference-#{release}.html"
35+ end
36+
37+ version '1.2' do
38+ self.release = '1.2.0'
39+ self.base_url = "https://leafletjs.com/reference-#{release}.html"
40+ end
41+
42+ version '1.1' do
43+ self.release = '1.1.0'
44+ self.base_url = "https://leafletjs.com/reference-#{release}.html"
45+ end
46+
47+ version '1.0' do
48+ self.release = '1.0.3'
49+ self.base_url = "https://leafletjs.com/reference-#{release}.html"
50+ end
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
57+ end
58+end
lib/docs/scrapers/less.rb+7−1
@@ -1,6 +1,6 @@
11 module Docs
22 class Less < UrlScraper
3− self.type = 'less'
3+ self.type = 'simple'
44 self.release = '2.7.2'
55 self.base_url = 'http://lesscss.org'
66 self.root_path = '/features'
@@ -21,5 +21,11 @@ module Docs · 14 unmodified lines
2121 &copy; 2009&ndash;2016 The Core Less Team<br>
2222 Licensed under the Creative Commons Attribution License 3.0.
2323 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
2430 end
2531 end
lib/docs/scrapers/liquid.rb+5−0
@@ -19,5 +19,10 @@ module Docs · 18 unmodified lines
1919 &copy; 2005, 2006 Tobias Luetke<br>
2020 Licensed under the MIT License.
2121 HTML
22+
23+ def get_latest_version(opts)
24+ tags = get_github_tags('Shopify', 'liquid', opts)
25+ tags[0]['name'][1..-1]
26+ end
2227 end
2328 end
lib/docs/scrapers/lodash.rb+8−3
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class Lodash < UrlScraper
33 self.name = 'lodash'
44 self.slug = 'lodash'
5− self.type = 'lodash'
5+ self.type = 'simple'
66 self.links = {
77 home: 'https://lodash.com/',
88 code: 'https://github.com/lodash/lodash/'
@@ -14,12 +14,12 @@ module Docs · 5 unmodified lines
1414 options[:skip_links] = true
1515
1616 options[:attribution] = <<-HTML
17− &copy; 2012&ndash;2016 The Dojo Foundation<br>
17+ &copy; JS Foundation and other contributors<br>
1818 Licensed under the MIT License.
1919 HTML
2020
2121 version '4' do
22− self.release = '4.17.4'
22+ self.release = '4.17.10'
2323 self.base_url = "https://lodash.com/docs/#{release}"
2424 end
2525
@@ -32,5 +32,10 @@ module Docs · 6 unmodified lines
3232 self.release = '2.4.2'
3333 self.base_url = "https://lodash.com/docs/#{release}"
3434 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
3540 end
3641 end
lib/docs/scrapers/love.rb+5−0
@@ -39,5 +39,10 @@ module Docs · 38 unmodified lines
3939 &copy; 2006&ndash;2016 L&Ouml;VE Development Team<br>
4040 Licensed under the GNU Free Documentation License, Version 1.3.
4141 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
4247 end
4348 end
lib/docs/scrapers/lua.rb+5−0
@@ -26,5 +26,10 @@ module Docs · 25 unmodified lines
2626 self.release = '5.1.5'
2727 self.base_url = 'https://www.lua.org/manual/5.1/'
2828 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
2934 end
3035 end
lib/docs/scrapers/marionette.rb+13−2
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class Marionette < UrlScraper
33 self.name = 'Marionette.js'
44 self.slug = 'marionette'
5− self.type = 'marionette'
5+ self.type = 'simple'
66 self.root_path = 'index'
77 self.links = {
88 home: 'https://marionettejs.com/',
@@ -18,8 +18,15 @@ module Docs · 9 unmodified lines
1818 Licensed under the MIT License.
1919 HTML
2020
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+
2128 version '3' do
22− self.release = '3.4.3'
29+ self.release = '3.5.1'
2330 self.base_url = "https://marionettejs.com/docs/v#{release}/"
2431
2532 html_filters.push 'marionette/entries_v3'
@@ -31,5 +38,9 @@ module Docs · 5 unmodified lines
3138
3239 html_filters.push 'marionette/entries_v2'
3340 end
41+
42+ def get_latest_version(opts)
43+ get_npm_version('backbone.marionette', opts)
44+ end
3445 end
3546 end
lib/docs/scrapers/markdown.rb+5−1
@@ -1,7 +1,7 @@
11 module Docs
22 class Markdown < UrlScraper
33 self.name = 'Markdown'
4− self.type = 'markdown'
4+ self.type = 'simple'
55 self.base_url = 'http://daringfireball.net/projects/markdown/syntax'
66
77 html_filters.push 'markdown/clean_html', 'markdown/entries'
@@ -13,5 +13,9 @@ module Docs · 5 unmodified lines
1313 &copy; 2004 John Gruber<br>
1414 Licensed under the BSD License.
1515 HTML
16+
17+ def get_latest_version(opts)
18+ '1.0.0'
19+ end
1620 end
1721 end
lib/docs/scrapers/matplotlib.rb+48−8
@@ -6,7 +6,7 @@ module Docs · 5 unmodified lines
66 self.type = 'sphinx'
77 self.root_path = 'index.html'
88 self.links = {
9− home: 'http://matplotlib.org/',
9+ home: 'https://matplotlib.org/',
1010 code: 'https://github.com/matplotlib/matplotlib'
1111 }
1212
@@ -16,26 +16,66 @@ module Docs · 3 unmodified lines
1616 options[:skip] = %w(api_changes.html tutorial.html faq.html)
1717
1818 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>
2020 Licensed under the Matplotlib License Agreement.
2121 HTML
2222
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+
50+ version '2.1' do
51+ self.release = '2.1.0'
52+ self.base_urls = [
53+ "https://matplotlib.org/#{release}/api/",
54+ "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
55+ "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
56+ ]
57+ end
58+
2359 version '2.0' do
2460 self.release = '2.0.2'
2561 self.base_urls = [
26− "http://matplotlib.org/#{release}/api/",
27− "http://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
28− "http://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
62+ "https://matplotlib.org/#{release}/api/",
63+ "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
64+ "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
2965 ]
3066 end
3167
3268 version '1.5' do
3369 self.release = '1.5.3'
3470 self.base_urls = [
35− "http://matplotlib.org/#{release}/api/",
36− "http://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
37− "http://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
71+ "https://matplotlib.org/#{release}/api/",
72+ "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
73+ "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
3874 ]
3975 end
76+
77+ def get_latest_version(opts)
78+ get_latest_github_release('matplotlib', 'matplotlib', opts)
79+ end
4080 end
4181 end
lib/docs/scrapers/mdn/css.rb+2−0
@@ -6,6 +6,8 @@ module Docs · 5 unmodified lines
66
77 html_filters.push 'css/clean_html', 'css/entries', 'title'
88
9+ options[:mdn_tag] = 'CSS'
10+
911 options[:root_title] = 'CSS'
1012
1113 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
88
99 html_filters.push 'dom/clean_html', 'dom/entries', 'title'
1010
11+ options[:mdn_tag] = 'XSLT_Reference'
12+
1113 options[:root_title] = 'DOM'
1214
1315 options[:skip] = %w(
lib/docs/scrapers/mdn/dom_events.rb+4−0
@@ -1,5 +1,7 @@
11 module Docs
22 class DomEvents < Mdn
3+ prepend FixInternalUrlsBehavior
4+
35 self.name = 'DOM Events'
46 self.slug = 'dom_events'
57 self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/Events'
@@ -7,6 +9,8 @@ module Docs · 1 unmodified line
79 html_filters.insert_after 'clean_html', 'dom_events/clean_html'
810 html_filters.push 'dom_events/entries', 'title'
911
12+ options[:mdn_tag] = 'events'
13+
1014 options[:root_title] = 'DOM Events'
1115
1216 options[:skip] = %w(/MozOrientation)
lib/docs/scrapers/mdn/html.rb+2−0
@@ -7,6 +7,8 @@ module Docs · 6 unmodified lines
77
88 html_filters.push 'html/clean_html', 'html/entries', 'title'
99
10+ options[:mdn_tag] = 'HTML'
11+
1012 options[:root_title] = 'HTML'
1113
1214 options[:title] = ->(filter) do
lib/docs/scrapers/mdn/javascript.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
88
99 html_filters.push 'javascript/clean_html', 'javascript/entries', 'title'
1010
11+ options[:mdn_tag] = 'JavaScript'
12+
1113 options[:root_title] = 'JavaScript'
1214
1315 # Don't want
lib/docs/scrapers/mdn/mdn.rb+7−1
@@ -9,6 +9,7 @@ module Docs · 8 unmodified lines
99 html_filters.push 'mdn/clean_html'
1010 text_filters.insert_before 'attribution', 'mdn/contribute_link'
1111
12+ options[:rate_limit] = 200
1213 options[:trailing_slash] = false
1314
1415 options[:skip_link] = ->(link) {
@@ -16,10 +17,15 @@ module Docs · 1 unmodified line
1617 }
1718
1819 options[:attribution] = <<-HTML
19− &copy; 2005&ndash;2017 Mozilla Developer Network and individual contributors.<br>
20+ &copy; 2005&ndash;2018 Mozilla Developer Network and individual contributors.<br>
2021 Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
2122 HTML
2223
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+
2329 private
2430
2531 def process_response?(response)
lib/docs/scrapers/mdn/svg.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
88
99 html_filters.push 'svg/clean_html', 'svg/entries', 'title'
1010
11+ options[:mdn_tag] = 'XSLT_Reference'
12+
1113 options[:root_title] = 'SVG'
1214
1315 options[:title] = ->(filter) do
lib/docs/scrapers/mdn/xslt_xpath.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
88
99 html_filters.push 'xslt_xpath/clean_html', 'xslt_xpath/entries', 'title'
1010
11+ options[:mdn_tag] = 'XSLT_Reference'
12+
1113 options[:root_title] = 'XSLT'
1214
1315 options[:only_patterns] = [/\A\/XSLT/, /\A\/XPath/]
lib/docs/scrapers/meteor.rb+6−1
@@ -31,7 +31,7 @@ module Docs · 30 unmodified lines
3131 HTML
3232
3333 version '1.5' do
34− self.release = '1.5.1'
34+ self.release = '1.5.2'
3535 self.base_urls = ['https://docs.meteor.com/', 'https://guide.meteor.com/', 'http://blazejs.org/']
3636 end
3737
@@ -45,5 +45,10 @@ module Docs · 7 unmodified lines
4545 self.base_urls = ['https://guide.meteor.com/v1.3/', "https://docs.meteor.com/v#{self.release}/"]
4646 options[:fix_urls] = nil
4747 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
4853 end
4954 end
lib/docs/scrapers/mocha.rb+7−3
@@ -1,7 +1,7 @@
11 module Docs
22 class Mocha < UrlScraper
3− self.type = 'mocha'
4− self.release = '3.5.0'
3+ self.type = 'simple'
4+ self.release = '5.2.0'
55 self.base_url = 'https://mochajs.org/'
66 self.links = {
77 home: 'https://mochajs.org/',
@@ -15,8 +15,12 @@ module Docs · 7 unmodified lines
1515 options[:skip_links] = true
1616
1717 options[:attribution] = <<-HTML
18− &copy; 2016 JS Foundation and contributors<br>
18+ &copy; 2011&ndash;2018 JS Foundation and contributors<br>
1919 Licensed under the Creative Commons Attribution 4.0 International License.
2020 HTML
21+
22+ def get_latest_version(opts)
23+ get_npm_version('mocha', opts)
24+ end
2125 end
2226 end
lib/docs/scrapers/modernizr.rb+4−0
@@ -15,5 +15,9 @@ module Docs · 14 unmodified lines
1515 &copy; 2009&ndash;2017 The Modernizr team<br>
1616 Licensed under the MIT License.
1717 HTML
18+
19+ def get_latest_version(opts)
20+ get_npm_version('modernizr', opts)
21+ end
1822 end
1923 end
lib/docs/scrapers/moment.rb+6−1
@@ -3,7 +3,7 @@ module Docs · 2 unmodified lines
33 self.name = 'Moment.js'
44 self.slug = 'moment'
55 self.type = 'moment'
6− self.release = '2.18.1'
6+ self.release = '2.22.1'
77 self.base_url = 'http://momentjs.com'
88 self.root_path = '/docs/'
99 self.initial_paths = %w(/guides/)
@@ -22,5 +22,10 @@ module Docs · 12 unmodified lines
2222 &copy; JS Foundation and other contributors<br>
2323 Licensed under the MIT License.
2424 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
2530 end
2631 end
lib/docs/scrapers/mongoose.rb+9−2
@@ -1,11 +1,12 @@
11 module Docs
22 class Mongoose < UrlScraper
33 self.name = 'Mongoose'
4− self.type = 'mongoose'
5− self.release = '4.12.0'
4+ self.type = 'simple'
5+ self.release = '4.13.1'
66 self.base_url = 'http://mongoosejs.com/docs/'
77 self.root_path = 'index.html'
88 self.initial_paths = %w(guide.html api.html)
9+ self.force_gzip = true
910 self.links = {
1011 home: 'http://mongoosejs.com/',
1112 code: 'https://github.com/Automattic/mongoose'
@@ -25,5 +26,11 @@ module Docs · 13 unmodified lines
2526 &copy; 2010 LearnBoost<br>
2627 Licensed under the MIT License.
2728 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
2835 end
2936 end
lib/docs/scrapers/nginx.rb+10−4
@@ -2,11 +2,11 @@ module Docs · 1 unmodified line
22 class Nginx < UrlScraper
33 self.name = 'nginx'
44 self.type = 'nginx'
5− self.release = '1.13.4'
5+ self.release = '1.17.2'
66 self.base_url = 'https://nginx.org/en/docs/'
77 self.links = {
88 home: 'https://nginx.org/',
9− code: 'http://hg.nginx.org/nginx'
9+ code: 'https://hg.nginx.org/nginx'
1010 }
1111
1212 html_filters.push 'nginx/clean_html', 'nginx/entries'
@@ -21,9 +21,15 @@ module Docs · 8 unmodified lines
2121 options[:skip_patterns] = [/\/faq\//]
2222
2323 options[:attribution] = <<-HTML
24− &copy; 2002-2017 Igor Sysoev<br>
25− &copy; 2011-2017 Nginx, Inc.<br>
24+ &copy; 2002-2019 Igor Sysoev<br>
25+ &copy; 2011-2019 Nginx, Inc.<br>
2626 Licensed under the BSD License.
2727 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
2834 end
2935 end
lib/docs/scrapers/nginx_lua_module.rb+12−3
@@ -2,8 +2,11 @@ module Docs · 1 unmodified line
22 class NginxLuaModule < Github
33 self.name = 'nginx / Lua Module'
44 self.slug = 'nginx_lua_module'
5− self.release = '0.10.8'
5+ self.release = '0.10.13'
66 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+ }
710
811 html_filters.push 'nginx_lua_module/clean_html', 'nginx_lua_module/entries', 'title'
912
@@ -11,9 +14,15 @@ module Docs · 1 unmodified line
1114 options[:container] = '#readme > article'
1215
1316 options[:attribution] = <<-HTML
14− &copy; 2009&ndash;2016 Xiaozhe Wang (chaoslawful)<br>
15− &copy; 2009&ndash;2017 Yichun "agentzh" Zhang (章亦春), OpenResty Inc.<br>
17+ &copy; 2009&ndash;2017 Xiaozhe Wang (chaoslawful)<br>
18+ &copy; 2009&ndash;2018 Yichun "agentzh" Zhang (章亦春), OpenResty Inc.<br>
1619 Licensed under the BSD License.
1720 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
1827 end
1928 end
lib/docs/scrapers/nim.rb+8−3
@@ -1,7 +1,7 @@
11 module Docs
22 class Nim < UrlScraper
3− self.type = 'nim'
4− self.release = '0.17.0'
3+ self.type = 'simple'
4+ self.release = '0.19.0'
55 self.base_url = 'https://nim-lang.org/docs/'
66 self.root_path = 'overview.html'
77 self.links = {
@@ -14,8 +14,13 @@ module Docs · 6 unmodified lines
1414 options[:skip] = %w(theindex.html docgen.txt)
1515
1616 options[:attribution] = <<-HTML
17− &copy; 2006&ndash;2017 Andreas Rumpf<br>
17+ &copy; 2006&ndash;2018 Andreas Rumpf<br>
1818 Licensed under the MIT License.
1919 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
2025 end
2126 end
lib/docs/scrapers/node.rb+18−3
@@ -23,18 +23,33 @@ module Docs · 22 unmodified lines
2323 HTML
2424
2525 version do
26− self.release = '8.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'
32+ self.base_url = 'https://nodejs.org/dist/latest-v10.x/docs/api/'
33+ end
34+
35+ version '8 LTS' do
36+ self.release = '8.12.0'
2737 self.base_url = 'https://nodejs.org/dist/latest-v8.x/docs/api/'
2838 end
2939
3040 version '6 LTS' do
31− self.release = '6.11.4'
41+ self.release = '6.14.4'
3242 self.base_url = 'https://nodejs.org/dist/latest-v6.x/docs/api/'
3343 end
3444
3545 version '4 LTS' do
36− self.release = '4.8.4'
46+ self.release = '4.9.1'
3747 self.base_url = 'https://nodejs.org/dist/latest-v4.x/docs/api/'
3848 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
3954 end
4055 end
lib/docs/scrapers/nokogiri2.rb+12−4
@@ -1,9 +1,13 @@
11 module Docs
22 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+
38 self.name = 'Nokogiri'
49 self.slug = 'nokogiri'
5− self.release = '1.8.1'
6− self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Nokogiri'
10+ self.release = '1.9.0'
711
812 html_filters.replace 'rdoc/entries', 'nokogiri2/entries'
913
@@ -11,9 +15,13 @@ module Docs · 1 unmodified line
1115 options[:only_patterns] = [/\ANokogiri/, /\AXSD/]
1216
1317 options[:attribution] = <<-HTML
14− &copy; 2008&ndash;2017 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo<br>
15− Patrick Mahoney, Yoko Harada, Akinori Musha, John Shahid<br>
18+ &copy; 2008&ndash;2018 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo,<br>
19+ Patrick Mahoney, Yoko Harada, Akinori Musha, John Shahid, Lars Kanis<br>
1620 Licensed under the MIT License.
1721 HTML
22+
23+ def get_latest_version(opts)
24+ get_latest_github_release('sparklemotion', 'nokogiri', opts)
25+ end
1826 end
1927 end
lib/docs/scrapers/npm.rb+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
22 class Npm < UrlScraper
33 self.name = 'npm'
44 self.type = 'npm'
5− self.release = '5.4.1'
5+ self.release = '6.4.0'
66 self.base_url = 'https://docs.npmjs.com/'
77 self.force_gzip = true
88 self.links = {
@@ -29,5 +29,9 @@ module Docs · 20 unmodified lines
2929 Licensed under the npm License.<br>
3030 npm is a trademark of npm, Inc.
3131 HTML
32+
33+ def get_latest_version(opts)
34+ get_latest_github_release('npm', 'cli', opts)
35+ end
3236 end
3337 end
lib/docs/scrapers/numpy.rb+9−1
@@ -2,7 +2,6 @@ module Docs · 1 unmodified line
22 class Numpy < FileScraper
33 self.name = 'NumPy'
44 self.type = 'sphinx'
5− self.dir = '/Users/Thibaut/DevDocs/Docs/numpy/reference/'
65 self.root_path = 'index.html'
76 self.links = {
87 home: 'http://www.numpy.org/',
@@ -26,6 +25,11 @@ module Docs · 17 unmodified lines
2625 Licensed under the NumPy License.
2726 HTML
2827
28+ version '1.14' do
29+ self.release = '1.14.2'
30+ self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"
31+ end
32+
2933 version '1.13' do
3034 self.release = '1.13.0'
3135 self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"
@@ -45,5 +49,9 @@ module Docs · 13 unmodified lines
4549 self.release = '1.10.4'
4650 self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"
4751 end
52+
53+ def get_latest_version(opts)
54+ get_latest_github_release('numpy', 'numpy', opts)
55+ end
4856 end
4957 end
lib/docs/scrapers/openjdk.rb+22−3
@@ -1,11 +1,10 @@
11 module Docs
22 class Openjdk < FileScraper
3+ # Downloaded from packages.debian.org/sid/openjdk-8-doc
4+ # Extracting subdirectory /usr/share/doc/openjdk-8-jre-headless/api
35 self.name = 'OpenJDK'
46 self.type = 'openjdk'
57 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'
98
109 html_filters.insert_after 'internal_urls', 'openjdk/clean_urls'
1110 html_filters.push 'openjdk/entries', 'openjdk/clean_html'
@@ -87,5 +86,25 @@ module Docs · 75 unmodified lines
8786 def read_file(path)
8887 File.read(path).force_encoding('iso-8859-1').encode('utf-8') rescue nil
8988 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
90109 end
91110 end
lib/docs/scrapers/opentsdb.rb+4−0
@@ -18,5 +18,9 @@ module Docs · 17 unmodified lines
1818 &copy; 2010&ndash;2016 The OpenTSDB Authors<br>
1919 Licensed under the GNU LGPLv2.1+ and GPLv3+ licenses.
2020 HTML
21+
22+ def get_latest_version(opts)
23+ get_latest_github_release('OpenTSDB', 'opentsdb', opts)
24+ end
2125 end
2226 end
lib/docs/scrapers/padrino.rb+7−3
@@ -2,8 +2,8 @@ module Docs · 1 unmodified line
22 class Padrino < UrlScraper
33 self.slug = 'padrino'
44 self.type = 'rubydoc'
5− self.release = '0.13.2'
6− self.base_url = 'http://www.rubydoc.info/github/padrino/padrino-framework/'
5+ self.release = '0.14.4'
6+ self.base_url = 'https://www.rubydoc.info/github/padrino/padrino-framework/'
77 self.root_path = 'file/README.rdoc'
88 self.initial_paths = %w(index2)
99 self.links = {
@@ -16,12 +16,16 @@ module Docs · 6 unmodified lines
1616 options[:container] = ->(filter) { filter.root_page? ? '#filecontents' : '#content' }
1717
1818 options[:attribution] = <<-HTML
19− &copy; 2010&ndash;2016 Padrino<br>
19+ &copy; 2010&ndash;2019 Padrino<br>
2020 Licensed under the MIT License.
2121 HTML
2222
2323 stub 'index2' do
2424 request_one(url_for('index')).body
2525 end
26+
27+ def get_latest_version(opts)
28+ get_github_tags('padrino', 'padrino-framework', opts)[0]['name']
29+ end
2630 end
2731 end
lib/docs/scrapers/pandas.rb+36−4
@@ -14,25 +14,57 @@ module Docs · 13 unmodified lines
1414 options[:container] = '.document'
1515
1616 options[:skip] = %w(internals.html release.html contributing.html whatsnew.html)
17+ options[:skip_patterns] = [/whatsnew\//]
1718
1819 options[:attribution] = <<-HTML
1920 &copy; 2008&ndash;2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team<br>
2021 Licensed under the 3-clause BSD License.
2122 HTML
2223
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+
39+ version '0.22' do
40+ self.release = '0.22.0'
41+ self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
42+ end
43+
44+ version '0.21' do
45+ self.release = '0.21.1'
46+ self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
47+ end
48+
2349 version '0.20' do
24− self.release = '0.20.2'
25− self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"
50+ self.release = '0.20.3'
51+ self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
2652 end
2753
2854 version '0.19' do
2955 self.release = '0.19.2'
30− 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}/"
3157 end
3258
3359 version '0.18' do
3460 self.release = '0.18.1'
35− 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]
3668 end
3769 end
3870 end
lib/docs/scrapers/perl.rb+6−1
@@ -2,7 +2,6 @@ module Docs · 1 unmodified line
22 class Perl < FileScraper
33 self.name = 'Perl'
44 self.type = 'perl'
5− self.dir = '/Users/Thibaut/DevDocs/Docs/Perl'
65 self.root_path = 'index.html'
76 self.links = {
87 home: 'https://www.perl.org/'
@@ -44,5 +43,11 @@ module Docs · 35 unmodified lines
4443 self.release = '5.20.2'
4544 self.base_url = "https://perldoc.perl.org/#{self.release}/"
4645 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
4752 end
4853 end
lib/docs/scrapers/phalcon.rb+5−0
@@ -29,5 +29,10 @@ module Docs · 28 unmodified lines
2929 self.release = '2.0.13'
3030 self.base_url = 'https://docs.phalconphp.com/en/2.0.0/'
3131 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
3237 end
3338 end
lib/docs/scrapers/phaser.rb+4−0
@@ -25,5 +25,9 @@ module Docs · 24 unmodified lines
2525 &copy; 2016 Richard Davey, Photon Storm Ltd.<br>
2626 Licensed under the MIT License.
2727 HTML
28+
29+ def get_latest_version(opts)
30+ get_latest_github_release('photonstorm', 'phaser', opts)
31+ end
2832 end
2933 end
lib/docs/scrapers/phoenix.rb+6−1
@@ -1,7 +1,7 @@
11 module Docs
22 class Phoenix < UrlScraper
33 self.type = 'elixir'
4− self.release = '1.3.0'
4+ self.release = '1.3.4'
55 self.base_url = 'https://hexdocs.pm/'
66 self.root_path = 'phoenix/Phoenix.html'
77 self.initial_paths = %w(
@@ -46,5 +46,10 @@ module Docs · 38 unmodified lines
4646 HTML
4747 end
4848 }
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
4954 end
5055 end
lib/docs/scrapers/php.rb+13−8
@@ -1,10 +1,12 @@
11 module Docs
22 class Php < FileScraper
3+ # Downloaded from php.net/download-docs.php
4+
35 include FixInternalUrlsBehavior
46
57 self.name = 'PHP'
68 self.type = 'php'
7− self.release = 'up to 7.1.5'
9+ self.release = '7.2.9'
810 self.base_url = 'https://secure.php.net/manual/en/'
911 self.root_path = 'index.html'
1012 self.initial_paths = %w(
@@ -20,12 +22,9 @@ module Docs · 9 unmodified lines
2022
2123 self.links = {
2224 home: 'https://secure.php.net/',
23− code: 'https://github.com/php/php-src'
25+ code: 'https://git.php.net/?p=php-src.git;a=summary'
2426 }
2527
26− # Downloaded from php.net/download-docs.php
27− self.dir = '/Users/Thibaut/DevDocs/Docs/PHP'
28−
2928 html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title'
3029 text_filters.push 'php/fix_urls'
3130
@@ -45,7 +44,7 @@ module Docs · 13 unmodified lines
4544
4645 BOOKS = %w(apache apc apcu array bc bzip2 calendar csprng classobj ctype curl
4746 datetime dba dir dom ds eio errorfunc ev event exec exif fileinfo filesystem filter
48− ftp funchand gearman geoip gettext gmagick gmp hash iconv iisfunc image
47+ ftp funchand gearman geoip gettext gmagick gmp hash ibase iconv iisfunc image
4948 imagick imap info inotify intl json judy ldap libevent libxml lua mail mailparse
5049 math mbstring mcrypt memcached misc mysqli network oauth openssl
5150 outcontrol password pcntl pcre pdo pgsql phar posix proctitle pthreads quickhash regex runkit
@@ -61,11 +60,17 @@ module Docs · 9 unmodified lines
6160 function.mssql-select-db.html
6261 pthreads.modifiers.html)
6362
64− options[:skip_patterns] = [/mysqlnd/]
63+ options[:skip_patterns] = [/mysqlnd/, /xdevapi/i]
6564
6665 options[:attribution] = <<-HTML
67− &copy; 1997&ndash;2017 The PHP Documentation Group<br>
66+ &copy; 1997&ndash;2018 The PHP Documentation Group<br>
6867 Licensed under the Creative Commons Attribution License v3.0 or later.
6968 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
7075 end
7176 end
lib/docs/scrapers/phpunit.rb+7−1
@@ -24,7 +24,7 @@ module Docs · 23 unmodified lines
2424 HTML
2525
2626 version '6' do
27− self.release = '6.3'
27+ self.release = '6.5'
2828 self.base_url = "https://phpunit.de/manual/#{release}/en/"
2929 end
3030
@@ -37,5 +37,11 @@ module Docs · 6 unmodified lines
3737 self.release = '4.8'
3838 self.base_url = "https://phpunit.de/manual/#{release}/en/"
3939 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
4046 end
4147 end
lib/docs/scrapers/postgresql.rb+21−4
@@ -51,23 +51,40 @@ module Docs · 50 unmodified lines
5151 /\Aunsupported-features/ ]
5252
5353 options[:attribution] = <<-HTML
54− &copy; 1996&ndash;2017 The PostgreSQL Global Development Group<br>
54+ &copy; 1996&ndash;2018 The PostgreSQL Global Development Group<br>
5555 Licensed under the PostgreSQL License.
5656 HTML
5757
58+ version '10' do
59+ self.release = '10.3'
60+ self.base_url = 'https://www.postgresql.org/docs/10/static/'
61+ end
62+
5863 version '9.6' do
59− self.release = '9.6.5'
64+ self.release = '9.6.6'
6065 self.base_url = 'https://www.postgresql.org/docs/9.6/static/'
66+
67+ html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'
6168 end
6269
6370 version '9.5' do
64− self.release = '9.5.9'
71+ self.release = '9.5.10'
6572 self.base_url = 'https://www.postgresql.org/docs/9.5/static/'
73+
74+ html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'
6675 end
6776
6877 version '9.4' do
69− self.release = '9.4.14'
78+ self.release = '9.4.15'
7079 self.base_url = 'https://www.postgresql.org/docs/9.4/static/'
80+
81+ html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'
82+ 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]
7188 end
7289 end
7390 end
lib/docs/scrapers/pug.rb+5−1
@@ -3,7 +3,7 @@ module Docs · 2 unmodified lines
33 self.type = 'pug'
44 self.base_url = 'https://pugjs.org/'
55 self.root_path = 'api/getting-started.html'
6− self.release = '2.0.0-rc.2'
6+ self.release = '2.0.3'
77 self.links = {
88 home: 'https://pugjs.org/',
99 code: 'https://github.com/pugjs/pug'
@@ -18,6 +18,10 @@ module Docs · 8 unmodified lines
1818 Licensed under the MIT license.
1919 HTML
2020
21+ def get_latest_version(opts)
22+ get_npm_version('pug', opts)
23+ end
24+
2125 private
2226
2327 def parse(response) # Hook here because Nokogori removes whitespace from textareas
lib/docs/scrapers/puppeteer.rb+23−0
@@ -0,0 +1,23 @@
1+module 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
23+end
lib/docs/scrapers/pygame.rb+25−0
@@ -0,0 +1,25 @@
1+module 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
25+end
lib/docs/scrapers/python.rb+20−11
@@ -1,6 +1,6 @@
11 module Docs
22 class Python < FileScraper
3− self.type = 'sphinx'
3+ self.type = 'python'
44 self.root_path = 'library/index.html'
55 self.links = {
66 home: 'https://www.python.org/',
@@ -19,32 +19,41 @@ module Docs · 12 unmodified lines
1919 library/sunau.html)
2020
2121 options[:attribution] = <<-HTML
22− &copy; 2001&ndash;2017 Python Software Foundation<br>
22+ &copy; 2001&ndash;2018 Python Software Foundation<br>
2323 Licensed under the PSF License.
2424 HTML
2525
26− version '3.6' do
27− self.release = '3.6.1'
28− self.dir = '/Users/Thibaut/DevDocs/Docs/Python36' # docs.python.org/3.6/download.html
26+ version '3.7' do # docs.python.org/3.7/download.html
27+ self.release = '3.7.4'
28+ self.base_url = 'https://docs.python.org/3.7/'
29+
30+ html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'
31+ end
32+
33+ version '3.6' do # docs.python.org/3.6/download.html
34+ self.release = '3.6.9'
2935 self.base_url = 'https://docs.python.org/3.6/'
3036
3137 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'
3238 end
3339
34− version '3.5' do
35− self.release = '3.5.3'
36− self.dir = '/Users/Thibaut/DevDocs/Docs/Python35' # docs.python.org/3.5/download.html
40+ version '3.5' do # docs.python.org/3.5/download.html
41+ self.release = '3.5.7'
3742 self.base_url = 'https://docs.python.org/3.5/'
3843
3944 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'
4045 end
4146
42− version '2.7' do
43− self.release = '2.7.13'
44− self.dir = '/Users/Thibaut/DevDocs/Docs/Python27' # docs.python.org/2.7/download.html
47+ version '2.7' do # docs.python.org/2.7/download.html
48+ self.release = '2.7.16'
4549 self.base_url = 'https://docs.python.org/2.7/'
4650
4751 html_filters.push 'python/entries_v2', 'sphinx/clean_html', 'python/clean_html'
4852 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
4958 end
5059 end
lib/docs/scrapers/q.rb+5−1
@@ -1,7 +1,7 @@
11 module Docs
22 class Q < Github
33 self.name = 'Q'
4− self.release = '1.5.0'
4+ self.release = '1.5.1'
55 self.base_url = 'https://github.com/kriskowal/q/wiki/'
66 self.root_path = 'API-Reference'
77 self.links = {
@@ -19,5 +19,9 @@ module Docs · 11 unmodified lines
1919 &copy; 2009&ndash;2017 Kristopher Michael Kowal<br>
2020 Licensed under the MIT License.
2121 HTML
22+
23+ def get_latest_version(opts)
24+ get_npm_version('q', opts)
25+ end
2226 end
2327 end
lib/docs/scrapers/qt.rb+136−0
@@ -0,0 +1,136 @@
1+module 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+

26 more lines aren’t shown here.

lib/docs/scrapers/ramda.rb+7−3

This diff is large: its 23 lines aren’t shown here.

lib/docs/scrapers/rdoc/minitest.rb+12−2

This diff is large: its 28 lines aren’t shown here.

lib/docs/scrapers/rdoc/rails.rb+21−6

This diff is large: its 64 lines aren’t shown here.

lib/docs/scrapers/rdoc/ruby.rb+22−17

This diff is large: its 59 lines aren’t shown here.

lib/docs/scrapers/react.rb+7−2

This diff is large: its 24 lines aren’t shown here.

lib/docs/scrapers/react_native.rb+8−3

This diff is large: its 33 lines aren’t shown here.

lib/docs/scrapers/redis.rb+8−2

This diff is large: its 25 lines aren’t shown here.

lib/docs/scrapers/redux.rb+5−1

This diff is large: its 18 lines aren’t shown here.

lib/docs/scrapers/relay.rb+5−0

This diff is large: its 11 lines aren’t shown here.

lib/docs/scrapers/requirejs.rb+5−1

This diff is large: its 19 lines aren’t shown here.

lib/docs/scrapers/rethinkdb.rb+4−0

This diff is large: its 11 lines aren’t shown here.

lib/docs/scrapers/rust.rb+14−5

This diff is large: its 49 lines aren’t shown here.

lib/docs/scrapers/sass.rb+5−1

This diff is large: its 19 lines aren’t shown here.

lib/docs/scrapers/scikit_image.rb+7−2

This diff is large: its 22 lines aren’t shown here.

lib/docs/scrapers/scikit_learn.rb+7−2

This diff is large: its 27 lines aren’t shown here.

lib/docs/scrapers/sinon.rb+27−7

This diff is large: its 64 lines aren’t shown here.

lib/docs/scrapers/socketio.rb+4−0

This diff is large: its 10 lines aren’t shown here.

lib/docs/scrapers/sqlite.rb+6−2

This diff is large: its 22 lines aren’t shown here.

lib/docs/scrapers/statsmodels.rb+5−1

This diff is large: its 19 lines aren’t shown here.

lib/docs/scrapers/support_tables.rb+6−0

This diff is large: its 12 lines aren’t shown here.

lib/docs/scrapers/symfony.rb+28−9

This diff is large: its 63 lines aren’t shown here.

lib/docs/scrapers/tcl_tk.rb+5−0

This diff is large: its 11 lines aren’t shown here.

lib/docs/scrapers/tensorflow.rb+6−2

This diff is large: its 29 lines aren’t shown here.

lib/docs/scrapers/terraform.rb+27−0

This diff is large: its 28 lines aren’t shown here.

lib/docs/scrapers/twig.rb+11−6

This diff is large: its 40 lines aren’t shown here.

lib/docs/scrapers/typescript.rb+6−2

This diff is large: its 22 lines aren’t shown here.

lib/docs/scrapers/underscore.rb+12−3

This diff is large: its 30 lines aren’t shown here.

lib/docs/scrapers/vagrant.rb+8−3

This diff is large: its 26 lines aren’t shown here.

lib/docs/scrapers/vue.rb+6−2

This diff is large: its 25 lines aren’t shown here.

lib/docs/scrapers/vulkan.rb+6−1

This diff is large: its 20 lines aren’t shown here.

lib/docs/scrapers/webpack.rb+5−1

This diff is large: its 19 lines aren’t shown here.

lib/docs/scrapers/yarn.rb+7−3

This diff is large: its 24 lines aren’t shown here.

lib/docs/scrapers/yii.rb+4−0

This diff is large: its 10 lines aren’t shown here.

lib/docs/subscribers/doc_subscriber.rb+4−0

This diff is large: its 11 lines aren’t shown here.

lib/docs/subscribers/requester_subscriber.rb+13−0

This diff is large: its 14 lines aren’t shown here.

lib/tasks/assets.thor+3−0

This diff is large: its 10 lines aren’t shown here.

lib/tasks/docs.thor+117−15

This diff is large: its 178 lines aren’t shown here.

lib/tasks/sprites.thor+213−0

This diff is large: its 214 lines aren’t shown here.

lib/tasks/updates.thor+315−0

This diff is large: its 316 lines aren’t shown here.

newrelic.yml+221−0

This diff is large: its 222 lines aren’t shown here.

public/docs/docs.json+0−1

This diff is large: its 2 lines aren’t shown here.

public/favicon.pxm+0−0

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/babel/SOURCE+1−0

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/bluebird/SOURCE+1−0

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/eslint/SOURCE+1−0

This diff is large: its 2 lines aren’t shown here.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/falcon/SOURCE+1−1

This diff is large: its 3 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/homebrew/SOURCE+1−0

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/jekyll/SOURCE+1−0

This diff is large: its 2 lines aren’t shown here.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/leaflet/SOURCE+1−0

This diff is large: its 2 lines aren’t shown here.

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

Binary file not shown.

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

Binary file not shown.

public/icons/docs/mocha/16@2x.png+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

This diff is large: its 15 lines aren’t shown here.

public/icons/docs/moment/moment.sketch/version+1−0

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 2 lines aren’t shown here.

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

Binary file not shown.

public/icons/docs/sass/sass.pxm+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/terraform/16.png+0−0

Binary file not shown.

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

Binary file not shown.

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

This diff is large: its 19 lines aren’t shown here.

public/icons/docs/xpath/XPath.sketch/version+1−0

This diff is large: its 2 lines aren’t shown here.

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

This diff is large: its 29 lines aren’t shown here.

public/opensearch.xml+3−3

This diff is large: its 14 lines aren’t shown here.

test/app_test.rb+36−90

This diff is large: its 254 lines aren’t shown here.

test/lib/docs/core/doc_test.rb+21−0

This diff is large: its 55 lines aren’t shown here.

test/lib/docs/core/manifest_test.rb+11−40

This diff is large: its 84 lines aren’t shown here.

test/lib/docs/core/scrapers/file_scraper_test.rb+90−48

This diff is large: its 205 lines aren’t shown here.

views/app.erb+1−13

This diff is large: its 25 lines aren’t shown here.

views/index.erb+4−3

This diff is large: its 27 lines aren’t shown here.

views/manifest.erb+0−14

This diff is large: its 15 lines aren’t shown here.

views/other.erb+3−2

This diff is large: its 19 lines aren’t shown here.

views/service-worker.js.erb+61−0

This diff is large: its 62 lines aren’t shown here.

views/unsupported.erb+7−10

This diff is large: its 28 lines aren’t shown here.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t