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 @@
1root = true
2
3[*]
4charset = utf-8
5indent_style = space
6indent_size = 2
7insert_final_newline = true
8trim_trailing_whitespace = true
9
10[*.md]
11trim_trailing_whitespace = false
.github/CONTRIBUTING.mdrenamed from CONTRIBUTING.md+6−16
@@ -24,20 +24,18 @@ Want to contribute? Great. Please review the following guidelines carefully and · 23 unmodified lines
24## Requesting new features24## Requesting new features
25 25
261. Search for similar feature requests; someone may have already requested it.261. Search for similar feature requests; someone may have already requested it.
272. Make sure your feature fits DevDocs's [vision](https://github.com/Thibaut/devdocs/blob/master/README.md#vision).272. Make sure your feature fits DevDocs's [vision](../README.md#vision).
283. Provide a clear and detailed explanation of the feature and why it's important to add it.283. Provide a clear and detailed explanation of the feature and why it's important to add it.
29 29
30For general feedback and ideas, please use the [mailing list](https://groups.google.com/d/forum/devdocs).
31
32## Requesting new documentations30## Requesting new documentations
33 31
34Please don't open issues to request new documentations. 32Please don't open issues to request new documentations.
35Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) where everyone can vote.33Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) where everyone can vote.
36 34
37## Contributing code and features35## Contributing code and features
38 36
391. Search for existing issues; someone may already be working on a similar feature.371. Search for existing issues; someone may already be working on a similar feature.
402. Before embarking on any significant pull request, please open an issue describing the changes you intend to make. Otherwise you risk spending a lot of time working on something I may not want to merge. This also tells other contributors that you're working on the feature.382. Before embarking on any significant pull request, please open an issue describing the changes you intend to make. Otherwise you risk spending a lot of time working on something we may not want to merge. This also tells other contributors that you're working on the feature.
413. Follow the [coding conventions](#coding-conventions).393. Follow the [coding conventions](#coding-conventions).
424. If you're modifying the Ruby code, include tests and ensure they pass.404. If you're modifying the Ruby code, include tests and ensure they pass.
435. Try to keep your pull request small and simple.415. Try to keep your pull request small and simple.
@@ -46,7 +44,7 @@ Use the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) wher · 2 unmodified lines
46 44
47## Contributing new documentations45## Contributing new documentations
48 46
49See the [wiki](https://github.com/Thibaut/devdocs/wiki) to learn how to add new documentations.47See the [`docs` folder](https://github.com/freeCodeCamp/devdocs/tree/master/docs) to learn how to add new documentations.
50 48
51**Important:** the documentation's license must permit alteration, redistribution and commercial use, and the documented software must be released under an open source license. Feel free to get in touch if you're not sure if a documentation meets those requirements.49**Important:** the documentation's license must permit alteration, redistribution and commercial use, and the documented software must be released under an open source license. Feel free to get in touch if you're not sure if a documentation meets those requirements.
52 50
@@ -57,20 +55,12 @@ In addition to the [guidelines for contributing code](#contributing-code-and-fea · 4 unmodified lines
57* Remove as much content and HTML markup as possible, particularly content not associated with any entry (e.g. introduction, changelog, etc.).55* Remove as much content and HTML markup as possible, particularly content not associated with any entry (e.g. introduction, changelog, etc.).
58* Names must be as short as possible and unique across the documentation.56* Names must be as short as possible and unique across the documentation.
59* The number of types (categories) should ideally be less than 100.57* The number of types (categories) should ideally be less than 100.
60* Don't modify the icon sprite. I'll do it after your pull request is merged.
61 58
62## Updating existing documentations59## Updating existing documentations
63 60
64Please don't submit a pull request updating the version number of a documentation, unless a change is required in the scraper and you've verified that it works.61Please don't submit a pull request updating the version number of a documentation, unless a change is required in the scraper and you've verified that it works.
65 62
66To ask that an existing documentation be updated, please use the [Trello board](https://trello.com/c/2B0hmW7M/52-request-updates-here).63To ask that an existing documentation be updated, first check the last two [documentation versions reports](https://github.com/freeCodeCamp/devdocs/issues?utf8=%E2%9C%93&q=Documentation+versions+report+is%3Aissue+author%3Adevdocs-bot+sort%3Acreated-desc). Only create an issue if the documentation has been wrongly marked as up-to-date.
67
68## Other contributions
69
70Besides 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).
74 64
75## Coding conventions65## Coding conventions
76 66
@@ -80,4 +70,4 @@ Besides new docs and features, here are other ways you can contribute: · 3 unmodified lines
80 70
81## Questions?71## Questions?
82 72
83If you have any questions, please feel free to ask on the [mailing list](https://groups.google.com/d/forum/devdocs).73If you have any questions, please feel free to ask them on the contributor chat room on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs).
.github/ISSUE_TEMPLATE.md+7−0
@@ -0,0 +1,7 @@
1<!--
2 Please read the contributing guidelines before opening an issue:
3 https://github.com/freeCodeCamp/devdocs/blob/master/.github/CONTRIBUTING.md
4
5 Go here to request a new documentation:
6 https://trello.com/b/6BmTulfx/devdocs-documentation
7-->
.github/no-response.yml+8−0
@@ -0,0 +1,8 @@
1daysUntilClose: 30
2responseRequiredLabel: needs-info
3closeComment: >
4 This issue has been automatically closed because there has been no response
5 to our request for more information from the original author. With only the
6 information that’s currently in the issue, we don’t have enough information
7 to take action. Please comment if you have or find the answer we need so we
8 can investigate further.
.gitignore+3−4
@@ -1,10 +1,9 @@
1.DS_Store1.DS_Store
2.bundle2.bundle
3*.pxm3log
4*.sketch
5tmp4tmp
6public/assets5public/assets
7public/fonts6public/fonts
8public/docs/**/*7public/docs/**/*
9!public/docs/docs.json8docs/**/*
10!public/docs/**/index.json9!docs/*.md
.ruby-version+1−1
@@ -1 +1 @@
12.4.212.6.3
.slugignore+1−0
@@ -0,0 +1 @@
1test
.travis.yml+25−0
@@ -1 +1,26 @@
1language: ruby1language: ruby
2
3addons:
4 apt:
5 packages:
6 - libcurl4-openssl-dev
7
8cache: bundler
9
10before_install:
11 - "echo 'gem: --no-document' > ~/.gemrc"
12 - gem update --system
13 - gem install bundler
14
15script:
16 - if [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then bundle exec rake; fi
17 - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then bundle exec thor updates:check --github-token $GH_TOKEN --upload; fi
18
19deploy:
20 provider: heroku
21 app: devdocs
22 on:
23 branch: master
24 condition: $TRAVIS_EVENT_TYPE != cron
25 api_key:
26 secure: "4p1klvWJZSOImzFcKOduILjP93hlOlAhceWlYMKS4tU+TCFE8qTBzdKdFPSCsCgjB+YR9pBss+L0lJpVVMjSwFHXqpKe6EeUSltO2k7DFHfW7kXLUM/L0AfqXz+YXk76XUyZMhvOEbldPfaMaj10e8vgDOQCSHABDyK/4CU+hnI="
CODE_OF_CONDUCT.md+2−0
@@ -0,0 +1,2 @@
1
2> Our Code of Conduct is available here: <https://code-of-conduct.freecodecamp.org/>
COPYRIGHT+4−4
@@ -1,13 +1,13 @@
1Copyright 2013-2017 Thibaut Courouble and other contributors1Copyright 2013-2019 Thibaut Courouble and other contributors
2 2
3 This Source Code Form is subject to the terms of the Mozilla Public3 This Source Code Form is subject to the terms of the Mozilla Public
4 License, v. 2.0. If a copy of the MPL was not distributed with this4 License, v. 2.0. If a copy of the MPL was not distributed with this
5 file, You can obtain one at http://mozilla.org/MPL/2.0/.5 file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 6
7Please do not use the name DevDocs to endorse or promote products7Please do not use the name DevDocs to endorse or promote products
8derived from this software without my permission, except as may be8derived from this software without the maintainers' permission, except
9necessary to comply with the notice/attribution requirements.9as may be necessary to comply with the notice/attribution requirements.
10 10
11I also wish that any documentation file generated using this software11We also wish that any documentation file generated using this software
12be attributed to DevDocs. Let's be fair to all contributors by giving12be attributed to DevDocs. Let's be fair to all contributors by giving
13credit where credit's due. Thanks.13credit where credit's due. Thanks.
Dockerfile+5−2
@@ -1,9 +1,12 @@
1FROM ruby:2.4.21FROM ruby:2.6.3
2
3ENV LANG=C.UTF-8
4ENV ENABLE_SERVICE_WORKER=true
2 5
3WORKDIR /devdocs6WORKDIR /devdocs
4 7
5RUN apt-get update && \8RUN apt-get update && \
6 apt-get -y install git nodejs && \9 apt-get -y install git nodejs libcurl4 && \
7 gem install bundler && \10 gem install bundler && \
8 rm -rf /var/lib/apt/lists/*11 rm -rf /var/lib/apt/lists/*
9 12
Dockerfile-alpine+5−2
@@ -1,10 +1,13 @@
1FROM ruby:2.4.2-alpine1FROM ruby:2.6.3-alpine
2
3ENV LANG=C.UTF-8
4ENV ENABLE_SERVICE_WORKER=true
2 5
3WORKDIR /devdocs6WORKDIR /devdocs
4 7
5COPY . /devdocs8COPY . /devdocs
6 9
7RUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev && \10RUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev libcurl && \
8 gem install bundler && \11 gem install bundler && \
9 bundle install --system --without test && \12 bundle install --system --without test && \
10 thor docs:download --all && \13 thor docs:download --all && \
Gemfile+12−6
@@ -1,16 +1,20 @@
1source 'https://rubygems.org'1source 'https://rubygems.org'
2ruby '2.4.2'2ruby '2.6.3'
3 3
4gem 'rake'4gem 'rake'
5gem 'thor'5gem 'thor'
6gem 'pry', '~> 0.11.0'6gem 'pry', '~> 0.12.0'
7gem 'activesupport', '~> 5.1', require: false7gem 'activesupport', '~> 5.2', require: false
8gem 'yajl-ruby', require: false8gem 'yajl-ruby', require: false
9gem 'html-pipeline'
10gem 'typhoeus'
11gem 'nokogiri'
9 12
10group :app do13group :app do
11 gem 'rack'14 gem 'rack'
12 gem 'sinatra'15 gem 'sinatra'
13 gem 'sinatra-contrib'16 gem 'sinatra-contrib'
17 gem 'rack-ssl-enforcer'
14 gem 'thin'18 gem 'thin'
15 gem 'sprockets'19 gem 'sprockets'
16 gem 'sprockets-helpers'20 gem 'sprockets-helpers'
@@ -18,10 +22,13 @@ group :app do · 1 unmodified line
18 gem 'browser'22 gem 'browser'
19 gem 'sass'23 gem 'sass'
20 gem 'coffee-script'24 gem 'coffee-script'
25 gem 'chunky_png'
26 gem 'sprockets-sass'
21end27end
22 28
23group :production do29group :production do
24 gem 'uglifier'30 gem 'uglifier'
31 gem 'newrelic_rpm'
25end32end
26 33
27group :development do34group :development do
@@ -29,14 +36,13 @@ group :development do · 1 unmodified line
29end36end
30 37
31group :docs do38group :docs do
32 gem 'typhoeus'
33 gem 'nokogiri'
34 gem 'html-pipeline'
35 gem 'image_optim'39 gem 'image_optim'
36 gem 'image_optim_pack', platforms: :ruby40 gem 'image_optim_pack', platforms: :ruby
37 gem 'progress_bar', require: false41 gem 'progress_bar', require: false
38 gem 'unix_utils', require: false42 gem 'unix_utils', require: false
39 gem 'tty-pager', require: false43 gem 'tty-pager', require: false
44 gem 'net-sftp', '>= 2.1.3.rc2', require: false
45 gem 'terminal-table', require: false
40end46end
41 47
42group :test do48group :test do
Gemfile.lock+86−67
@@ -1,139 +1,156 @@
1GEM1GEM
2 remote: https://rubygems.org/2 remote: https://rubygems.org/
3 specs:3 specs:
4 activesupport (5.1.4)4 activesupport (5.2.3)
5 concurrent-ruby (~> 1.0, >= 1.0.2)5 concurrent-ruby (~> 1.0, >= 1.0.2)
6 i18n (~> 0.7)6 i18n (>= 0.7, < 2)
7 minitest (~> 5.1)7 minitest (~> 5.1)
8 tzinfo (~> 1.1)8 tzinfo (~> 1.1)
9 backports (3.9.1)9 backports (3.15.0)
10 better_errors (2.3.0)10 better_errors (2.5.1)
11 coderay (>= 1.0.0)11 coderay (>= 1.0.0)
12 erubi (>= 1.0.0)12 erubi (>= 1.0.0)
13 rack (>= 0.9.0)13 rack (>= 0.9.0)
14 browser (2.5.1)14 browser (2.6.1)
15 chunky_png (1.3.11)
15 coderay (1.1.2)16 coderay (1.1.2)
16 coffee-script (2.4.1)17 coffee-script (2.4.1)
17 coffee-script-source18 coffee-script-source
18 execjs19 execjs
19 coffee-script-source (1.12.2)20 coffee-script-source (1.12.2)
20 concurrent-ruby (1.0.5)21 concurrent-ruby (1.1.5)
21 daemons (1.2.4)22 daemons (1.3.1)
22 erubi (1.6.1)23 erubi (1.8.0)
23 ethon (0.10.1)24 ethon (0.12.0)
24 ffi (>= 1.3.0)25 ffi (>= 1.3.0)
25 eventmachine (1.2.5)26 eventmachine (1.2.7)
26 execjs (2.7.0)27 execjs (2.7.0)
27 exifr (1.3.2)28 exifr (1.3.6)
28 ffi (1.9.18)29 ffi (1.11.1)
29 fspath (3.1.0)30 fspath (3.1.2)
30 highline (1.7.8)31 highline (2.0.2)
31 html-pipeline (2.7.1)32 html-pipeline (2.11.1)
32 activesupport (>= 2)33 activesupport (>= 2)
33 nokogiri (>= 1.4)34 nokogiri (>= 1.4)
34 i18n (0.8.6)35 i18n (1.6.0)
35 image_optim (0.25.0)36 concurrent-ruby (~> 1.0)
37 image_optim (0.26.5)
36 exifr (~> 1.2, >= 1.2.2)38 exifr (~> 1.2, >= 1.2.2)
37 fspath (~> 3.0)39 fspath (~> 3.0)
38 image_size (~> 1.5)40 image_size (>= 1.5, < 3)
39 in_threads (~> 1.3)41 in_threads (~> 1.3)
40 progress (~> 3.0, >= 3.0.1)42 progress (~> 3.0, >= 3.0.1)
41 image_optim_pack (0.5.0.20171001)43 image_optim_pack (0.5.6)
42 fspath (>= 2.1, < 4)44 fspath (>= 2.1, < 4)
43 image_optim (~> 0.19)45 image_optim (~> 0.19)
44 image_size (1.5.0)46 image_size (2.0.2)
45 in_threads (1.4.0)47 in_threads (1.5.3)
46 method_source (0.9.0)48 method_source (0.9.2)
47 mini_portile2 (2.3.0)49 mini_portile2 (2.4.0)
48 minitest (5.10.3)50 minitest (5.11.3)
49 multi_json (1.12.2)51 multi_json (1.13.1)
50 mustermann (1.0.1)52 mustermann (1.0.3)
51 nokogiri (1.8.1)53 net-sftp (3.0.0.beta1)
52 mini_portile2 (~> 2.3.0)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)
53 options (2.3.2)59 options (2.3.2)
54 progress (3.3.1)60 progress (3.5.2)
55 progress_bar (1.1.0)61 progress_bar (1.3.0)
56 highline (~> 1.6)62 highline (>= 1.6, < 3)
57 options (~> 2.3.0)63 options (~> 2.3.0)
58 pry (0.11.1)64 pry (0.12.2)
59 coderay (~> 1.1.0)65 coderay (~> 1.1.0)
60 method_source (~> 0.9.0)66 method_source (~> 0.9.0)
61 rack (2.0.3)67 rack (2.0.7)
62 rack-protection (2.0.0)68 rack-protection (2.0.5)
63 rack69 rack
64 rack-test (0.7.0)70 rack-ssl-enforcer (0.2.9)
71 rack-test (1.1.0)
65 rack (>= 1.0, < 3)72 rack (>= 1.0, < 3)
66 rake (12.1.0)73 rake (12.3.2)
67 rb-fsevent (0.10.2)74 rb-fsevent (0.10.3)
68 rb-inotify (0.9.10)75 rb-inotify (0.10.0)
69 ffi (>= 0.5.0, < 2)76 ffi (~> 1.0)
70 rr (1.2.1)77 rr (1.2.1)
71 sass (3.5.2)78 sass (3.7.4)
72 sass-listen (~> 4.0.0)79 sass-listen (~> 4.0.0)
73 sass-listen (4.0.0)80 sass-listen (4.0.0)
74 rb-fsevent (~> 0.9, >= 0.9.4)81 rb-fsevent (~> 0.9, >= 0.9.4)
75 rb-inotify (~> 0.9, >= 0.9.7)82 rb-inotify (~> 0.9, >= 0.9.7)
76 sinatra (2.0.0)83 sinatra (2.0.5)
77 mustermann (~> 1.0)84 mustermann (~> 1.0)
78 rack (~> 2.0)85 rack (~> 2.0)
79 rack-protection (= 2.0.0)86 rack-protection (= 2.0.5)
80 tilt (~> 2.0)87 tilt (~> 2.0)
81 sinatra-contrib (2.0.0)88 sinatra-contrib (2.0.5)
82 backports (>= 2.0)89 backports (>= 2.8.2)
83 multi_json90 multi_json
84 mustermann (~> 1.0)91 mustermann (~> 1.0)
85 rack-protection (= 2.0.0)92 rack-protection (= 2.0.5)
86 sinatra (= 2.0.0)93 sinatra (= 2.0.5)
87 tilt (>= 1.3, < 3)94 tilt (>= 1.3, < 3)
88 sprockets (3.7.1)95 sprockets (3.7.2)
89 concurrent-ruby (~> 1.0)96 concurrent-ruby (~> 1.0)
90 rack (> 1, < 3)97 rack (> 1, < 3)
91 sprockets-helpers (1.2.1)98 sprockets-helpers (1.2.1)
92 sprockets (>= 2.2)99 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)
93 thin (1.7.2)109 thin (1.7.2)
94 daemons (~> 1.0, >= 1.0.9)110 daemons (~> 1.0, >= 1.0.9)
95 eventmachine (~> 1.0, >= 1.0.4)111 eventmachine (~> 1.0, >= 1.0.4)
96 rack (>= 1, < 3)112 rack (>= 1, < 3)
97 thor (0.20.0)113 thor (0.20.3)
98 thread_safe (0.3.6)114 thread_safe (0.3.6)
99 tilt (2.0.8)115 tilt (2.0.9)
100 tty-pager (0.9.0)116 tty-pager (0.12.1)
101 tty-screen (~> 0.5.0)117 strings (~> 0.1.4)
102 tty-which (~> 0.3.0)118 tty-screen (~> 0.6)
103 verse (~> 0.5.0)119 tty-which (~> 0.4)
104 tty-screen (0.5.0)120 tty-screen (0.7.0)
105 tty-which (0.3.0)121 tty-which (0.4.1)
106 typhoeus (1.3.0)122 typhoeus (1.3.1)
107 ethon (>= 0.9.0)123 ethon (>= 0.9.0)
108 tzinfo (1.2.3)124 tzinfo (1.2.5)
109 thread_safe (~> 0.1)125 thread_safe (~> 0.1)
110 uglifier (3.2.0)126 uglifier (4.1.20)
111 execjs (>= 0.3.0, < 3)127 execjs (>= 0.3.0, < 3)
112 unicode-display_width (1.1.3)128 unicode-display_width (1.6.0)
113 unicode_utils (1.4.0)129 unicode_utils (1.4.0)
114 unix_utils (0.0.15)130 unix_utils (0.0.15)
115 verse (0.5.0)131 yajl-ruby (1.4.1)
116 unicode-display_width (~> 1.1.0)
117 unicode_utils (~> 1.4.0)
118 yajl-ruby (1.3.0)
119 132
120PLATFORMS133PLATFORMS
121 ruby134 ruby
122 135
123DEPENDENCIES136DEPENDENCIES
124 activesupport (~> 5.1)137 activesupport (~> 5.2)
125 better_errors138 better_errors
126 browser139 browser
140 chunky_png
127 coffee-script141 coffee-script
128 erubi142 erubi
129 html-pipeline143 html-pipeline
130 image_optim144 image_optim
131 image_optim_pack145 image_optim_pack
132 minitest146 minitest
147 net-sftp (>= 2.1.3.rc2)
148 newrelic_rpm
133 nokogiri149 nokogiri
134 progress_bar150 progress_bar
135 pry (~> 0.11.0)151 pry (~> 0.12.0)
136 rack152 rack
153 rack-ssl-enforcer
137 rack-test154 rack-test
138 rake155 rake
139 rr156 rr
@@ -142,6 +159,8 @@ DEPENDENCIES · 2 unmodified lines
142 sinatra-contrib159 sinatra-contrib
143 sprockets160 sprockets
144 sprockets-helpers161 sprockets-helpers
162 sprockets-sass
163 terminal-table
145 thin164 thin
146 thor165 thor
147 tty-pager166 tty-pager
@@ -151,7 +170,7 @@ DEPENDENCIES · 3 unmodified lines
151 yajl-ruby170 yajl-ruby
152 171
153RUBY VERSION172RUBY VERSION
154 ruby 2.4.2p198173 ruby 2.6.3p62
155 174
156BUNDLED WITH175BUNDLED WITH
157 1.15.4176 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 @@
1If you’re adding a new scraper, please ensure that you have:
2
3- [ ] Tested the scraper on a local copy of DevDocs
4- [ ] Ensured that the docs are styled similarly to other docs on DevDocs
5<!-- If the docs don’t have an icon, delete the next four items: -->
6- [ ] Added these files to the <code>public/icons/*your_scraper_name*/</code> directory:
7 - [ ] `16.png`: a 16×16 pixel icon for the doc
8 - [ ] `16@2x.png`: a 32×32 pixel icon for the doc
9 - [ ] `SOURCE`: A text file containing the URL to the page the image can be found on or the URL of the original image itself
10
11<!-- Replace the `[ ]` with a `[x]` once you’ve completed each step. -->
Procfile+1−0
@@ -0,0 +1 @@
1web: 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)
2 2
3DevDocs combines multiple API documentations in a fast, organized, and searchable interface.3DevDocs combines multiple developer documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.
4 4
5* Created by [Thibaut Courouble](https://thibaut.me)5DevDocs was created by [Thibaut Courouble](https://thibaut.me) and is operated by [freeCodeCamp](https://www.freecodecamp.org).
6 6
7Keep track of development news:7Keep track of development news:
8 8
9* 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)
10* Follow [@DevDocs](https://twitter.com/DevDocs) on Twitter11* Follow [@DevDocs](https://twitter.com/DevDocs) on Twitter
11* Join the [mailing list](https://groups.google.com/d/forum/devdocs)
12 12
13DevDocs is free and open source. If you like it, please consider supporting my work on [Gratipay](https://gratipay.com/devdocs/). Thanks!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)
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)
16 14
17## Quick Start15## Quick Start
18 16
19Unless 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.17Unless you wish to contribute to the project, we recommend using the hosted version at [devdocs.io](https://devdocs.io). It's up-to-date and works offline out-of-the-box.
20 18
21DevDocs is made of two pieces: a Ruby scraper that generates the documentation and metadata, and a JavaScript app powered by a small Sinatra app.19DevDocs is made of two pieces: a Ruby scraper that generates the documentation and metadata, and a JavaScript app powered by a small Sinatra app.
22 20
23DevDocs 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:21DevDocs requires Ruby 2.6.0, libcurl, and a JavaScript runtime supported by [ExecJS](https://github.com/rails/execjs#readme) (included in OS X and Windows; [Node.js](https://nodejs.org/en/) on Linux). Once you have these installed, run the following commands:
24 22
25```23```
26git clone https://github.com/Thibaut/devdocs.git && cd devdocs24git clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs
27gem install bundler25gem install bundler
28bundle install26bundle install
29bundle exec thor docs:download --default27bundle exec thor docs:download --default
@@ -34,13 +32,13 @@ Finally, point your browser at [localhost:9292](http://localhost:9292) (the firs · 4 unmodified lines
34 32
35The `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`.33The `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`.
36 34
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.
38 36
39Alternatively, DevDocs may be started as a Docker container:37Alternatively, DevDocs may be started as a Docker container:
40 38
41```39```
42# First, build the image40# First, build the image
43git clone https://github.com/Thibaut/devdocs.git && cd devdocs41git clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs
44docker build -t thibaut/devdocs .42docker build -t thibaut/devdocs .
45 43
46# Finally, start a DevDocs container (access http://localhost:9292)44# 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
61 59
62Many of the code's design decisions were driven by the fact that the app uses XHR to load content directly into the main frame. This includes stripping the original documents of most of their HTML markup (e.g. scripts and stylesheets) to avoid polluting the main frame, and prefixing all CSS class names with an underscore to prevent conflicts.60Many of the code's design decisions were driven by the fact that the app uses XHR to load content directly into the main frame. This includes stripping the original documents of most of their HTML markup (e.g. scripts and stylesheets) to avoid polluting the main frame, and prefixing all CSS class names with an underscore to prevent conflicts.
63 61
64Another driving factor is performance and the fact that everything happens in the browser. `applicationCache` (which comes with its own set of constraints) and `localStorage` are used to speed up the boot time, while memory consumption is kept in check by allowing the user to pick his/her own set of documentations. The search algorithm is kept simple because it needs to be fast even searching through 100,000 strings.62Another driving factor is performance and the fact that everything happens in the browser. A service worker (which comes with its own set of constraints) and `localStorage` are used to speed up the boot time, while memory consumption is kept in check by allowing the user to pick his/her own set of documentations. The search algorithm is kept simple because it needs to be fast even searching through 100,000 strings.
65 63
66DevDocs being a developer tool, the browser requirements are high:64DevDocs being a developer tool, the browser requirements are high:
67 65
681. On the desktop:66* Recent versions of Firefox, Chrome, or Opera
69 * Recent version of Chrome, Firefox, or Opera67* Safari 11.1+
70 * Safari 8+68* Edge 17+
71 * IE / Edge 10+69* iOS 11.3+
722. On mobile:
73 * iOS 8+
74 * Android 4.1+
75 * Windows Phone 8+
76 70
77This allows the code to take advantage of the latest DOM and HTML5 APIs and make developing DevDocs a lot more fun!71This allows the code to take advantage of the latest DOM and HTML5 APIs and make developing DevDocs a lot more fun!
78 72
@@ -89,12 +83,13 @@ Modifications made to each document include: · 10 unmodified lines
89* replacing all external (not scraped) URLs with their fully qualified counterpart83* replacing all external (not scraped) URLs with their fully qualified counterpart
90* replacing all internal (scraped) URLs with their unqualified and relative counterpart84* replacing all internal (scraped) URLs with their unqualified and relative counterpart
91* adding content, such as a title and link to the original document85* adding content, such as a title and link to the original document
86* ensuring correct syntax highlighting using [Prism](http://prismjs.com/)
92 87
93These modifications are applied via a set of filters using the [HTML::Pipeline](https://github.com/jch/html-pipeline) library. Each scraper includes filters specific to itself, one of which is tasked with figuring out the pages' metadata.88These modifications are applied via a set of filters using the [HTML::Pipeline](https://github.com/jch/html-pipeline) library. Each scraper includes filters specific to itself, one of which is tasked with figuring out the pages' metadata.
94 89
95The end result is a set of normalized HTML partials and two JSON files (index + offline data). Because the index files are loaded separately by the [app](#app) following the user's preferences, the scraper also creates a JSON manifest file containing information about the documentations currently available on the system (such as their name, version, update date, etc.).90The end result is a set of normalized HTML partials and two JSON files (index + offline data). Because the index files are loaded separately by the [app](#app) following the user's preferences, the scraper also creates a JSON manifest file containing information about the documentations currently available on the system (such as their name, version, update date, etc.).
96 91
97More information about scrapers and filters is available on the [wiki](https://github.com/Thibaut/devdocs/wiki).92More information about [scrapers](./docs/scraper-reference.md) and [filters](./docs/filter-reference.md) is available in the `docs` folder.
98 93
99## Available Commands94## Available Commands
100 95
@@ -134,20 +129,45 @@ If multiple versions of Ruby are installed on your system, commands must be run · 33 unmodified lines
134 129
135## Contributing130## Contributing
136 131
137Contributions are welcome. Please read the [contributing guidelines](https://github.com/Thibaut/devdocs/blob/master/CONTRIBUTING.md).132Contributions are welcome. Please read the [contributing guidelines](./.github/CONTRIBUTING.md).
138 133
139DevDocs's own documentation is available on the [wiki](https://github.com/Thibaut/devdocs/wiki).134## Documentation
135
136* [Adding documentations to DevDocs](./docs/adding-docs.md)
137* [Scraper Reference](./docs/scraper-reference.md)
138* [Filter Reference](./docs/filter-reference.md)
139* [Maintainers’ Guide](./docs/maintainers.md)
140
141## Plugins and Extensions
142
143* [Chrome web app](https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe)
144* [Ubuntu Touch app](https://uappexplorer.com/app/devdocsunofficial.berkes)
145* [Sublime Text plugin](https://sublime.wbond.net/packages/DevDocs)
146* [Atom plugin](https://atom.io/packages/devdocs)
147* [Brackets extension](https://github.com/gruehle/dev-docs-viewer)
148* [Fluid](http://fluidapp.com) for turning DevDocs into a real OS X app
149* [GTK shell / Vim integration](https://github.com/naquad/devdocs-shell)
150* [Emacs lookup](https://github.com/skeeto/devdocs-lookup)
151* [Alfred Workflow](https://github.com/yannickglt/alfred-devdocs)
152* [Vim search plugin with Devdocs in its defaults](https://github.com/waiting-for-dev/vim-www) Just set `let g:www_shortcut_engines = { 'devdocs': ['Devdocs', '<leader>dd'] }` to have a `:Devdocs` command and a `<leader>dd` mapping.
153* [Visual Studio Code plugin](https://marketplace.visualstudio.com/items?itemName=akfish.vscode-devdocs ) (1)
154* [Visual Studio Code plugin](https://marketplace.visualstudio.com/items?itemName=deibit.devdocs) (2)
155* [Desktop application](https://github.com/egoist/devdocs-desktop)
156* [Doc Browser](https://github.com/qwfy/doc-browser) is a native Linux app that supports DevDocs docsets
157* [GNOME Application](https://github.com/hardpixel/devdocs-desktop) GTK3 application with search integrated in headerbar
158* [macOS Application](https://github.com/dteoh/devdocs-macos)
159* [Android Application](https://github.com/Merith-TK/devdocs_webapp_kotlin) is a fully working, advanced WebView
140 160
141## Copyright / License161## Copyright / License
142 162
143Copyright 2013-2017 Thibaut Courouble and [other contributors](https://github.com/Thibaut/devdocs/graphs/contributors)163Copyright 2013-2019 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors)
144 164
145This 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.165This software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](./COPYRIGHT) and [LICENSE](./LICENSE) files.
146 166
147Please do not use the name DevDocs to endorse or promote products derived from this software without my permission, except as may be necessary to comply with the notice/attribution requirements.167Please do not use the name DevDocs to endorse or promote products derived from this software without the maintainers' permission, except as may be necessary to comply with the notice/attribution requirements.
148 168
149I also wish that any documentation file generated using this software be attributed to DevDocs. Let's be fair to all contributors by giving credit where credit's due. Thanks!169We also wish that any documentation file generated using this software be attributed to DevDocs. Let's be fair to all contributors by giving credit where credit's due. Thanks!
150 170
151## Questions?171## Questions?
152 172
153If you have any questions, please feel free to ask them on the [mailing list](https://groups.google.com/d/forum/devdocs).173If you have any questions, please feel free to ask them on the contributor chat room on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs).
Rakefile+4−0
@@ -3,6 +3,7 @@ · 2 unmodified lines
3require 'bundler/setup'3require 'bundler/setup'
4require 'thor'4require 'thor'
5 5
6Bundler.require :default
6$LOAD_PATH.unshift 'lib'7$LOAD_PATH.unshift 'lib'
7 8
8task :default do9task :default do
@@ -13,6 +14,9 @@ end · 4 unmodified lines
13namespace :assets do14namespace :assets do
14 desc 'Compile all assets'15 desc 'Compile all assets'
15 task :precompile do16 task :precompile do
17 load 'tasks/docs.thor'
18 DocsCLI.new.prepare_deploy
19
16 load 'tasks/assets.thor'20 load 'tasks/assets.thor'
17 AssetsCLI.new.compile21 AssetsCLI.new.compile
18 end22 end
assets/images/.gitignore+1−0
@@ -0,0 +1 @@
1sprites/**/*
assets/images/docs-1.png+0−0

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

assets/javascripts/app/app.coffee+21−14
@@ -13,10 +13,12 @@ · 12 unmodified lines
13 13
14 @el = $('._app')14 @el = $('._app')
15 @localStorage = new LocalStorageStore15 @localStorage = new LocalStorageStore
16 @appCache = new app.AppCache if app.AppCache.isEnabled()16 @serviceWorker = new app.ServiceWorker if app.ServiceWorker.isEnabled()
17 @settings = new app.Settings17 @settings = new app.Settings
18 @db = new app.DB()18 @db = new app.DB()
19 19
20 @settings.initLayout()
21
20 @docs = new app.collections.Docs22 @docs = new app.collections.Docs
21 @disabledDocs = new app.collections.Docs23 @disabledDocs = new app.collections.Docs
22 @entries = new app.collections.Entries24 @entries = new app.collections.Entries
@@ -76,7 +78,7 @@ · 53 unmodified lines
76 .install()78 .install()
77 @previousErrorHandler = onerror79 @previousErrorHandler = onerror
78 window.onerror = @onWindowError.bind(@)80 window.onerror = @onWindowError.bind(@)
79 CookieStore.onBlocked = @onCookieBlocked81 CookiesStore.onBlocked = @onCookieBlocked
80 return82 return
81 83
82 bootOne: ->84 bootOne: ->
@@ -138,7 +140,10 @@ · 55 unmodified lines
138 @docs.sort()140 @docs.sort()
139 @initDoc(doc)141 @initDoc(doc)
140 @saveDocs()142 @saveDocs()
141 _onSuccess()143 if app.settings.get('autoInstall')
144 doc.install(_onSuccess, onError)
145 else
146 _onSuccess()
142 return147 return
143 148
144 doc.load onSuccess, onError, writeCache: true149 doc.load onSuccess, onError, writeCache: true
@@ -147,7 +152,7 @@ · 2 unmodified lines
147 saveDocs: ->152 saveDocs: ->
148 @settings.setDocs(doc.slug for doc in @docs.all())153 @settings.setDocs(doc.slug for doc in @docs.all())
149 @db.migrate()154 @db.migrate()
150 @appCache?.updateInBackground()155 @serviceWorker?.updateInBackground()
151 156
152 welcomeBack: ->157 welcomeBack: ->
153 visitCount = @settings.get('count')158 visitCount = @settings.get('count')
@@ -157,17 +162,24 @@ · 3 unmodified lines
157 new app.views.Updates()162 new app.views.Updates()
158 @updateChecker = new app.UpdateChecker()163 @updateChecker = new app.UpdateChecker()
159 164
165 reboot: ->
166 if location.pathname isnt '/' and location.pathname isnt '/settings'
167 window.location = "/##{location.pathname}"
168 else
169 window.location = '/'
170 return
171
160 reload: ->172 reload: ->
161 @docs.clearCache()173 @docs.clearCache()
162 @disabledDocs.clearCache()174 @disabledDocs.clearCache()
163 if @appCache then @appCache.reload() else window.location = '/'175 if @serviceWorker then @serviceWorker.reload() else @reboot()
164 return176 return
165 177
166 reset: ->178 reset: ->
167 @localStorage.reset()179 @localStorage.reset()
168 @settings.reset()180 @settings.reset()
169 @db?.reset()181 @db?.reset()
170 @appCache?.update()182 @serviceWorker?.update()
171 window.location = '/'183 window.location = '/'
172 return184 return
173 185
@@ -186,9 +198,9 @@ · 12 unmodified lines
186 return198 return
187 199
188 indexHost: ->200 indexHost: ->
189 # Can't load the index files from the host/CDN when applicationCache is201 # Can't load the index files from the host/CDN when service worker is
190 # enabled because it doesn't support caching URLs that use CORS.202 # enabled because it doesn't support caching URLs that use CORS.
191 @config[if @appCache and @settings.hasDocs() then 'index_path' else 'docs_origin']203 @config[if @serviceWorker and @settings.hasDocs() then 'index_path' else 'docs_origin']
192 204
193 onBootError: (args...) ->205 onBootError: (args...) ->
194 @trigger 'bootError'206 @trigger 'bootError'
@@ -245,7 +257,7 @@ · 50 unmodified lines
245 matchMedia: !!window.matchMedia257 matchMedia: !!window.matchMedia
246 insertAdjacentHTML: !!document.body.insertAdjacentHTML258 insertAdjacentHTML: !!document.body.insertAdjacentHTML
247 defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false259 defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false
248 cssGradients: supportsCssGradients()260 cssVariables: !!CSS?.supports?('(--t: 0)')
249 261
250 for key, value of features when not value262 for key, value of features when not value
251 Raven.captureMessage "unsupported/#{key}", level: 'info'263 Raven.captureMessage "unsupported/#{key}", level: 'info'
@@ -268,9 +280,4 @@ · 16 unmodified lines
268 isInvalidLocation: ->280 isInvalidLocation: ->
269 @config.env is 'production' and location.host.indexOf(app.config.production_host) isnt 0281 @config.env is 'production' and location.host.indexOf(app.config.production_host) isnt 0
270 282
271supportsCssGradients = ->
272 el = document.createElement('div')
273 el.style.cssText = "background-image: -webkit-linear-gradient(top, #000, #fff); background-image: linear-gradient(to top, #000, #fff);"
274 el.style.backgroundImage.indexOf('gradient') >= 0
275
276$.extend app, Events283$.extend app, Events
assets/javascripts/app/appcache.coffee+0−42
@@ -1,42 +0,0 @@
1class app.AppCache
2 $.extend @prototype, Events
3
4 @isEnabled: ->
5 try
6 applicationCache and applicationCache.status isnt applicationCache.UNCACHED
7 catch
8
9 constructor: ->
10 @cache = applicationCache
11 @notifyUpdate = true
12 @onUpdateReady() if @cache.status is @cache.UPDATEREADY
13
14 $.on @cache, 'progress', @onProgress
15 $.on @cache, 'updateready', @onUpdateReady
16
17 update: ->
18 @notifyUpdate = true
19 @notifyProgress = true
20 try @cache.update() catch
21 return
22
23 updateInBackground: ->
24 @notifyUpdate = false
25 @notifyProgress = false
26 try @cache.update() catch
27 return
28
29 reload: ->
30 $.on @cache, 'updateready noupdate error', -> window.location = '/'
31 @notifyUpdate = false
32 @notifyProgress = true
33 try @cache.update() catch
34 return
35
36 onProgress: (event) =>
37 @trigger 'progress', event if @notifyProgress
38 return
39
40 onUpdateReady: =>
41 @trigger 'updateready' if @notifyUpdate
42 return
assets/javascripts/app/config.coffee.erb+3−0
@@ -13,3 +13,6 @@ app.config = · 12 unmodified lines
13 version: <%= Time.now.to_i %>13 version: <%= Time.now.to_i %>
14 release: <%= Time.now.utc.httpdate.to_json %>14 release: <%= Time.now.utc.httpdate.to_json %>
15 mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'15 mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'
16 favicon_spritesheet: '<%= image_path('sprites/docs.png') %>'
17 service_worker_path: '/service-worker.js'
18 service_worker_enabled: <%= App.environment == :production || ENV['ENABLE_SERVICE_WORKER'] == 'true' %>
assets/javascripts/app/serviceworker.coffee+49−0
@@ -0,0 +1,49 @@
1class app.ServiceWorker
2 $.extend @prototype, Events
3
4 @isEnabled: ->
5 !!navigator.serviceWorker and app.config.service_worker_enabled
6
7 constructor: ->
8 @registration = null
9 @notifyUpdate = true
10
11 navigator.serviceWorker.register(app.config.service_worker_path, {scope: '/'})
12 .then(
13 (registration) => @updateRegistration(registration),
14 (error) -> console.error('Could not register service worker:', error)
15 )
16
17 update: ->
18 return unless @registration
19 @notifyUpdate = true
20 return @registration.update().catch(->)
21
22 updateInBackground: ->
23 return unless @registration
24 @notifyUpdate = false
25 return @registration.update().catch(->)
26
27 reload: ->
28 return @updateInBackground().then(() -> app.reboot())
29
30 updateRegistration: (registration) ->
31 @registration = registration
32 $.on @registration, 'updatefound', @onUpdateFound
33 return
34
35 onUpdateFound: =>
36 $.off @installingRegistration, 'statechange', @onStateChange() if @installingRegistration
37 @installingRegistration = @registration.installing
38 $.on @installingRegistration, 'statechange', @onStateChange
39 return
40
41 onStateChange: =>
42 if @installingRegistration and @installingRegistration.state == 'installed' and navigator.serviceWorker.controller
43 @installingRegistration = null
44 @onUpdateReady()
45 return
46
47 onUpdateReady: ->
48 @trigger 'updateready' if @notifyUpdate
49 return
assets/javascripts/app/settings.coffee+34−1
@@ -5,11 +5,13 @@ class app.Settings · 4 unmodified lines
5 'manualUpdate'5 'manualUpdate'
6 'fastScroll'6 'fastScroll'
7 'arrowScroll'7 'arrowScroll'
8 'analyticsConsent'
8 'docs'9 'docs'
9 'dark'10 'dark'
10 'layout'11 'layout'
11 'size'12 'size'
12 'tips'13 'tips'
14 'autoInstall'
13 ]15 ]
14 16
15 INTERNAL_KEYS = [17 INTERNAL_KEYS = [
@@ -19,6 +21,8 @@ class app.Settings · 3 unmodified lines
19 'news'21 'news'
20 ]22 ]
21 23
24 LAYOUTS: ['_max-width', '_sidebar-hidden', '_native-scrollbars']
25
22 @defaults:26 @defaults:
23 count: 027 count: 0
24 hideDisabled: false28 hideDisabled: false
@@ -26,9 +30,10 @@ class app.Settings · 1 unmodified line
26 news: 030 news: 0
27 manualUpdate: false31 manualUpdate: false
28 schema: 132 schema: 1
33 analyticsConsent: false
29 34
30 constructor: ->35 constructor: ->
31 @store = new CookieStore36 @store = new CookiesStore
32 @cache = {}37 @cache = {}
33 38
34 get: (key) ->39 get: (key) ->
@@ -38,6 +43,7 @@ class app.Settings · 3 unmodified lines
38 set: (key, value) ->43 set: (key, value) ->
39 @store.set(key, value)44 @store.set(key, value)
40 delete @cache[key]45 delete @cache[key]
46 @toggleDark(value) if key == 'dark'
41 return47 return
42 48
43 del: (key) ->49 del: (key) ->
@@ -63,6 +69,8 @@ class app.Settings · 19 unmodified lines
63 return69 return
64 70
65 setLayout: (name, enable) ->71 setLayout: (name, enable) ->
72 @toggleLayout(name, enable)
73
66 layout = (@store.get('layout') || '').split(' ')74 layout = (@store.get('layout') || '').split(' ')
67 $.arrayDelete(layout, '')75 $.arrayDelete(layout, '')
68 76
@@ -104,3 +112,28 @@ class app.Settings · 35 unmodified lines
104 @store.reset()112 @store.reset()
105 @cache = {}113 @cache = {}
106 return114 return
115
116 initLayout: ->
117 @toggleDark(@get('dark') is 1)
118 @toggleLayout(layout, @hasLayout(layout)) for layout in @LAYOUTS
119 @initSidebarWidth()
120 return
121
122 toggleDark: (enable) ->
123 classList = document.documentElement.classList
124 classList.toggle('_theme-default', !enable)
125 classList.toggle('_theme-dark', enable)
126 color = getComputedStyle(document.documentElement).getPropertyValue('--headerBackground').trim()
127 $('meta[name=theme-color]').setAttribute('content', color)
128 return
129
130 toggleLayout: (layout, enable) ->
131 classList = document.body.classList
132 classList.toggle(layout, enable) unless layout is '_sidebar-hidden'
133 classList.toggle('_overlay-scrollbars', $.overlayScrollbarsEnabled())
134 return
135
136 initSidebarWidth: ->
137 size = @get('size')
138 document.documentElement.style.setProperty('--sidebarWidth', size + 'px') if size
139 return
assets/javascripts/app/shortcuts.coffee+3−0
@@ -149,6 +149,9 @@ class app.Shortcuts · 148 unmodified lines
149 when 40149 when 40
150 @trigger 'altDown'150 @trigger 'altDown'
151 false151 false
152 when 68
153 @trigger 'altD'
154 false
152 when 70155 when 70
153 @trigger 'altF', event156 @trigger 'altF', event
154 when 71157 when 71
assets/javascripts/app/update_checker.coffee+3−3
@@ -3,13 +3,13 @@ class app.UpdateChecker · 2 unmodified lines
3 @lastCheck = Date.now()3 @lastCheck = Date.now()
4 4
5 $.on window, 'focus', @onFocus5 $.on window, 'focus', @onFocus
6 app.appCache.on 'updateready', @onUpdateReady if app.appCache6 app.serviceWorker?.on 'updateready', @onUpdateReady
7 7
8 setTimeout @checkDocs, 08 setTimeout @checkDocs, 0
9 9
10 check: ->10 check: ->
11 if app.appCache11 if app.serviceWorker
12 app.appCache.update()12 app.serviceWorker.update()
13 else13 else
14 ajax14 ajax
15 url: $('script[src*="application"]').getAttribute('src')15 url: $('script[src*="application"]').getAttribute('src')
assets/javascripts/lib/cookies_store.coffeerenamed from assets/javascripts/lib/cookie_store.coffee+5−1
@@ -1,4 +1,8 @@
1class @CookieStore1class @CookiesStore
2 # Intentionally called CookiesStore instead of CookieStore
3 # Calling it CookieStore causes issues when the Experimental Web Platform features flag is enabled in Chrome
4 # Related issue: https://github.com/freeCodeCamp/devdocs/issues/932
5
2 INT = /^\d+$/6 INT = /^\d+$/
3 7
4 @onBlocked: ->8 @onBlocked: ->
assets/javascripts/lib/favicon.coffee+64−0
@@ -0,0 +1,64 @@
1defaultUrl = null
2currentSlug = null
3
4imageCache = {}
5urlCache = {}
6
7withImage = (url, action) ->
8 if imageCache[url]
9 action(imageCache[url])
10 else
11 img = new Image()
12 img.crossOrigin = 'anonymous'
13 img.src = url
14 img.onload = () =>
15 imageCache[url] = img
16 action(img)
17
18@setFaviconForDoc = (doc) ->
19 return if currentSlug == doc.slug
20
21 favicon = $('link[rel="icon"]')
22
23 if defaultUrl == null
24 defaultUrl = favicon.href
25
26 if urlCache[doc.slug]
27 favicon.href = urlCache[doc.slug]
28 currentSlug = doc.slug
29 return
30
31 styles = window.getComputedStyle($("._icon-#{doc.slug.split('~')[0]}"), ':before')
32
33 bgUrl = app.config.favicon_spritesheet
34 sourceSize = 16
35 sourceX = Math.abs(parseInt(styles['background-position-x'].slice(0, -2)))
36 sourceY = Math.abs(parseInt(styles['background-position-y'].slice(0, -2)))
37
38 withImage(bgUrl, (docImg) ->
39 withImage(defaultUrl, (defaultImg) ->
40 size = defaultImg.width
41
42 canvas = document.createElement('canvas')
43 ctx = canvas.getContext('2d')
44
45 canvas.width = size
46 canvas.height = size
47 ctx.drawImage(defaultImg, 0, 0)
48
49 docIconPercentage = 65
50 destinationCoords = size / 100 * (100 - docIconPercentage)
51 destinationSize = size / 100 * docIconPercentage
52 ctx.drawImage(docImg, sourceX, sourceY, sourceSize, sourceSize, destinationCoords, destinationCoords, destinationSize, destinationSize)
53
54 urlCache[doc.slug] = canvas.toDataURL()
55 favicon.href = urlCache[doc.slug]
56
57 currentSlug = doc.slug
58 )
59 )
60
61@resetFavicon = () ->
62 if defaultUrl != null and currentSlug != null
63 $('link[rel="icon"]').href = defaultUrl
64 currentSlug = null
assets/javascripts/lib/license.coffee+1−1
@@ -1,5 +1,5 @@
1###1###
2 * Copyright 2013-2017 Thibaut Courouble and other contributors2 * Copyright 2013-2019 Thibaut Courouble and other contributors
3 *3 *
4 * This source code is licensed under the terms of the Mozilla4 * This source code is licensed under the terms of the Mozilla
5 * Public License, v. 2.0, a copy of which may be obtained at:5 * Public License, v. 2.0, a copy of which may be obtained at:
assets/javascripts/lib/page.coffee+18−2
@@ -190,7 +190,7 @@ isSameOrigin = (url) -> · 189 unmodified lines
190 190
191updateCanonicalLink = ->191updateCanonicalLink = ->
192 @canonicalLink ||= document.head.querySelector('link[rel="canonical"]')192 @canonicalLink ||= document.head.querySelector('link[rel="canonical"]')
193 @canonicalLink.setAttribute('href', "http://#{location.host}#{location.pathname}")193 @canonicalLink.setAttribute('href', "https://#{location.host}#{location.pathname}")
194 194
195trackers = []195trackers = []
196 196
@@ -199,5 +199,21 @@ page.track = (fn) -> · 2 unmodified lines
199 return199 return
200 200
201track = ->201track = ->
202 tracker.call() for tracker in trackers202 consentGiven = Cookies.get('analyticsConsent')
203 consentAsked = Cookies.get('analyticsConsentAsked')
204
205 if consentGiven == '1'
206 tracker.call() for tracker in trackers
207 else if consentGiven == undefined and consentAsked == undefined
208 # Only ask for consent once per browser session
209 Cookies.set('analyticsConsentAsked', '1')
210
211 new app.views.Notif 'AnalyticsConsent', autoHide: null
212 return
213
214@resetAnalytics = ->
215 for cookie in document.cookie.split(/;\s?/)
216 name = cookie.split('=')[0]
217 if name[0] == '_'
218 Cookies.expire(name)
203 return219 return
assets/javascripts/lib/util.coffee+5−1
@@ -124,7 +124,7 @@ $.empty = (el) -> · 123 unmodified lines
124 return124 return
125 125
126# Calls the function while the element is off the DOM to avoid triggering126# Calls the function while the element is off the DOM to avoid triggering
127# unecessary reflows and repaints.127# unnecessary reflows and repaints.
128$.batchUpdate = (el, fn) ->128$.batchUpdate = (el, fn) ->
129 parent = el.parentNode129 parent = el.parentNode
130 sibling = el.nextSibling130 sibling = el.nextSibling
@@ -352,6 +352,10 @@ isIE = null · 221 unmodified lines
352$.isIE = ->352$.isIE = ->
353 isIE ?= navigator.userAgent?.indexOf('MSIE') >= 0 || navigator.userAgent?.indexOf('rv:11.0') >= 0353 isIE ?= navigator.userAgent?.indexOf('MSIE') >= 0 || navigator.userAgent?.indexOf('rv:11.0') >= 0
354 354
355isChromeForAndroid = null
356$.isChromeForAndroid = ->
357 isChromeForAndroid ?= navigator.userAgent?.indexOf('Android') >= 0 && /Chrome\/([.0-9])+ Mobile/.test(navigator.userAgent)
358
355isAndroid = null359isAndroid = null
356$.isAndroid = ->360$.isAndroid = ->
357 isAndroid ?= navigator.userAgent?.indexOf('Android') >= 0361 isAndroid ?= navigator.userAgent?.indexOf('Android') >= 0
assets/javascripts/models/doc.coffee+3−1
@@ -142,4 +142,6 @@ class app.models.Doc extends app.Model · 141 unmodified lines
142 return142 return
143 143
144 isOutdated: (status) ->144 isOutdated: (status) ->
145 status and status.installed and @mtime isnt status.mtime145 return false if not status
146 isInstalled = status.installed or app.settings.get('autoInstall')
147 isInstalled and @mtime isnt status.mtime
assets/javascripts/news.json+35−3
@@ -1,5 +1,37 @@
1[1[
2 [2 [
3 "2019-07-21",
4 "Fixed several bugs, added an option to automatically download documentation and <a href=\"https://github.com/freeCodeCamp/devdocs/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+sort%3Aupdated-desc+closed%3A%3E2019-07-18+977+988+986+870+886+1024+979+975+941+831+1005+848+942\" target=\"_blank\">more</a>."
5 ],
6 [
7 "2019-07-19",
8 "Replaced the AppCache with a Service Worker (which makes DevDocs an installable PWA) and fixed layout preferences on Firefox."
9 ],
10 [
11 "2018-09-23",
12 "New documentations: <a href=\"/puppeteer/\">Puppeteer</a> and <a href=\"/handlebars/\">Handlebars.js</a>"
13 ], [
14 "2018-08-12",
15 "New documentations: <a href=\"/dart/\">Dart</a> and <a href=\"/qt/\">Qt</a>"
16 ], [
17 "2018-07-29",
18 "New documentations: <a href=\"/bash/\">Bash</a>, <a href=\"/graphite/\">Graphite</a> and <a href=\"/pygame/\">Pygame</a>"
19 ], [
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 ], [
3 "2017-09-10",35 "2017-09-10",
4 "<a href=\"/settings\">Preferences</a> can now be exported and imported."36 "<a href=\"/settings\">Preferences</a> can now be exported and imported."
5 ], [37 ], [
@@ -110,7 +142,7 @@ · 104 unmodified lines
110 "New documentations: <a href=\"/q/\">Q</a> and <a href=\"/opentsdb/\">OpenTSDB</a>"142 "New documentations: <a href=\"/q/\">Q</a> and <a href=\"/opentsdb/\">OpenTSDB</a>"
111 ], [143 ], [
112 "2015-07-26",144 "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>.",
114 "Added <code class=\"_label\">shift + &darr;/&uarr;</code> shortcut for scrolling (same as <code class=\"_label\">alt + &darr;/&uarr;</code>)."146 "Added <code class=\"_label\">shift + &darr;/&uarr;</code> shortcut for scrolling (same as <code class=\"_label\">alt + &darr;/&uarr;</code>)."
115 ], [147 ], [
116 "2015-07-05",148 "2015-07-05",
@@ -151,7 +183,7 @@ · 34 unmodified lines
151 "New <a href=\"/python2/\">Python 2</a> documentation"183 "New <a href=\"/python2/\">Python 2</a> documentation"
152 ], [184 ], [
153 "2014-11-09",185 "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>."
155 ], [187 ], [
156 "2014-10-19",188 "2014-10-19",
157 "New <a href=\"/svg/\">SVG</a>, <a href=\"/marionette/\">Marionette.js</a>, and <a href=\"/mongoose/\">Mongoose</a> documentations"189 "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
236 "New <a href=\"/ruby/\">Ruby</a> documentation"268 "New <a href=\"/ruby/\">Ruby</a> documentation"
237 ], [269 ], [
238 "2013-10-24",270 "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>."
240 ], [272 ], [
241 "2013-10-09",273 "2013-10-09",
242 "DevDocs is now available as a <a href=\"https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe\">Chrome web app</a>."274 "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
12 12
13app.templates.pageLoadError = ->13app.templates.pageLoadError = ->
14 error """ The page failed to load. """,14 error """ The page failed to load. """,
15 """ It may be missing from the server (try reloading the app) or you could be offline.<br>15 """ It may be missing from the server (try reloading the app) or you could be offline (try <a href="/offline">installing the documentation for offline usage</a> when online again).<br>
16 If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """,16 If you're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. """,
17 """ #{back} &middot; <a href="/##{location.pathname}" target="_top" class="_error-link">Reload</a>17 """ #{back} &middot; <a href="/##{location.pathname}" target="_top" class="_error-link">Reload</a>
18 &middot; <a href="#" class="_error-link" data-retry>Retry</a> """18 &middot; <a href="#" class="_error-link" data-retry>Retry</a> """
19 19
@@ -37,10 +37,10 @@ app.templates.offlineError = (reason, exception) -> · 17 unmodified lines
37 """ Your browser appears to be running in private mode.<br>37 """ Your browser appears to be running in private mode.<br>
38 This prevents DevDocs from caching documentations for offline access."""38 This prevents DevDocs from caching documentations for offline access."""
39 when 'exception'39 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>
41 <code class="_label">#{exception.name}: #{exception.message}</code> """41 <code class="_label">#{exception.name}: #{exception.message}</code> """
42 when 'cant_open'42 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>
44 <code class="_label">#{exception.name}: #{exception.message}</code><br>44 <code class="_label">#{exception.name}: #{exception.message}</code><br>
45 This could be because you're browsing in private mode or have disallowed offline storage on the domain. """45 This could be because you're browsing in private mode or have disallowed offline storage on the domain. """
46 when 'version'46 when 'version'
@@ -56,21 +56,18 @@ app.templates.unsupportedBrowser = """ · 9 unmodified lines
56 <h1 class="_fail-title">Your browser is unsupported, sorry.</h1>56 <h1 class="_fail-title">Your browser is unsupported, sorry.</h1>
57 <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:57 <p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:
58 <ul class="_fail-list">58 <ul class="_fail-list">
59 <li>Recent versions of Chrome and Firefox59 <li>Recent versions of Firefox, Chrome, or Opera
60 <li>Safari 5.1+60 <li>Safari 11.1+
61 <li>Opera 12.1+61 <li>Edge 17+
62 <li>Internet Explorer 10+62 <li>iOS 11.3+
63 <li>iOS 6+
64 <li>Android 4.1+
65 <li>Windows Phone 8+
66 </ul>63 </ul>
67 <p class="_fail-text">64 <p class="_fail-text">
68 If you're unable to upgrade, I apologize.65 If you're unable to upgrade, we apologize.
69 I decided to prioritize speed and new features over support for older browsers.66 We decided to prioritize speed and new features over support for older browsers.
70 <p class="_fail-text">67 <p class="_fail-text">
71 Note: if you're already using one of the browsers above, check your settings and add-ons.68 Note: if you're already using one of the browsers above, check your settings and add-ons.
72 The app uses feature detection, not user agent sniffing.69 The app uses feature detection, not user agent sniffing.
73 <p class="_fail-text">70 <p class="_fail-text">
74 &mdash; Thibaut <a href="https://twitter.com/DevDocs" class="_fail-link">@DevDocs</a>71 &mdash; <a href="https://twitter.com/DevDocs">@DevDocs</a>
75 </div>72 </div>
76"""73"""
assets/javascripts/templates/notice_tmpl.coffee+1−1
@@ -2,7 +2,7 @@ notice = (text) -> """<p class="_notice-text">#{text}</p>""" · 1 unmodified line
2 2
3app.templates.singleDocNotice = (doc) ->3app.templates.singleDocNotice = (doc) ->
4 notice """ You're browsing the #{doc.fullName} documentation. To browse all docs, go to4 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>). """
6 6
7app.templates.disabledDocNotice = ->7app.templates.disabledDocNotice = ->
8 notice """ <strong>This documentation is disabled.</strong>8 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
13 """<span data-behavior="reboot"><a href="#" data-behavior="reboot">Reload the page</a> to use the new version.</span>"""13 """<span data-behavior="reboot"><a href="#" data-behavior="reboot">Reload the page</a> to use the new version.</span>"""
14 14
15app.templates.notifError = ->15app.templates.notifError = ->
16 textNotif """ Oops, an error occured. """,16 textNotif """ Oops, an error occurred. """,
17 """ Try <a href="#" data-behavior="hard-reload">reloading</a>, and if the problem persists,17 """ Try <a href="#" data-behavior="hard-reload">reloading</a>, and if the problem persists,
18 <a href="#" data-behavior="reset">resetting the app</a>.<br>18 <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>. """
20 20
21app.templates.notifQuotaExceeded = ->21app.templates.notifQuotaExceeded = ->
22 textNotif """ The offline database has exceeded its size limitation. """,22 textNotif """ The offline database has exceeded its size limitation. """,
@@ -31,7 +31,7 @@ app.templates.notifInvalidLocation = -> · 8 unmodified lines
31 """ Otherwise things are likely to break. """31 """ Otherwise things are likely to break. """
32 32
33app.templates.notifImportInvalid = ->33app.templates.notifImportInvalid = ->
34 textNotif """ Oops, an error occured. """,34 textNotif """ Oops, an error occurred. """,
35 """ The file you selected is invalid. """35 """ The file you selected is invalid. """
36 36
37app.templates.notifNews = (news) ->37app.templates.notifNews = (news) ->
@@ -68,3 +68,9 @@ app.templates.notifShare = -> · 30 unmodified lines
68app.templates.notifUpdateDocs = ->68app.templates.notifUpdateDocs = ->
69 textNotif """ Documentation updates available. """,69 textNotif """ Documentation updates available. """,
70 """ <a href="/offline">Install them</a> as soon as possible to avoid broken pages. """70 """ <a href="/offline">Install them</a> as soon as possible to avoid broken pages. """
71
72app.templates.notifAnalyticsConsent = ->
73 textNotif """ Tracking cookies """,
74 """ We would like to gather usage data about how DevDocs is used through Google Analytics and Gauges. We only collect anonymous traffic information.
75 Please confirm if you accept our tracking cookies. You can always change your decision in the settings.
76 <br><span class="_notif-right"><a href="#" data-behavior="accept-analytics">Accept</a> or <a href="#" data-behavior="decline-analytics">Decline</a></span> """
assets/javascripts/templates/pages/about_tmpl.coffee+146−72
@@ -11,28 +11,22 @@ app.templates.aboutPage = -> """ · 10 unmodified lines
11 </nav>11 </nav>
12 12
13 <h1 class="_lined-heading">DevDocs: API Documentation Browser</h1>13 <h1 class="_lined-heading">DevDocs: API Documentation Browser</h1>
14 <p>DevDocs combines multiple API documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.14 <p>DevDocs combines multiple developer documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.
15 <ul>15 <p>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>. It was created by <a href="https://thibaut.me">Thibaut Courouble</a> and is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a>.
16 <li>Created and maintained by <a href="https://thibaut.me">Thibaut Courouble</a>
17 <li>Free and <a href="https://github.com/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>
20 <p>To keep up-to-date with the latest news:16 <p>To keep up-to-date with the latest news:
21 <ul>17 <ul>
22 <li>Follow <a href="https://twitter.com/DevDocs">@DevDocs</a> on Twitter18 <li>Follow <a href="https://twitter.com/DevDocs">@DevDocs</a> on Twitter
23 <li>Watch the repository on <a href="https://github.com/Thibaut/devdocs/subscription">GitHub</a>19 <li>Watch the repository on <a href="https://github.com/freeCodeCamp/devdocs/subscription">GitHub</a> <iframe class="_github-btn" src="https://ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20" tabindex="-1"></iframe>
24 <li>Join the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>20 <li>Join the <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a> chat room
25 </ul>21 </ul>
26 <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>
28 22
29 <h2 class="_block-heading" id="copyright">Copyright and License</h2>23 <h2 class="_block-heading" id="copyright">Copyright and License</h2>
30 <p class="_note">24 <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>
32 This software is licensed under the terms of the Mozilla Public License v2.0.<br>26 This software is licensed under the terms of the Mozilla Public License v2.0.<br>
33 You may obtain a copy of the source code at <a href="https://github.com/Thibaut/devdocs">github.com/Thibaut/devdocs</a>.<br>27 You may obtain a copy of the source code at <a href="https://github.com/freeCodeCamp/devdocs">github.com/freeCodeCamp/devdocs</a>.<br>
34 For more information, see the <a href="https://github.com/Thibaut/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a>28 For more information, see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a>
35 and <a href="https://github.com/Thibaut/devdocs/blob/master/LICENSE">LICENSE</a> files.29 and <a href="https://github.com/freeCodeCamp/devdocs/blob/master/LICENSE">LICENSE</a> files.
36 30
37 <h2 class="_block-heading" id="plugins">Plugins and Extensions</h2>31 <h2 class="_block-heading" id="plugins">Plugins and Extensions</h2>
38 <ul>32 <ul>
@@ -49,20 +43,19 @@ app.templates.aboutPage = -> """ · 10 unmodified lines
49 <dl>43 <dl>
50 <dt>Where can I suggest new docs and features?44 <dt>Where can I suggest new docs and features?
51 <dd>You can suggest and vote for new docs on the <a href="https://trello.com/b/6BmTulfx/devdocs-documentation">Trello board</a>.<br>45 <dd>You can suggest and vote for new docs on the <a href="https://trello.com/b/6BmTulfx/devdocs-documentation">Trello board</a>.<br>
52 If you have a specific feature request, add it to the <a href="https://github.com/Thibaut/devdocs/issues">issue tracker</a>.<br>46 If you have a specific feature request, add it to the <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a>.<br>
53 Otherwise use the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>.47 Otherwise, come talk to us in the <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a> chat room.
54 <dt>Where can I report bugs?48 <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!
56 </dl>50 </dl>
57 <p>For anything else, feel free to email me at <a href="mailto:thibaut@devdocs.io">thibaut@devdocs.io</a>.
58 51
59 <h2 class="_block-heading" id="credits">Credits</h2>52 <h2 class="_block-heading" id="credits">Credits</h2>
60 53
61 <p><strong>Special thanks to:</strong>54 <p><strong>Special thanks to:</strong>
62 <ul>55 <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 DevDocs56 <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
64 <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 past57 <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 service58 <li><a href="https://www.heroku.com">Heroku</a> and <a href="https://newrelic.com/">New Relic</a> for providing awesome free service
66 <li><a href="https://www.jeremykratz.com/">Jeremy Kratz</a> for the C/C++ logo59 <li><a href="https://www.jeremykratz.com/">Jeremy Kratz</a> for the C/C++ logo
67 </ul>60 </ul>
68 61
@@ -78,43 +71,59 @@ app.templates.aboutPage = -> """ · 9 unmodified lines
78 71
79 <h2 class="_block-heading" id="privacy">Privacy Policy</h2>72 <h2 class="_block-heading" id="privacy">Privacy Policy</h2>
80 <ul>73 <ul>
81 <li><a href="http://devdocs.io">devdocs.io</a> ("App") is operated by Thibaut Courouble ("We").74 <li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a> ("We").
82 <li>We do not collect personal information.75 <li>We do not collect personal information through the app.
83 <li>We use Google Analytics, Gauges and Sentry to collect anonymous traffic information and improve the app.76 <li>We use Google Analytics and Gauges to collect anonymous traffic information if you have given consent to this. You can change your decision in the <a href="/settings">settings</a>.
77 <li>We use Sentry to collect crash data and improve the app.
84 <li>The app uses cookies to store user preferences.78 <li>The app uses cookies to store user preferences.
85 <li>By using the app, you signify your acceptance of this policy. If you do not agree to this policy, please do not use the app.79 <li>By using the app, you signify your acceptance of this policy. If you do not agree to this policy, please do not use the app.
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>.
87 </ul>81 </ul>
88"""82"""
89 83
90credits = [84credits = [
91 [ 'Angular<br>Angular.js',85 [ 'Angular<br>Angular.js',
92 '2010-2017 Google, Inc.',86 '2010-2018 Google, Inc.',
93 'CC BY',87 'CC BY',
94 'https://creativecommons.org/licenses/by/4.0/'88 'https://creativecommons.org/licenses/by/4.0/'
95 ], [89 ], [
96 'Ansible',90 'Ansible',
97 '2012-2017 Michael DeHaan<br>&copy; 2017 Red Hat, Inc.',91 '2012-2018 Michael DeHaan<br>&copy; 2018–2019 Red Hat, Inc.',
98 'GPLv3',92 'GPLv3',
99 'https://raw.githubusercontent.com/ansible/ansible/devel/COPYING'93 'https://raw.githubusercontent.com/ansible/ansible/devel/COPYING'
100 ], [94 ], [
101 'Apache HTTP Server<br>Apache Pig',95 '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.',
103 'Apache',97 'Apache',
104 'https://www.apache.org/licenses/LICENSE-2.0'98 'https://www.apache.org/licenses/LICENSE-2.0'
105 ], [99 ], [
106 'Async',100 'Async',
107 '2010-2017 Caolan McMahon',101 '2010-2018 Caolan McMahon',
108 'MIT',102 'MIT',
109 'https://raw.githubusercontent.com/caolan/async/master/LICENSE'103 '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'
110 ], [109 ], [
111 'Backbone.js',110 'Backbone.js',
112 '2010-2016 Jeremy Ashkenas, DocumentCloud',111 '2010-2016 Jeremy Ashkenas, DocumentCloud',
113 'MIT',112 'MIT',
114 'https://raw.githubusercontent.com/jashkenas/backbone/master/LICENSE'113 'https://raw.githubusercontent.com/jashkenas/backbone/master/LICENSE'
114 ], [
115 'Bash',
116 '2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.',
117 'GFDL',
118 'https://www.gnu.org/licenses/fdl-1.3.en.html'
119 ], [
120 'Bluebird',
121 '2013-2017 Petka Antonov',
122 'MIT',
123 'https://raw.githubusercontent.com/petkaantonov/bluebird/master/LICENSE'
115 ], [124 ], [
116 'Bootstrap',125 'Bootstrap',
117 '2011-2017 Twitter, Inc.<br>2011-2017 The Bootstrap Authors',126 '2011-2019 Twitter, Inc.<br>2011-2019 The Bootstrap Authors',
118 'CC BY',127 'CC BY',
119 'https://creativecommons.org/licenses/by/3.0/'128 'https://creativecommons.org/licenses/by/3.0/'
120 ], [129 ], [
@@ -124,7 +133,7 @@ credits = [ · 3 unmodified lines
124 'https://raw.githubusercontent.com/bottlepy/bottle/master/LICENSE'133 'https://raw.githubusercontent.com/bottlepy/bottle/master/LICENSE'
125 ], [134 ], [
126 'Bower',135 'Bower',
127 '2017 Bower contributors',136 '2018 Bower contributors',
128 'MIT',137 'MIT',
129 'https://github.com/bower/bower.github.io/blob/1057905c18d899106f91372e6cca7ef54a91d60f/package.json#L20'138 'https://github.com/bower/bower.github.io/blob/1057905c18d899106f91372e6cca7ef54a91d60f/package.json#L20'
130 ], [139 ], [
@@ -134,9 +143,9 @@ credits = [ · 3 unmodified lines
134 'http://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA'143 'http://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA'
135 ], [144 ], [
136 'CakePHP',145 'CakePHP',
137 '2005-2017 The Cake Software Foundation, Inc.',146 '2005-2018 The Cake Software Foundation, Inc.',
138 'MIT',147 'MIT',
139 'https://raw.githubusercontent.com/cakephp/cakephp/master/LICENSE.txt'148 'https://raw.githubusercontent.com/cakephp/cakephp/master/LICENSE'
140 ], [149 ], [
141 'Chai',150 'Chai',
142 '2016 Chai.js Assertion Library',151 '2016 Chai.js Assertion Library',
@@ -154,12 +163,12 @@ credits = [ · 11 unmodified lines
154 'https://github.com/clojure/clojure/blob/master/epl-v10.html'163 'https://github.com/clojure/clojure/blob/master/epl-v10.html'
155 ], [164 ], [
156 'CMake',165 'CMake',
157 '2000-2017 Kitware, Inc. and Contributors',166 '2000-2019 Kitware, Inc. and Contributors',
158 'BSD',167 'BSD',
159 'https://cmake.org/licensing/'168 'https://cmake.org/licensing/'
160 ], [169 ], [
161 'Codeception',170 'Codeception',
162 '2011-2017 Michael Bodnarchuk and contributors',171 '2011 Michael Bodnarchuk and contributors',
163 'MIT',172 'MIT',
164 'https://raw.githubusercontent.com/Codeception/Codeception/master/LICENSE'173 'https://raw.githubusercontent.com/Codeception/Codeception/master/LICENSE'
165 ], [174 ], [
@@ -169,17 +178,17 @@ credits = [ · 3 unmodified lines
169 'https://raw.githubusercontent.com/Codeception/CodeceptJS/master/LICENSE'178 'https://raw.githubusercontent.com/Codeception/CodeceptJS/master/LICENSE'
170 ], [179 ], [
171 'CodeIgniter',180 'CodeIgniter',
172 '2014-2017 British Columbia Institute of Technology',181 '2014-2018 British Columbia Institute of Technology',
173 'MIT',182 'MIT',
174 'https://raw.githubusercontent.com/bcit-ci/CodeIgniter/develop/license.txt'183 'https://raw.githubusercontent.com/bcit-ci/CodeIgniter/develop/license.txt'
175 ], [184 ], [
176 'CoffeeScript',185 'CoffeeScript',
177 '2009-2017 Jeremy Ashkenas',186 '2009-2018 Jeremy Ashkenas',
178 'MIT',187 'MIT',
179 'https://raw.githubusercontent.com/jashkenas/coffee-script/master/LICENSE'188 'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE'
180 ], [189 ], [
181 'Cordova',190 'Cordova',
182 '2012-2017 The Apache Software Foundation',191 '2012-2018 The Apache Software Foundation',
183 'Apache',192 'Apache',
184 'https://raw.githubusercontent.com/apache/cordova-docs/master/LICENSE'193 'https://raw.githubusercontent.com/apache/cordova-docs/master/LICENSE'
185 ], [194 ], [
@@ -194,14 +203,19 @@ credits = [ · 8 unmodified lines
194 'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE'203 'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE'
195 ], [204 ], [
196 'D',205 'D',
197 '1999-2017 The D Language Foundation',206 '1999-2018 The D Language Foundation',
198 'Boost',207 'Boost',
199 'https://raw.githubusercontent.com/dlang/phobos/master/LICENSE_1_0.txt'208 'https://raw.githubusercontent.com/dlang/phobos/master/LICENSE_1_0.txt'
200 ], [209 ], [
201 'D3.js',210 'D3.js',
202 '2010-2017 Michael Bostock',211 '2010-2018 Michael Bostock',
203 'BSD',212 'BSD',
204 'https://raw.githubusercontent.com/d3/d3/master/LICENSE'213 '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/'
205 ], [219 ], [
206 'Django',220 'Django',
207 'Django Software Foundation and individual contributors',221 'Django Software Foundation and individual contributors',
@@ -209,7 +223,7 @@ credits = [ · 1 unmodified line
209 'https://raw.githubusercontent.com/django/django/master/LICENSE'223 'https://raw.githubusercontent.com/django/django/master/LICENSE'
210 ], [224 ], [
211 'Docker',225 '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.',
213 'Apache',227 'Apache',
214 'https://raw.githubusercontent.com/docker/docker.github.io/master/LICENSE'228 'https://raw.githubusercontent.com/docker/docker.github.io/master/LICENSE'
215 ], [229 ], [
@@ -224,12 +238,12 @@ credits = [ · 8 unmodified lines
224 'https://api.drupal.org/api/drupal/LICENSE.txt'238 'https://api.drupal.org/api/drupal/LICENSE.txt'
225 ], [239 ], [
226 'Electron',240 'Electron',
227 '2013-2017 GitHub Inc.',241 '2013-2018 GitHub Inc.',
228 'MIT',242 'MIT',
229 'https://raw.githubusercontent.com/electron/electron/master/LICENSE'243 'https://raw.githubusercontent.com/electron/electron/master/LICENSE'
230 ], [244 ], [
231 'Elixir',245 'Elixir',
232 '2012-2017 Plataformatec',246 '2012 Plataformatec',
233 'Apache',247 'Apache',
234 'https://raw.githubusercontent.com/elixir-lang/elixir/master/LICENSE'248 'https://raw.githubusercontent.com/elixir-lang/elixir/master/LICENSE'
235 ], [249 ], [
@@ -242,6 +256,11 @@ credits = [ · 6 unmodified lines
242 '2010-2017 Ericsson AB',256 '2010-2017 Ericsson AB',
243 'Apache',257 'Apache',
244 'https://raw.githubusercontent.com/erlang/otp/maint/LICENSE.txt'258 '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'
245 ], [264 ], [
246 'Express',265 'Express',
247 '2017 StrongLoop, IBM, and other expressjs.com contributors.',266 '2017 StrongLoop, IBM, and other expressjs.com contributors.',
@@ -264,7 +283,7 @@ credits = [ · 16 unmodified lines
264 'https://www.gnu.org/licenses/fdl-1.3.en.html'283 'https://www.gnu.org/licenses/fdl-1.3.en.html'
265 ], [284 ], [
266 'Git',285 'Git',
267 '2005-2017 Linus Torvalds and others',286 '2005-2018 Linus Torvalds and others',
268 'GPLv2',287 'GPLv2',
269 'https://raw.githubusercontent.com/git/git/master/COPYING'288 'https://raw.githubusercontent.com/git/git/master/COPYING'
270 ], [289 ], [
@@ -274,14 +293,24 @@ credits = [ · 3 unmodified lines
274 'https://creativecommons.org/licenses/by/3.0/'293 'https://creativecommons.org/licenses/by/3.0/'
275 ], [294 ], [
276 'Godot',295 'Godot',
277 '2014-2017 Juan Linietsky, Ariel Manzur, Godot Engine contributors',296 '2014-2018 Juan Linietsky, Ariel Manzur, Godot Engine contributors',
278 'MIT',297 'MIT',
279 'https://raw.githubusercontent.com/godotengine/godot/master/LICENSE.txt'298 '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'
280 ], [304 ], [
281 'Grunt',305 'Grunt',
282 'GruntJS Team',306 'GruntJS Team',
283 'MIT',307 'MIT',
284 'https://github.com/gruntjs/grunt-docs/blob/master/package.json#L10'308 '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'
285 ], [314 ], [
286 'Haskell',315 'Haskell',
287 'The University of Glasgow',316 'The University of Glasgow',
@@ -289,9 +318,14 @@ credits = [ · 1 unmodified line
289 'https://www.haskell.org/ghc/license'318 'https://www.haskell.org/ghc/license'
290 ], [319 ], [
291 'Haxe',320 'Haxe',
292 '2005-2016 Haxe Foundation',321 '2005-2018 Haxe Foundation',
293 'MIT',322 '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'
295 ], [329 ], [
296 'Immutable.js',330 'Immutable.js',
297 '2014-2016 Facebook, Inc.',331 '2014-2016 Facebook, Inc.',
@@ -307,6 +341,11 @@ credits = [ · 9 unmodified lines
307 '2008-2017 Pivotal Labs',341 '2008-2017 Pivotal Labs',
308 'MIT',342 'MIT',
309 'https://raw.githubusercontent.com/jasmine/jasmine/master/MIT.LICENSE'343 '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'
310 ], [349 ], [
311 'Jest',350 'Jest',
312 '2014-present Facebook Inc.',351 '2014-present Facebook Inc.',
@@ -329,17 +368,27 @@ credits = [ · 16 unmodified lines
329 'https://raw.githubusercontent.com/jquery/api.jqueryui.com/master/LICENSE.txt'368 'https://raw.githubusercontent.com/jquery/api.jqueryui.com/master/LICENSE.txt'
330 ], [369 ], [
331 'Julia',370 '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',
333 'MIT',372 'MIT',
334 'https://raw.githubusercontent.com/JuliaLang/julia/master/LICENSE.md'373 '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'
335 ], [379 ], [
336 'Knockout.js',380 'Knockout.js',
337 'Steven Sanderson, the Knockout.js team, and other contributors',381 'Steven Sanderson, the Knockout.js team, and other contributors',
338 'MIT',382 'MIT',
339 'https://raw.githubusercontent.com/knockout/knockout/master/LICENSE'383 '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'
340 ], [389 ], [
341 'Kotlin',390 'Kotlin',
342 '2010-2017 JetBrains s.r.o.',391 '2010-2019 JetBrains s.r.o.',
343 'Apache',392 'Apache',
344 'https://raw.githubusercontent.com/JetBrains/kotlin/master/license/LICENSE.txt'393 'https://raw.githubusercontent.com/JetBrains/kotlin/master/license/LICENSE.txt'
345 ], [394 ], [
@@ -347,6 +396,11 @@ credits = [ · 1 unmodified line
347 'Taylor Otwell',396 'Taylor Otwell',
348 'MIT',397 'MIT',
349 'https://raw.githubusercontent.com/laravel/framework/master/LICENSE.txt'398 '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'
350 ], [404 ], [
351 'Less',405 'Less',
352 '2009-2016 The Core Less Team',406 '2009-2016 The Core Less Team',
@@ -359,9 +413,9 @@ credits = [ · 6 unmodified lines
359 'https://raw.githubusercontent.com/Shopify/liquid/master/LICENSE'413 'https://raw.githubusercontent.com/Shopify/liquid/master/LICENSE'
360 ], [414 ], [
361 'Lo-Dash',415 'Lo-Dash',
362 '2012-2016 The Dojo Foundation',416 'JS Foundation and other contributors',
363 'MIT',417 'MIT',
364 'https://raw.githubusercontent.com/lodash/lodash/master/LICENSE.txt'418 'https://raw.githubusercontent.com/lodash/lodash/master/LICENSE'
365 ], [419 ], [
366 'Lua',420 'Lua',
367 '1994–2017 Lua.org, PUC-Rio',421 '1994–2017 Lua.org, PUC-Rio',
@@ -384,7 +438,7 @@ credits = [ · 16 unmodified lines
384 'https://daringfireball.net/projects/markdown/license'438 'https://daringfireball.net/projects/markdown/license'
385 ], [439 ], [
386 'Matplotlib',440 'Matplotlib',
387 '2012-2017 Matplotlib Development Team. All rights reserved.',441 '2012-2018 Matplotlib Development Team. All rights reserved.',
388 'Custom',442 'Custom',
389 'https://raw.githubusercontent.com/matplotlib/matplotlib/master/LICENSE/LICENSE'443 'https://raw.githubusercontent.com/matplotlib/matplotlib/master/LICENSE/LICENSE'
390 ], [444 ], [
@@ -399,7 +453,7 @@ credits = [ · 8 unmodified lines
399 'https://github.com/seattlerb/minitest/blob/master/README.rdoc#license'453 'https://github.com/seattlerb/minitest/blob/master/README.rdoc#license'
400 ], [454 ], [
401 'Mocha',455 'Mocha',
402 '2016 JS Foundation and contributors',456 '2011-2018 JS Foundation and contributors',
403 'CC BY',457 'CC BY',
404 'https://creativecommons.org/licenses/by/4.0/'458 'https://creativecommons.org/licenses/by/4.0/'
405 ], [459 ], [
@@ -419,12 +473,12 @@ credits = [ · 13 unmodified lines
419 'https://github.com/LearnBoost/mongoose/blob/master/README.md#license'473 'https://github.com/LearnBoost/mongoose/blob/master/README.md#license'
420 ], [474 ], [
421 'nginx',475 'nginx',
422 '2002-2017 Igor Sysoev<br>&copy; 2011-2017 Nginx, Inc.',476 '2002-2019 Igor Sysoev<br>&copy; 2011-2019 Nginx, Inc.',
423 'BSD',477 'BSD',
424 'http://nginx.org/LICENSE'478 'http://nginx.org/LICENSE'
425 ], [479 ], [
426 'nginx / Lua Module',480 '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.',
428 'BSD',482 'BSD',
429 'https://github.com/openresty/lua-nginx-module#copyright-and-license'483 'https://github.com/openresty/lua-nginx-module#copyright-and-license'
430 ], [484 ], [
@@ -464,7 +518,7 @@ credits = [ · 33 unmodified lines
464 'https://raw.githubusercontent.com/OpenTSDB/opentsdb.net/gh-pages/COPYING.LESSER'518 'https://raw.githubusercontent.com/OpenTSDB/opentsdb.net/gh-pages/COPYING.LESSER'
465 ], [519 ], [
466 'Padrino',520 'Padrino',
467 '2010-2016 Padrino',521 '2010-2019 Padrino',
468 'MIT',522 'MIT',
469 'https://raw.githubusercontent.com/padrino/padrino-framework/master/padrino/LICENSE.txt'523 'https://raw.githubusercontent.com/padrino/padrino-framework/master/padrino/LICENSE.txt'
470 ], [524 ], [
@@ -494,7 +548,7 @@ credits = [ · 23 unmodified lines
494 'https://raw.githubusercontent.com/phoenixframework/phoenix/master/LICENSE.md'548 'https://raw.githubusercontent.com/phoenixframework/phoenix/master/LICENSE.md'
495 ], [549 ], [
496 'PHP',550 'PHP',
497 '1997-2017 The PHP Documentation Group',551 '1997-2018 The PHP Documentation Group',
498 'CC BY',552 'CC BY',
499 'https://secure.php.net/manual/en/copyright.php'553 'https://secure.php.net/manual/en/copyright.php'
500 ], [554 ], [
@@ -504,12 +558,22 @@ credits = [ · 3 unmodified lines
504 'https://creativecommons.org/licenses/by/3.0/'558 'https://creativecommons.org/licenses/by/3.0/'
505 ], [559 ], [
506 'PostgreSQL',560 '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',
508 'PostgreSQL',562 'PostgreSQL',
509 'https://www.postgresql.org/about/licence/'563 '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'
510 ], [574 ], [
511 'Python',575 '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.',
513 'PSFL',577 'PSFL',
514 'https://docs.python.org/3/license.html'578 'https://docs.python.org/3/license.html'
515 ], [579 ], [
@@ -517,6 +581,11 @@ credits = [ · 1 unmodified line
517 '2009-2017 Kristopher Michael Kowal',581 '2009-2017 Kristopher Michael Kowal',
518 'MIT',582 'MIT',
519 'https://raw.githubusercontent.com/kriskowal/q/master/LICENSE'583 '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'
520 ], [589 ], [
521 'Ramda',590 'Ramda',
522 '2013-2016 Scott Sauyet and Michael Hurley',591 '2013-2016 Scott Sauyet and Michael Hurley',
@@ -524,12 +593,12 @@ credits = [ · 1 unmodified line
524 'https://raw.githubusercontent.com/ramda/ramda/master/LICENSE.txt'593 'https://raw.githubusercontent.com/ramda/ramda/master/LICENSE.txt'
525 ], [594 ], [
526 'React, React Native, Flow, Relay',595 'React, React Native, Flow, Relay',
527 '2013-2017 Facebook Inc.',596 '2013-present Facebook Inc.',
528 'CC BY',597 'MIT',
529 'https://raw.githubusercontent.com/facebook/react/master/LICENSE-docs'598 'https://raw.githubusercontent.com/facebook/react/master/LICENSE'
530 ], [599 ], [
531 'Redis',600 'Redis',
532 '2009-2017 Salvatore Sanfilippo',601 '2009-2018 Salvatore Sanfilippo',
533 'CC BY-SA',602 'CC BY-SA',
534 'https://creativecommons.org/licenses/by-sa/4.0/'603 'https://creativecommons.org/licenses/by-sa/4.0/'
535 ], [604 ], [
@@ -574,12 +643,12 @@ credits = [ · 38 unmodified lines
574 'http://scikit-image.org/docs/dev/license.html'643 'http://scikit-image.org/docs/dev/license.html'
575 ], [644 ], [
576 'scikit-learn',645 'scikit-learn',
577 '2007-2017 The scikit-learn developers',646 '2007-2018 The scikit-learn developers',
578 'BSD',647 'BSD',
579 'https://raw.githubusercontent.com/scikit-learn/scikit-learn/master/COPYING'648 'https://raw.githubusercontent.com/scikit-learn/scikit-learn/master/COPYING'
580 ], [649 ], [
581 'Sinon',650 'Sinon',
582 '2010-2017 Christian Johansen',651 '2010-2018 Christian Johansen',
583 'BSD',652 'BSD',
584 'https://raw.githubusercontent.com/sinonjs/sinon/master/LICENSE'653 'https://raw.githubusercontent.com/sinonjs/sinon/master/LICENSE'
585 ], [654 ], [
@@ -609,14 +678,19 @@ credits = [ · 23 unmodified lines
609 'http://tcl.tk/software/tcltk/license.html'678 'http://tcl.tk/software/tcltk/license.html'
610 ], [679 ], [
611 'TensorFlow',680 'TensorFlow',
612 '2017 The TensorFlow Authors',681 '2018 The TensorFlow Authors',
613 'CC BY',682 'CC BY',
614 'https://creativecommons.org/licenses/by/3.0/'683 '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'
615 ], [689 ], [
616 'Twig',690 'Twig',
617 '2009-2017 The Twig Team',691 '2009-2018 The Twig Team',
618 'BSD',692 'BSD',
619 'https://twig.sensiolabs.org/license'693 'https://twig.symfony.com/license'
620 ], [694 ], [
621 'TypeScript',695 'TypeScript',
622 'Microsoft and other contributors',696 'Microsoft and other contributors',
@@ -624,17 +698,17 @@ credits = [ · 1 unmodified line
624 'https://raw.githubusercontent.com/Microsoft/TypeScript-Handbook/master/LICENSE'698 'https://raw.githubusercontent.com/Microsoft/TypeScript-Handbook/master/LICENSE'
625 ], [699 ], [
626 'Underscore.js',700 'Underscore.js',
627 '2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors',701 '2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors',
628 'MIT',702 'MIT',
629 'https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE'703 'https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE'
630 ], [704 ], [
631 'Vagrant',705 'Vagrant',
632 '2010-2017 Mitchell Hashimoto',706 '2010-2018 Mitchell Hashimoto',
633 'MPL',707 'MPL',
634 'https://raw.githubusercontent.com/mitchellh/vagrant/master/website/LICENSE.md'708 'https://raw.githubusercontent.com/mitchellh/vagrant/master/website/LICENSE.md'
635 ], [709 ], [
636 'Vue.js',710 'Vue.js',
637 '2013-2017 Evan You, Vue.js contributors',711 '2013-2018 Evan You, Vue.js contributors',
638 'MIT',712 'MIT',
639 'https://raw.githubusercontent.com/vuejs/vue/master/LICENSE'713 'https://raw.githubusercontent.com/vuejs/vue/master/LICENSE'
640 ], [714 ], [
@@ -649,7 +723,7 @@ credits = [ · 8 unmodified lines
649 'https://creativecommons.org/licenses/by/4.0/'723 'https://creativecommons.org/licenses/by/4.0/'
650 ], [724 ], [
651 'Yarn',725 'Yarn',
652 '2016-2017 Yarn Contributors',726 '2016-present Yarn Contributors',
653 'BSD',727 'BSD',
654 'https://raw.githubusercontent.com/yarnpkg/yarn/master/LICENSE'728 'https://raw.githubusercontent.com/yarnpkg/yarn/master/LICENSE'
655 ], [729 ], [
assets/javascripts/templates/pages/help_tmpl.coffee+6−5
@@ -20,7 +20,7 @@ app.templates.helpPage = -> · 19 unmodified lines
20 </ul>20 </ul>
21 </nav>21 </nav>
22 22
23 <h1 class="_lined-heading">User Guide</h2>23 <h1 class="_lined-heading">User Guide</h1>
24 24
25 <h2 class="_block-heading" id="managing-documentations">Managing Documentations</h2>25 <h2 class="_block-heading" id="managing-documentations">Managing Documentations</h2>
26 <p>26 <p>
@@ -57,9 +57,7 @@ app.templates.helpPage = -> · 30 unmodified lines
57 <ul>57 <ul>
58 <li>On Chrome, the setup is done automatically. Simply press <code class="_label">tab</code> when devdocs.io is autocompleted58 <li>On Chrome, the setup is done automatically. Simply press <code class="_label">tab</code> when devdocs.io is autocompleted
59 in the omnibox (to set a custom keyword, click <em>Manage search engines\u2026</em> in Chrome's settings).59 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>.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.
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>.
63 </dl>61 </dl>
64 <p>62 <p>
65 <i>Note: the above search features only work for documentations that are enabled.</i>63 <i>Note: the above search features only work for documentations that are enabled.</i>
@@ -133,6 +131,9 @@ app.templates.helpPage = -> · 67 unmodified lines
133 <dt class="_shortcuts-dt">131 <dt class="_shortcuts-dt">
134 <code class="_shortcut-code">alt + s</code>132 <code class="_shortcut-code">alt + s</code>
135 <dd class="_shortcuts-dd">Search on Stack Overflow133 <dd class="_shortcuts-dd">Search on Stack Overflow
134 <dt class="_shortcuts-dt">
135 <code class="_shortcut-code">alt + d</code>
136 <dd class="_shortcuts-dd">Search on DuckDuckGo
136 </dl>137 </dl>
137 <p class="_note _note-green">138 <p class="_note _note-green">
138 <strong>Tip:</strong> If the cursor is no longer in the search field, press <code class="_label">/</code> or139 <strong>Tip:</strong> If the cursor is no longer in the search field, press <code class="_label">/</code> or
@@ -153,5 +154,5 @@ app.templates.helpPage = -> · 14 unmodified lines
153 #{("<tr><td class=\"_code\">#{key}<td class=\"_code\">#{value}" for key, value of aliases_two).join('')}154 #{("<tr><td class=\"_code\">#{key}<td class=\"_code\">#{value}" for key, value of aliases_two).join('')}
154 </table>155 </table>
155 </div>156 </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>.
157"""158"""
assets/javascripts/templates/pages/news_tmpl.coffee.erb+1−1
@@ -4,7 +4,7 @@ app.templates.newsPage = -> · 3 unmodified lines
4 """ <h1 class="_lined-heading">Changelog</h1>4 """ <h1 class="_lined-heading">Changelog</h1>
5 <p class="_note">5 <p class="_note">
6 For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.<br>6 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>.
8 <div class="_news">#{app.templates.newsList app.news}</div> """8 <div class="_news">#{app.templates.newsList app.news}</div> """
9 9
10app.templates.newsList = (news, options = {}) ->10app.templates.newsList = (news, options = {}) ->
assets/javascripts/templates/pages/offline_tmpl.coffee+10−5
@@ -25,14 +25,14 @@ app.templates.offlinePage = (docs) -> """ · 24 unmodified lines
25 <h2 class="_block-heading">Questions & Answers</h2>25 <h2 class="_block-heading">Questions & Answers</h2>
26 <dl>26 <dl>
27 <dt>How does this work?27 <dt>How does this work?
28 <dd>Each page is cached as a key-value pair in <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> (downloaded from a single file).<br>28 <dd>Each page is cached as a key-value pair in <a href="https://devdocs.io/dom/indexeddb_api">IndexedDB</a> (downloaded from a single file).<br>
29 The app also uses <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache">AppCache</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a> to cache the assets and index files.29 The app also uses <a href="https://devdocs.io/dom/service_worker_api/using_service_workers">Service Workers</a> and <a href="https://devdocs.io/dom/web_storage_api">localStorage</a> to cache the assets and index files.
30 <dt>Can I close the tab/browser?30 <dt>Can I close the tab/browser?
31 <dd>#{canICloseTheTab()}31 <dd>#{canICloseTheTab()}
32 <dt>What if I don't update a documentation?32 <dt>What if I don't update a documentation?
33 <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.33 <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.
34 <dt>I found a bug, where do I report it?34 <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!
36 <dt>How do I uninstall/reset the app?36 <dt>How do I uninstall/reset the app?
37 <dd>Click <a href="#" data-behavior="reset">here</a>.37 <dd>Click <a href="#" data-behavior="reset">here</a>.
38 <dt>Why aren't all documentations listed above?38 <dt>Why aren't all documentations listed above?
@@ -41,10 +41,15 @@ app.templates.offlinePage = (docs) -> """ · 2 unmodified lines
41"""41"""
42 42
43canICloseTheTab = ->43canICloseTheTab = ->
44 if app.AppCache.isEnabled()44 if app.ServiceWorker.isEnabled()
45 """ Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). """45 """ Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). """
46 else46 else
47 """ No. AppCache isn't available in your browser (or is disabled), so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br>47 reason = "aren't available in your browser (or are disabled)"
48
49 if app.config.env != 'production'
50 reason = "are disabled in your development instance of DevDocs (enable them by setting the <code>ENABLE_SERVICE_WORKER</code> environment variable to <code>true</code>)"
51
52 """ No. Service Workers #{reason}, so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br>
48 The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """53 The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """
49 54
50app.templates.offlineDoc = (doc, status) ->55app.templates.offlineDoc = (doc, status) ->
assets/javascripts/templates/pages/root_tmpl.coffee.erb+11−13
@@ -8,18 +8,17 @@ app.templates.intro = """ · 7 unmodified lines
8 <p>Thanks for downloading DevDocs. Here are a few things you should know:8 <p>Thanks for downloading DevDocs. Here are a few things you should know:
9 <ol class="_intro-list">9 <ol class="_intro-list">
10 <li>Your local version of DevDocs won't self-update. Unless you're modifying the code,10 <li>Your local version of DevDocs won't self-update. Unless you're modifying the code,
11 I&nbsp;recommend using the hosted version at <a href="http://devdocs.io">devdocs.io</a>.11 we&nbsp;recommend using the hosted version at <a href="https://devdocs.io">devdocs.io</a>.
12 <li>Run <code>thor docs:list</code> to see all available documentations.12 <li>Run <code>thor docs:list</code> to see all available documentations.
13 <li>Run <code>thor docs:download &lt;name&gt;</code> to download documentations.13 <li>Run <code>thor docs:download &lt;name&gt;</code> to download documentations.
14 <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations.14 <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations.
15 <li>To be notified about new versions, don't forget to <a href="https://github.com/Thibaut/devdocs/subscription">watch the repository</a> on GitHub.15 <li>To be notified about new versions, don't forget to <a href="https://github.com/freeCodeCamp/devdocs/subscription">watch the repository</a> on GitHub.
16 <li>The <a href="https://github.com/Thibaut/devdocs/issues">issue tracker</a> is the preferred channel for bug reports and16 <li>The <a href="https://github.com/freeCodeCamp/devdocs/issues">issue tracker</a> is the preferred channel for bug reports and
17 feature requests. For everything else, use the <a href="https://groups.google.com/d/forum/devdocs">mailing list</a>.17 feature requests. For everything else, use <a href="https://gitter.im/FreeCodeCamp/DevDocs">Gitter</a>.
18 <li>Contributions are welcome. See the <a href="https://github.com/Thibaut/devdocs/blob/master/CONTRIBUTING.md">guidelines</a>.18 <li>Contributions are welcome. See the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/CONTRIBUTING.md">guidelines</a>.
19 <li>DevDocs is licensed under the terms of the Mozilla Public License v2.0. For more information,19 <li>DevDocs is licensed under the terms of the Mozilla Public License v2.0. For more information,
20 see the <a href="https://github.com/Thibaut/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a> and20 see the <a href="https://github.com/freeCodeCamp/devdocs/blob/master/COPYRIGHT">COPYRIGHT</a> and
21 <a href="https://github.com/Thibaut/devdocs/blob/master/LICENSE">LICENSE</a> files.21 <a href="https://github.com/freeCodeCamp/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!
23 </ol>22 </ol>
24 <p>Happy coding!23 <p>Happy coding!
25 </div></div>24 </div></div>
@@ -39,9 +38,8 @@ app.templates.intro = """ · 13 unmodified lines
39 <li>You can search using your browser's address bar &mdash; <a href="/help#browser_search">learn how</a>.38 <li>You can search using your browser's address bar &mdash; <a href="/help#browser_search">learn how</a>.
40 <li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed on <a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome</a>.39 <li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed on <a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome</a>.
41 <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.40 <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.
42 <li>DevDocs is free and <a href="https://github.com/Thibaut/devdocs">open source</a>.41 <li>DevDocs is free and <a href="https://github.com/freeCodeCamp/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>42 <object data="https://img.shields.io/github/stars/freeCodeCamp/devdocs.svg?style=social" type="image/svg+xml" aria-hidden="true"></object>
44 <li>If you like the app, please consider supporting the project on <a href="https://gratipay.com/devdocs/">Gratipay</a>. Thanks!
45 <li>And if you're new to coding, check out <a href="https://www.freecodecamp.org/">freeCodeCamp's open source curriculum</a>.43 <li>And if you're new to coding, check out <a href="https://www.freecodecamp.org/">freeCodeCamp's open source curriculum</a>.
46 </ol>44 </ol>
47 <p>Happy coding!45 <p>Happy coding!
@@ -59,7 +57,7 @@ app.templates.mobileIntro = """ · 11 unmodified lines
59 <li>The search supports fuzzy matching.57 <li>The search supports fuzzy matching.
60 <li>To search a specific documentation, type its name (or an abbr.), then Space.58 <li>To search a specific documentation, type its name (or an abbr.), then Space.
61 <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.59 <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>.
63 </ol>61 </ol>
64 <p>Happy coding!62 <p>Happy coding!
65 <a class="_intro-hide" data-hide-intro>Stop showing this message</a>63 <a class="_intro-hide" data-hide-intro>Stop showing this message</a>
@@ -71,6 +69,6 @@ app.templates.androidWarning = """ · 5 unmodified lines
71 <h2 class="_intro-title">Hi there</h2>69 <h2 class="_intro-title">Hi there</h2>
72 <p>DevDocs is running inside an Android WebView. Some features may not work properly.70 <p>DevDocs is running inside an Android WebView. Some features may not work properly.
73 <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.71 <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.
75 </div>73 </div>
76"""74"""
assets/javascripts/templates/pages/settings_tmpl.coffee+8−0
@@ -15,6 +15,14 @@ app.templates.settingsPage = (settings) -> """ · 14 unmodified lines
15 <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar15 <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar
16 <small>Tip: drag the edge of the sidebar to resize it.</small>16 <small>Tip: drag the edge of the sidebar to resize it.</small>
17 </label>17 </label>
18 <label class="_settings-label">
19 <input type="checkbox" form="settings" name="autoInstall" value="_auto-install"#{if settings.autoInstall then ' checked' else ''}>Automatically download documentation for offline use
20 <small>Only enable this when bandwidth isn't a concern to you.</small>
21 </label>
22 <label class="_settings-label">
23 <input type="checkbox" form="settings" name="analyticsConsent"#{if settings.analyticsConsent then ' checked' else ''}>Enable tracking cookies
24 <small>With this checked, we enable Google Analytics and Gauges to collect anonymous traffic information.</small>
25 </label>
18 </div>26 </div>
19 </div>27 </div>
20 28
assets/javascripts/tracking.js+27−23
@@ -1,28 +1,32 @@
1try {1try {
2 if (app.config.env == 'production') {2 if (app.config.env === 'production') {
3 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){3 if (Cookies.get('analyticsConsent') === '1') {
4 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),4 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
5 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)5 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
6 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');6 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
7 ga('create', 'UA-5544833-12', 'devdocs.io');7 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
8 page.track(function() {8 ga('create', 'UA-5544833-12', 'devdocs.io');
9 ga('send', 'pageview', {9 page.track(function() {
10 page: location.pathname + location.search + location.hash,10 ga('send', 'pageview', {
11 dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version11 page: location.pathname + location.search + location.hash,
12 dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version
13 });
12 });14 });
13 });
14 15
15 page.track(function() {16 page.track(function() {
16 if (window._gauges)17 if (window._gauges)
17 _gauges.push(['track']);18 _gauges.push(['track']);
18 else19 else
19 (function() {20 (function() {
20 var _gauges=_gauges||[];!function(){var a=document.createElement("script");21 var _gauges=_gauges||[];!function(){var a=document.createElement("script");
21 a.type="text/javascript",a.async=!0,a.id="gauges-tracker",22 a.type="text/javascript",a.async=!0,a.id="gauges-tracker",
22 a.setAttribute("data-site-id","51c15f82613f5d7819000067"),23 a.setAttribute("data-site-id","51c15f82613f5d7819000067"),
23 a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];24 a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];
24 b.parentNode.insertBefore(a,b)}();25 b.parentNode.insertBefore(a,b)}();
25 })();26 })();
26 });27 });
28 } else {
29 resetAnalytics();
30 }
27 }31 }
28} catch(e) { }32} catch(e) { }
assets/javascripts/vendor/fastclick.js+2−2
@@ -413,7 +413,7 @@ · 412 unmodified lines
413 // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched413 // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
414 // with the same identifier as the touch event that previously triggered the click that triggered the alert.414 // with the same identifier as the touch event that previously triggered the click that triggered the alert.
415 // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an415 // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
416 // immediately preceeding touch event (issue #52), so this fix is unavailable on that platform.416 // immediately preceding touch event (issue #52), so this fix is unavailable on that platform.
417 // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,417 // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,
418 // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,418 // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,
419 // random integers, it's safe to to continue if the identifier is 0 here.419 // random integers, it's safe to to continue if the identifier is 0 here.
@@ -805,7 +805,7 @@ · 385 unmodified lines
805 }805 }
806 }806 }
807 807
808 // IE11: prefixed -ms-touch-action is no longer supported and it's recomended to use non-prefixed version808 // IE11: prefixed -ms-touch-action is no longer supported and it's recommended to use non-prefixed version
809 // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx809 // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx
810 if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {810 if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {
811 return true;811 return true;
assets/javascripts/vendor/prism.js+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
2https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+c+bash+cpp+coffeescript+ruby+d+dart+django+elixir+markup-templating+erlang+go+java+json+kotlin+lua+crystal+nginx+nim+perl+php+sql+scss+python+jsx+typescript+rust+yaml */
2var _self = (typeof window !== 'undefined')3var _self = (typeof window !== 'undefined')
3 ? window // if in browser4 ? window // if in browser
4 : (5 : (
@@ -16,11 +17,12 @@ var _self = (typeof window !== 'undefined') · 11 unmodified lines
16var Prism = (function(){17var Prism = (function(){
17 18
18// Private helper vars19// Private helper vars
19var lang = /\blang(?:uage)?-(\w+)\b/i;20var lang = /\blang(?:uage)?-([\w-]+)\b/i;
20var uniqueId = 0;21var uniqueId = 0;
21 22
22var _ = _self.Prism = {23var _ = _self.Prism = {
23 manual: _self.Prism && _self.Prism.manual,24 manual: _self.Prism && _self.Prism.manual,
25 disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
24 util: {26 util: {
25 encode: function (tokens) {27 encode: function (tokens) {
26 if (tokens instanceof Token) {28 if (tokens instanceof Token) {
@@ -44,23 +46,38 @@ var _ = _self.Prism = { · 17 unmodified lines
44 },46 },
45 47
46 // Deep clone a language definition (e.g. to extend it)48 // Deep clone a language definition (e.g. to extend it)
47 clone: function (o) {49 clone: function (o, visited) {
48 var type = _.util.type(o);50 var type = _.util.type(o);
51 visited = visited || {};
49 52
50 switch (type) {53 switch (type) {
51 case 'Object':54 case 'Object':
55 if (visited[_.util.objId(o)]) {
56 return visited[_.util.objId(o)];
57 }
52 var clone = {};58 var clone = {};
59 visited[_.util.objId(o)] = clone;
53 60
54 for (var key in o) {61 for (var key in o) {
55 if (o.hasOwnProperty(key)) {62 if (o.hasOwnProperty(key)) {
56 clone[key] = _.util.clone(o[key]);63 clone[key] = _.util.clone(o[key], visited);
57 }64 }
58 }65 }
59 66
60 return clone;67 return clone;
61 68
62 case 'Array':69 case 'Array':
63 return o.map(function(v) { return _.util.clone(v); });70 if (visited[_.util.objId(o)]) {
71 return visited[_.util.objId(o)];
72 }
73 var clone = [];
74 visited[_.util.objId(o)] = clone;
75
76 o.forEach(function (v, i) {
77 clone[i] = _.util.clone(v, visited);
78 });
79
80 return clone;
64 }81 }
65 82
66 return o;83 return o;
@@ -155,6 +172,10 @@ var _ = _self.Prism = { · 88 unmodified lines
155 plugins: {},172 plugins: {},
156 173
157 highlightAll: function(async, callback) {174 highlightAll: function(async, callback) {
175 _.highlightAllUnder(document, async, callback);
176 },
177
178 highlightAllUnder: function(container, async, callback) {
158 var env = {179 var env = {
159 callback: callback,180 callback: callback,
160 selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'181 selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
@@ -162,7 +183,7 @@ var _ = _self.Prism = { · 1 unmodified line
162 183
163 _.hooks.run("before-highlightall", env);184 _.hooks.run("before-highlightall", env);
164 185
165 var elements = env.elements || document.querySelectorAll(env.selector);186 var elements = env.elements || container.querySelectorAll(env.selector);
166 187
167 for (var i=0, element; element = elements[i++];) {188 for (var i=0, element; element = elements[i++];) {
168 _.highlightElement(element, async === true, env.callback);189 _.highlightElement(element, async === true, env.callback);
@@ -185,11 +206,13 @@ var _ = _self.Prism = { · 16 unmodified lines
185 // Set language on the element, if not present206 // Set language on the element, if not present
186 element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;207 element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
187 208
188 // Set language on the parent, for styling209 if (element.parentNode) {
189 parent = element.parentNode;210 // Set language on the parent, for styling
211 parent = element.parentNode;
190 212
191 if (/pre/i.test(parent.nodeName)) {213 if (/pre/i.test(parent.nodeName)) {
192 parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;214 parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
215 }
193 }216 }
194 217
195 var code = element.textContent;218 var code = element.textContent;
@@ -251,8 +274,15 @@ var _ = _self.Prism = { · 55 unmodified lines
251 },274 },
252 275
253 highlight: function (text, grammar, language) {276 highlight: function (text, grammar, language) {
254 var tokens = _.tokenize(text, grammar);277 var env = {
255 return Token.stringify(_.util.encode(tokens), language);278 code: text,
279 grammar: grammar,
280 language: language
281 };
282 _.hooks.run('before-tokenize', env);
283 env.tokens = _.tokenize(env.code, env.grammar);
284 _.hooks.run('after-tokenize', env);
285 return Token.stringify(_.util.encode(env.tokens), env.language);
256 },286 },
257 287
258 matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {288 matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
@@ -300,15 +330,9 @@ var _ = _self.Prism = { · 41 unmodified lines
300 continue;330 continue;
301 }331 }
302 332
303 pattern.lastIndex = 0;333 if (greedy && i != strarr.length - 1) {
304
305 var match = pattern.exec(str),
306 delNum = 1;
307
308 // Greedy patterns can override/remove up to two previously matched tokens
309 if (!match && greedy && i != strarr.length - 1) {
310 pattern.lastIndex = pos;334 pattern.lastIndex = pos;
311 match = pattern.exec(text);335 var match = pattern.exec(text);
312 if (!match) {336 if (!match) {
313 break;337 break;
314 }338 }
@@ -327,11 +351,8 @@ var _ = _self.Prism = { · 12 unmodified lines
327 }351 }
328 }352 }
329 353
330 /*354 // If strarr[i] is a Token, then the match starts inside another Token, which is invalid
331 * If strarr[i] is a Token, then the match starts inside another Token, which is invalid355 if (strarr[i] instanceof Token) {
332 * If strarr[k - 1] is greedy we are in conflict with another greedy pattern
333 */
334 if (strarr[i] instanceof Token || strarr[k - 1].greedy) {
335 continue;356 continue;
336 }357 }
337 358
@@ -339,6 +360,11 @@ var _ = _self.Prism = { · 1 unmodified line
339 delNum = k - i;360 delNum = k - i;
340 str = text.slice(pos, p);361 str = text.slice(pos, p);
341 match.index -= pos;362 match.index -= pos;
363 } else {
364 pattern.lastIndex = 0;
365
366 var match = pattern.exec(str),
367 delNum = 1;
342 }368 }
343 369
344 if (!match) {370 if (!match) {
@@ -350,7 +376,7 @@ var _ = _self.Prism = { · 5 unmodified lines
350 }376 }
351 377
352 if(lookbehind) {378 if(lookbehind) {
353 lookbehindLength = match[1].length;379 lookbehindLength = match[1] ? match[1].length : 0;
354 }380 }
355 381
356 var from = match.index + lookbehindLength,382 var from = match.index + lookbehindLength,
@@ -460,10 +486,6 @@ Token.stringify = function(o, language, parent) { · 103 unmodified lines
460 parent: parent486 parent: parent
461 };487 };
462 488
463 if (env.type == 'comment') {
464 env.attributes['spellcheck'] = 'true';
465 }
466
467 if (o.alias) {489 if (o.alias) {
468 var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];490 var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
469 Array.prototype.push.apply(env.classes, aliases);491 Array.prototype.push.apply(env.classes, aliases);
@@ -484,18 +506,21 @@ Token.stringify = function(o, language, parent) { · 14 unmodified lines
484// // in Node.js506// // in Node.js
485// return _self.Prism;507// return _self.Prism;
486// }508// }
487// // In worker509
488// _self.addEventListener('message', function(evt) {510// if (!_.disableWorkerMessageHandler) {
489// var message = JSON.parse(evt.data),511// // In worker
490// lang = message.language,512// _self.addEventListener('message', function (evt) {
491// code = message.code,513// var message = JSON.parse(evt.data),
492// immediateClose = message.immediateClose;514// lang = message.language,
493 515// code = message.code,
494// _self.postMessage(_.highlight(code, _.languages[lang], lang));516// immediateClose = message.immediateClose;
495// if (immediateClose) {517
496// _self.close();518// _self.postMessage(_.highlight(code, _.languages[lang], lang));
497// }519// if (immediateClose) {
498// }, false);520// _self.close();
521// }
522// }, false);
523// }
499 524
500// return _self.Prism;525// return _self.Prism;
501// }526// }
@@ -539,7 +564,8 @@ Prism.languages.markup = { · 37 unmodified lines
539 'doctype': /<!DOCTYPE[\s\S]+?>/i,564 'doctype': /<!DOCTYPE[\s\S]+?>/i,
540 'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,565 'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,
541 'tag': {566 '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,
543 inside: {569 inside: {
544 'tag': {570 'tag': {
545 pattern: /^<\/?[^\s>\/]+/i,571 pattern: /^<\/?[^\s>\/]+/i,
@@ -549,9 +575,15 @@ Prism.languages.markup = { · 3 unmodified lines
549 }575 }
550 },576 },
551 'attr-value': {577 'attr-value': {
552 pattern: /=(?:('|")[\s\S]*?(\1)|[^\s>]+)/i,578 pattern: /=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i,
553 inside: {579 inside: {
554 'punctuation': /[=>"']/580 'punctuation': [
581 /^=/,
582 {
583 pattern: /(^|[^\\])["']/,
584 lookbehind: true
585 }
586 ]
555 }587 }
556 },588 },
557 'punctuation': /\/?>/,589 'punctuation': /\/?>/,
@@ -586,25 +618,25 @@ Prism.languages.svg = Prism.languages.markup; · 28 unmodified lines
586Prism.languages.css = {618Prism.languages.css = {
587 'comment': /\/\*[\s\S]*?\*\//,619 'comment': /\/\*[\s\S]*?\*\//,
588 'atrule': {620 'atrule': {
589 pattern: /@[\w-]+?.*?(;|(?=\s*\{))/i,621 pattern: /@[\w-]+?.*?(?:;|(?=\s*\{))/i,
590 inside: {622 inside: {
591 'rule': /@[\w-]+/623 'rule': /@[\w-]+/
592 // See rest below624 // See rest below
593 }625 }
594 },626 },
595 'url': /url\((?:(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,627 'url': /url\((?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,
596 'selector': /[^\{\}\s][^\{\};]*?(?=\s*\{)/,628 'selector': /[^{}\s][^{};]*?(?=\s*\{)/,
597 'string': {629 'string': {
598 pattern: /("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,630 pattern: /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
599 greedy: true631 greedy: true
600 },632 },
601 'property': /(\b|\B)[\w-]+(?=\s*:)/i,633 'property': /[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,
602 'important': /\B!important\b/i,634 'important': /\B!important\b/i,
603 'function': /[-a-z0-9]+(?=\()/i,635 'function': /[-a-z0-9]+(?=\()/i,
604 'punctuation': /[(){};:]/636 'punctuation': /[(){};:]/
605};637};
606 638
607Prism.languages.css['atrule'].inside.rest = Prism.util.clone(Prism.languages.css);639Prism.languages.css['atrule'].inside.rest = Prism.languages.css;
608 640
609if (Prism.languages.markup) {641if (Prism.languages.markup) {
610 Prism.languages.insertBefore('markup', 'tag', {642 Prism.languages.insertBefore('markup', 'tag', {
@@ -612,13 +644,14 @@ if (Prism.languages.markup) { · 1 unmodified line
612 pattern: /(<style[\s\S]*?>)[\s\S]*?(?=<\/style>)/i,644 pattern: /(<style[\s\S]*?>)[\s\S]*?(?=<\/style>)/i,
613 lookbehind: true,645 lookbehind: true,
614 inside: Prism.languages.css,646 inside: Prism.languages.css,
615 alias: 'language-css'647 alias: 'language-css',
648 greedy: true
616 }649 }
617 });650 });
618 651
619 Prism.languages.insertBefore('inside', 'attr-value', {652 Prism.languages.insertBefore('inside', 'attr-value', {
620 'style-attr': {653 'style-attr': {
621 pattern: /\s*style=("|').*?\1/i,654 pattern: /\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,
622 inside: {655 inside: {
623 'attr-name': {656 'attr-name': {
624 pattern: /^\s*style/i,657 pattern: /^\s*style/i,
@@ -642,63 +675,71 @@ Prism.languages.clike = { · 17 unmodified lines
642 },675 },
643 {676 {
644 pattern: /(^|[^\\:])\/\/.*/,677 pattern: /(^|[^\\:])\/\/.*/,
645 lookbehind: true678 lookbehind: true,
679 greedy: true
646 }680 }
647 ],681 ],
648 'string': {682 'string': {
649 pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,683 pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
650 greedy: true684 greedy: true
651 },685 },
652 'class-name': {686 '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,
654 lookbehind: true,688 lookbehind: true,
655 inside: {689 inside: {
656 punctuation: /(\.|\\)/690 punctuation: /[.\\]/
657 }691 }
658 },692 },
659 'keyword': /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,693 'keyword': /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
660 'boolean': /\b(true|false)\b/,694 'boolean': /\b(?:true|false)\b/,
661 'function': /[a-z0-9_]+(?=\()/i,695 '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,
663 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,697 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,
664 'punctuation': /[{}[\];(),.:]/698 'punctuation': /[{}[\];(),.:]/
665};699};
666 700
667Prism.languages.javascript = Prism.languages.extend('clike', {701Prism.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/,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/,
669 'number': /\b-?(0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,703 'number': /\b(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,
670 // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)704 // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
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,
672 'operator': /-[-=]?|\+[+=]?|!=?=?|<<?=?|>>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/706 'operator': /-[-=]?|\+[+=]?|!=?=?|<<?=?|>>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/
673});707});
674 708
675Prism.languages.insertBefore('javascript', 'keyword', {709Prism.languages.insertBefore('javascript', 'keyword', {
676 'regex': {710 '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,.;})\]]))/,
678 lookbehind: true,712 lookbehind: true,
679 greedy: true713 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/
681});721});
682 722
683Prism.languages.insertBefore('javascript', 'string', {723Prism.languages.insertBefore('javascript', 'string', {
684 'template-string': {724 'template-string': {
685 pattern: /`(?:\\\\|\\?[^\\])*?`/,725 pattern: /`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,
686 greedy: true,726 greedy: true,
687 inside: {727 inside: {
688 'interpolation': {728 'interpolation': {
689 pattern: /\$\{[^}]+\}/,729 pattern: /\${[^}]+}/,
690 inside: {730 inside: {
691 'interpolation-punctuation': {731 'interpolation-punctuation': {
692 pattern: /^\$\{|\}$/,732 pattern: /^\${|}$/,
693 alias: 'punctuation'733 alias: 'punctuation'
694 },734 },
695 rest: Prism.languages.javascript735 rest: null // See below
696 }736 }
697 },737 },
698 'string': /[\s\S]+/738 'string': /[\s\S]+/
699 }739 }
700 }740 }
701});741});
742Prism.languages.javascript['template-string'].inside['interpolation'].inside.rest = Prism.languages.javascript;
702 743
703if (Prism.languages.markup) {744if (Prism.languages.markup) {
704 Prism.languages.insertBefore('markup', 'tag', {745 Prism.languages.insertBefore('markup', 'tag', {
@@ -706,7 +747,8 @@ if (Prism.languages.markup) { · 1 unmodified line
706 pattern: /(<script[\s\S]*?>)[\s\S]*?(?=<\/script>)/i,747 pattern: /(<script[\s\S]*?>)[\s\S]*?(?=<\/script>)/i,
707 lookbehind: true,748 lookbehind: true,
708 inside: Prism.languages.javascript,749 inside: Prism.languages.javascript,
709 alias: 'language-javascript'750 alias: 'language-javascript',
751 greedy: true
710 }752 }
711 });753 });
712}754}
@@ -714,51 +756,145 @@ if (Prism.languages.markup) { · 1 unmodified line
714Prism.languages.js = Prism.languages.javascript;756Prism.languages.js = Prism.languages.javascript;
715 757
716Prism.languages.c = Prism.languages.extend('clike', {758Prism.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/,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/,
718 'operator': /\-[>-]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|?\||[~^%?*\/]/,760 'operator': /-[>-]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/]/,
719 'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i761 'number': /(?:\b0x[\da-f]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?)[ful]*/i
720});762});
721 763
722Prism.languages.insertBefore('c', 'string', {764Prism.languages.insertBefore('c', 'string', {
723 'macro': {765 'macro': {
724 // allow for multiline macro definitions766 // allow for multiline macro definitions
725 // spaces after the # character compile fine with gcc767 // 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,
727 lookbehind: true,769 lookbehind: true,
728 alias: 'property',770 alias: 'property',
729 inside: {771 inside: {
730 // highlight the path of the include statement as a string772 // highlight the path of the include statement as a string
731 'string': {773 'string': {
732 pattern: /(#\s*include\s*)(<.+?>|("|')(\\?.)+?\3)/,774 pattern: /(#\s*include\s*)(?:<.+?>|("|')(?:\\?.)+?\2)/,
733 lookbehind: true775 lookbehind: true
734 },776 },
735 // highlight macro directives as keywords777 // highlight macro directives as keywords
736 'directive': {778 '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/,
738 lookbehind: true,780 lookbehind: true,
739 alias: 'keyword'781 alias: 'keyword'
740 }782 }
741 }783 }
742 },784 },
743 // highlight predefined macros as constants785 // 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/
745});787});
746 788
747delete Prism.languages.c['class-name'];789delete Prism.languages.c['class-name'];
748delete Prism.languages.c['boolean'];790delete Prism.languages.c['boolean'];
749 791
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
750Prism.languages.cpp = Prism.languages.extend('c', {877Prism.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/,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/,
752 'boolean': /\b(true|false)\b/,879 '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/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/
754});881});
755 882
756Prism.languages.insertBefore('cpp', 'keyword', {883Prism.languages.insertBefore('cpp', 'keyword', {
757 'class-name': {884 'class-name': {
758 pattern: /(class\s+)[a-z0-9_]+/i,885 pattern: /(class\s+)\w+/i,
759 lookbehind: true886 lookbehind: true
760 }887 }
761});888});
889
890Prism.languages.insertBefore('cpp', 'string', {
891 'raw-string': {
892 pattern: /R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,
893 alias: 'string',
894 greedy: true
895 }
896});
897
762(function(Prism) {898(function(Prism) {
763 899
764// Ignore comments starting with { to privilege string interpolation highlighting900// Ignore comments starting with { to privilege string interpolation highlighting
@@ -774,20 +910,20 @@ Prism.languages.coffeescript = Prism.languages.extend('javascript', { · 9 unmodified lines
774 910
775 // Strings are multiline911 // Strings are multiline
776 {912 {
777 pattern: /'(?:\\?[^\\])*?'/,913 pattern: /'(?:\\[\s\S]|[^\\'])*'/,
778 greedy: true914 greedy: true
779 },915 },
780 916
781 {917 {
782 // Strings are multiline918 // Strings are multiline
783 pattern: /"(?:\\?[^\\])*?"/,919 pattern: /"(?:\\[\s\S]|[^\\"])*"/,
784 greedy: true,920 greedy: true,
785 inside: {921 inside: {
786 'interpolation': interpolation922 'interpolation': interpolation
787 }923 }
788 }924 }
789 ],925 ],
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/,
791 'class-member': {927 'class-member': {
792 pattern: /@(?!\d)\w+/,928 pattern: /@(?!\d)\w+/,
793 alias: 'variable'929 alias: 'variable'
@@ -813,7 +949,7 @@ Prism.languages.insertBefore('coffeescript', 'comment', { · 19 unmodified lines
813 949
814Prism.languages.insertBefore('coffeescript', 'string', {950Prism.languages.insertBefore('coffeescript', 'string', {
815 'inline-javascript': {951 'inline-javascript': {
816 pattern: /`(?:\\?[\s\S])*?`/,952 pattern: /`(?:\\[\s\S]|[^\\`])*`/,
817 inside: {953 inside: {
818 'delimiter': {954 'delimiter': {
819 pattern: /^`|`$/,955 pattern: /^`|`$/,
@@ -859,10 +995,13 @@ delete Prism.languages.coffeescript['template-string']; · 39 unmodified lines
859(function(Prism) {995(function(Prism) {
860 Prism.languages.ruby = Prism.languages.extend('clike', {996 Prism.languages.ruby = Prism.languages.extend('clike', {
861 'comment': [997 'comment': [
862 /#(?!\{[^\r\n]*?\}).*/,998 /#.*/,
863 /^=begin(?:\r?\n|\r)(?:.*(?:\r?\n|\r))*?=end/m999 {
1000 pattern: /^=begin(?:\r?\n|\r)(?:.*(?:\r?\n|\r))*?=end/m,
1001 greedy: true
1002 }
864 ],1003 ],
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/
866 });1005 });
867 1006
868 var interpolation = {1007 var interpolation = {
@@ -872,14 +1011,14 @@ delete Prism.languages.coffeescript['template-string']; · 3 unmodified lines
872 pattern: /^#\{|\}$/,1011 pattern: /^#\{|\}$/,
873 alias: 'tag'1012 alias: 'tag'
874 },1013 },
875 rest: Prism.util.clone(Prism.languages.ruby)1014 rest: Prism.languages.ruby
876 }1015 }
877 };1016 };
878 1017
879 Prism.languages.insertBefore('ruby', 'keyword', {1018 Prism.languages.insertBefore('ruby', 'keyword', {
880 'regex': [1019 'regex': [
881 {1020 {
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}/,
883 greedy: true,1022 greedy: true,
884 inside: {1023 inside: {
885 'interpolation': interpolation1024 'interpolation': interpolation
@@ -920,18 +1059,21 @@ delete Prism.languages.coffeescript['template-string']; · 34 unmodified lines
920 greedy: true1059 greedy: true
921 }1060 }
922 ],1061 ],
923 'variable': /[@$]+[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/,1062 'variable': /[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,
924 'symbol': /:[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/1063 'symbol': {
1064 pattern: /(^|[^:]):[a-zA-Z_]\w*(?:[?!]|\b)/,
1065 lookbehind: true
1066 }
925 });1067 });
926 1068
927 Prism.languages.insertBefore('ruby', 'number', {1069 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/,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/,
929 'constant': /\b[A-Z][a-zA-Z_0-9]*(?:[?!]|\b)/1071 'constant': /\b[A-Z]\w*(?:[?!]|\b)/
930 });1072 });
931 1073
932 Prism.languages.ruby.string = [1074 Prism.languages.ruby.string = [
933 {1075 {
934 pattern: /%[qQiIwWxs]?([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1/,1076 pattern: /%[qQiIwWxs]?([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,
935 greedy: true,1077 greedy: true,
936 inside: {1078 inside: {
937 'interpolation': interpolation1079 'interpolation': interpolation
@@ -967,7 +1109,7 @@ delete Prism.languages.coffeescript['template-string']; · 29 unmodified lines
967 }1109 }
968 },1110 },
969 {1111 {
970 pattern: /("|')(#\{[^}]+\}|\\(?:\r?\n|\r)|\\?.)*?\1/,1112 pattern: /("|')(?:#\{[^}]+\}|\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
971 greedy: true,1113 greedy: true,
972 inside: {1114 inside: {
973 'interpolation': interpolation1115 'interpolation': interpolation
@@ -978,7 +1120,7 @@ delete Prism.languages.coffeescript['template-string']; · 4 unmodified lines
978Prism.languages.d = Prism.languages.extend('clike', {1120Prism.languages.d = Prism.languages.extend('clike', {
979 'string': [1121 'string': [
980 // r"", x""1122 // r"", x""
981 /\b[rx]"(\\.|[^\\"])*"[cwd]?/,1123 /\b[rx]"(?:\\[\s\S]|[^\\"])*"[cwd]?/,
982 // q"[]", q"()", q"<>", q"{}"1124 // q"[]", q"()", q"<>", q"{}"
983 /\bq"(?:\[[\s\S]*?\]|\([\s\S]*?\)|<[\s\S]*?>|\{[\s\S]*?\})"/,1125 /\bq"(?:\[[\s\S]*?\]|\([\s\S]*?\)|<[\s\S]*?>|\{[\s\S]*?\})"/,
984 // q"IDENT1126 // q"IDENT
@@ -990,7 +1132,7 @@ Prism.languages.d = Prism.languages.extend('clike', { · 5 unmodified lines
990 // Characters1132 // Characters
991 /'(?:\\'|\\?[^']+)'/,1133 /'(?:\\'|\\?[^']+)'/,
992 1134
993 /(["`])(\\.|(?!\1)[^\\])*\1[cwd]?/1135 /(["`])(?:\\[\s\S]|(?!\1)[^\\])*\1[cwd]?/
994 ],1136 ],
995 1137
996 'number': [1138 'number': [
@@ -1023,7 +1165,7 @@ Prism.languages.d.comment = [ · 26 unmodified lines
1023Prism.languages.insertBefore('d', 'comment', {1165Prism.languages.insertBefore('d', 'comment', {
1024 'token-string': {1166 'token-string': {
1025 // Allow one level of nesting1167 // Allow one level of nesting
1026 pattern: /\bq\{(?:|\{[^}]*\}|[^}])*\}/,1168 pattern: /\bq\{(?:\{[^}]*\}|[^}])*\}/,
1027 alias: 'string'1169 alias: 'string'
1028 }1170 }
1029});1171});
@@ -1039,20 +1181,86 @@ Prism.languages.insertBefore('d', 'function', { · 9 unmodified lines
1039 alias: 'variable'1181 alias: 'variable'
1040 }1182 }
1041});1183});
1184Prism.languages.dart = Prism.languages.extend('clike', {
1185 'string': [
1186 {
1187 pattern: /r?("""|''')[\s\S]*?\1/,
1188 greedy: true
1189 },
1190 {
1191 pattern: /r?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,
1192 greedy: true
1193 }
1194 ],
1195 'keyword': [
1196 /\b(?:async|sync|yield)\*/,
1197 /\b(?:abstract|assert|async|await|break|case|catch|class|const|continue|default|deferred|do|dynamic|else|enum|export|external|extends|factory|final|finally|for|get|if|implements|import|in|library|new|null|operator|part|rethrow|return|set|static|super|switch|this|throw|try|typedef|var|void|while|with|yield)\b/
1198 ],
1199 'operator': /\bis!|\b(?:as|is)\b|\+\+|--|&&|\|\||<<=?|>>=?|~(?:\/=?)?|[+\-*\/%&^|=!<>]=?|\?/
1200});
1201
1202Prism.languages.insertBefore('dart','function',{
1203 'metadata': {
1204 pattern: /@\w+/,
1205 alias: 'symbol'
1206 }
1207});
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
1211var _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
1230Prism.languages.django = Prism.languages.extend('markup', {'comment': /(?:<!--|{#)[\s\S]*?(?:#}|-->)/});
1231// Updated html tag pattern to allow template tags inside html tags
1232Prism.languages.django.tag.pattern = /<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^>=]+))?)*\s*\/?>/i;
1233Prism.languages.insertBefore('django', 'entity', _django_template);
1234Prism.languages.insertBefore('inside', 'tag', _django_template, Prism.languages.django.tag);
1235
1236if (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}
1241if (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
1248Prism.languages.jinja2 = Prism.languages.django;
1249
1042Prism.languages.elixir = {1250Prism.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
1046 'comment': {1251 'comment': {
1047 pattern: /(^|[^#])#(?![{#]).*/m,1252 pattern: /#.*/m,
1048 lookbehind: true1253 lookbehind: true
1049 },1254 },
1050 // ~r"""foo""", ~r'''foo''', ~r/foo/, ~r|foo|, ~r"foo", ~r'foo', ~r(foo), ~r[foo], ~r{foo}, ~r<foo>1255 // ~r"""foo""" (multi-line), ~r'''foo''' (multi-line), ~r/foo/, ~r|foo|, ~r"foo", ~r'foo', ~r(foo), ~r[foo], ~r{foo}, ~r<foo>
1051 'regex': /~[rR](?:("""|'''|[\/|"'])(?:\\.|(?!\1)[^\\])+\1|\((?:\\\)|[^)])+\)|\[(?:\\\]|[^\]])+\]|\{(?:\\\}|[^}])+\}|<(?:\\>|[^>])+>)[uismxfr]*/,1256 'regex': {
1257 pattern: /~[rR](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|[^\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[uismxfr]*/,
1258 greedy: true
1259 },
1052 'string': [1260 'string': [
1053 {1261 {
1054 // ~s"""foo""", ~s'''foo''', ~s/foo/, ~s|foo|, ~s"foo", ~s'foo', ~s(foo), ~s[foo], ~s{foo}, ~s<foo>1262 // ~s"""foo""" (multi-line), ~s'''foo''' (multi-line), ~s/foo/, ~s|foo|, ~s"foo", ~s'foo', ~s(foo), ~s[foo], ~s{foo} (with interpolation care), ~s<foo>
1055 pattern: /~[cCsSwW](?:("""|'''|[\/|"'])(?:\\.|(?!\1)[^\\])+\1|\((?:\\\)|[^)])+\)|\[(?:\\\]|[^\]])+\]|\{(?:\\\}|#\{[^}]+\}|[^}])+\}|<(?:\\>|[^>])+>)[csa]?/,1263 pattern: /~[cCsSwW](?:("""|''')(?:\\[\s\S]|(?!\1)[^\\])+\1|([\/|"'])(?:\\.|(?!\2)[^\\\r\n])+\2|\((?:\\.|[^\\)\r\n])+\)|\[(?:\\.|[^\\\]\r\n])+\]|\{(?:\\.|#\{[^}]+\}|[^\\}\r\n])+\}|<(?:\\.|[^\\>\r\n])+>)[csa]?/,
1056 greedy: true,1264 greedy: true,
1057 inside: {1265 inside: {
1058 // See interpolation below1266 // See interpolation below
@@ -1067,7 +1275,7 @@ Prism.languages.elixir = { · 8 unmodified lines
1067 },1275 },
1068 {1276 {
1069 // Multi-line strings are allowed1277 // Multi-line strings are allowed
1070 pattern: /("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/,1278 pattern: /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
1071 greedy: true,1279 greedy: true,
1072 inside: {1280 inside: {
1073 // See interpolation below1281 // See interpolation below
@@ -1095,7 +1303,7 @@ Prism.languages.elixir = { · 21 unmodified lines
1095 alias: 'variable'1303 alias: 'variable'
1096 },1304 },
1097 'attribute': {1305 'attribute': {
1098 pattern: /@[\S]+/,1306 pattern: /@\w+/,
1099 alias: 'variable'1307 alias: 'variable'
1100 },1308 },
1101 'number': /\b(?:0[box][a-f\d_]+|\d[\d_]*)(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?\b/i,1309 'number': /\b(?:0[box][a-f\d_]+|\d[\d_]*)(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?\b/i,
@@ -1126,25 +1334,114 @@ Prism.languages.elixir.string.forEach(function(o) { · 24 unmodified lines
1126 pattern: /^#\{|\}$/,1334 pattern: /^#\{|\}$/,
1127 alias: 'punctuation'1335 alias: 'punctuation'
1128 },1336 },
1129 rest: Prism.util.clone(Prism.languages.elixir)1337 rest: Prism.languages.elixir
1130 }1338 }
1131 }1339 }
1132 };1340 };
1133});1341});
1134 1342
1135 1343
1344Prism.languages['markup-templating'] = {};
1345
1346Object.defineProperties(Prism.languages['markup-templating'], {
1347 buildPlaceholders: {
1348 // Tokenize all inline templating expressions matching placeholderPattern
1349 // If the replaceFilter function is provided, it will be called with every match.
1350 // If it returns false, the match will not be replaced.
1351 value: function (env, language, placeholderPattern, replaceFilter) {
1352 if (env.language !== language) {
1353 return;
1354 }
1355
1356 env.tokenStack = [];
1357
1358 env.code = env.code.replace(placeholderPattern, function(match) {
1359 if (typeof replaceFilter === 'function' && !replaceFilter(match)) {
1360 return match;
1361 }
1362 var i = env.tokenStack.length;
1363 // Check for existing strings
1364 while (env.code.indexOf('___' + language.toUpperCase() + i + '___') !== -1)
1365 ++i;
1366
1367 // Create a sparse array
1368 env.tokenStack[i] = match;
1369
1370 return '___' + language.toUpperCase() + i + '___';
1371 });
1372
1373 // Switch the grammar to markup
1374 env.grammar = Prism.languages.markup;
1375 }
1376 },
1377 tokenizePlaceholders: {
1378 // Replace placeholders with proper tokens after tokenizing
1379 value: function (env, language) {
1380 if (env.language !== language || !env.tokenStack) {
1381 return;
1382 }
1383
1384 // Switch the grammar back
1385 env.grammar = Prism.languages[language];
1386
1387 var j = 0;
1388 var keys = Object.keys(env.tokenStack);
1389 var walkTokens = function (tokens) {
1390 if (j >= keys.length) {
1391 return;
1392 }
1393 for (var i = 0; i < tokens.length; i++) {
1394 var token = tokens[i];
1395 if (typeof token === 'string' || (token.content && typeof token.content === 'string')) {
1396 var k = keys[j];
1397 var t = env.tokenStack[k];
1398 var s = typeof token === 'string' ? token : token.content;
1399
1400 var index = s.indexOf('___' + language.toUpperCase() + k + '___');
1401 if (index > -1) {
1402 ++j;
1403 var before = s.substring(0, index);
1404 var middle = new Prism.Token(language, Prism.tokenize(t, env.grammar, language), 'language-' + language, t);
1405 var after = s.substring(index + ('___' + language.toUpperCase() + k + '___').length);
1406 var replacement;
1407 if (before || after) {
1408 replacement = [before, middle, after].filter(function (v) { return !!v; });
1409 walkTokens(replacement);
1410 } else {
1411 replacement = middle;
1412 }
1413 if (typeof token === 'string') {
1414 Array.prototype.splice.apply(tokens, [i, 1].concat(replacement));
1415 } else {
1416 token.content = replacement;
1417 }
1418
1419 if (j >= keys.length) {
1420 break;
1421 }
1422 }
1423 } else if (token.content && typeof token.content !== 'string') {
1424 walkTokens(token.content);
1425 }
1426 }
1427 };
1428
1429 walkTokens(env.tokens);
1430 }
1431 }
1432});
1136Prism.languages.erlang = {1433Prism.languages.erlang = {
1137 'comment': /%.+/,1434 'comment': /%.+/,
1138 'string': {1435 'string': {
1139 pattern: /"(?:\\?.)*?"/,1436 pattern: /"(?:\\.|[^\\"\r\n])*"/,
1140 greedy: true1437 greedy: true
1141 },1438 },
1142 'quoted-function': {1439 'quoted-function': {
1143 pattern: /'(?:\\.|[^'\\])+'(?=\()/,1440 pattern: /'(?:\\.|[^\\'\r\n])+'(?=\()/,
1144 alias: 'function'1441 alias: 'function'
1145 },1442 },
1146 'quoted-atom': {1443 'quoted-atom': {
1147 pattern: /'(?:\\.|[^'\\])+'/,1444 pattern: /'(?:\\.|[^\\'\r\n])+'/,
1148 alias: 'atom'1445 alias: 'atom'
1149 },1446 },
1150 'boolean': /\b(?:true|false)\b/,1447 'boolean': /\b(?:true|false)\b/,
@@ -1152,7 +1449,7 @@ Prism.languages.erlang = { · 1 unmodified line
1152 'number': [1449 'number': [
1153 /\$\\?./,1450 /\$\\?./,
1154 /\d+#[a-z0-9]+/i,1451 /\d+#[a-z0-9]+/i,
1155 /(?:\b|-)\d*\.?\d+([Ee][+-]?\d+)?\b/1452 /(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i
1156 ],1453 ],
1157 'function': /\b[a-z][\w@]*(?=\()/,1454 'function': /\b[a-z][\w@]*(?=\()/,
1158 'variable': {1455 'variable': {
@@ -1178,21 +1475,21 @@ Prism.languages.erlang = { · 19 unmodified lines
1178 1475
1179};1476};
1180Prism.languages.go = Prism.languages.extend('clike', {1477Prism.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/,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/,
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/,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/,
1183 'boolean': /\b(_|iota|nil|true|false)\b/,1480 'boolean': /\b(?:_|iota|nil|true|false)\b/,
1184 'operator': /[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,1481 '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,
1186 'string': {1483 'string': {
1187 pattern: /("|'|`)(\\?.|\r|\n)*?\1/,1484 pattern: /(["'`])(\\[\s\S]|(?!\1)[^\\])*\1/,
1188 greedy: true1485 greedy: true
1189 }1486 }
1190});1487});
1191delete Prism.languages.go['class-name'];1488delete Prism.languages.go['class-name'];
1192 1489
1193Prism.languages.java = Prism.languages.extend('clike', {1490Prism.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/,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/,
1195 'number': /\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,1492 'number': /\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp-]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?[df]?/i,
1196 'operator': {1493 'operator': {
1197 pattern: /(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<<?=?|>>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,1494 pattern: /(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<<?=?|>>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,
1198 lookbehind: true1495 lookbehind: true
@@ -1207,14 +1504,28 @@ Prism.languages.insertBefore('java','function', { · 8 unmodified lines
1207 }1504 }
1208});1505});
1209 1506
1507Prism.languages.insertBefore('java', 'class-name', {
1508 'generics': {
1509 pattern: /<\s*\w+(?:\.\w+)?(?:\s*,\s*\w+(?:\.\w+)?)*>/i,
1510 alias: 'function',
1511 inside: {
1512 keyword: Prism.languages.java.keyword,
1513 punctuation: /[<>(),.:]/
1514 }
1515 }
1516});
1517
1210Prism.languages.json = {1518Prism.languages.json = {
1211 'property': /"(?:\\.|[^\\"])*"(?=\s*:)/ig,1519 'property': /"(?:\\.|[^\\"\r\n])*"(?=\s*:)/i,
1212 'string': /"(?!:)(?:\\.|[^\\"])*"(?!:)/g,1520 'string': {
1213 'number': /\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?)\b/g,1521 pattern: /"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,
1214 'punctuation': /[{}[\]);,]/g,1522 greedy: true
1523 },
1524 'number': /-?\d+\.?\d*([Ee][+-]?\d+)?/,
1525 'punctuation': /[{}[\],]/,
1215 'operator': /:/g,1526 'operator': /:/g,
1216 'boolean': /\b(true|false)\b/gi,1527 'boolean': /\b(?:true|false)\b/i,
1217 'null': /\bnull\b/gi1528 'null': /\bnull\b/i
1218};1529};
1219 1530
1220Prism.languages.jsonp = Prism.languages.json;1531Prism.languages.jsonp = Prism.languages.json;
@@ -1223,7 +1534,7 @@ Prism.languages.jsonp = Prism.languages.json; · 2 unmodified lines
1223 Prism.languages.kotlin = Prism.languages.extend('clike', {1534 Prism.languages.kotlin = Prism.languages.extend('clike', {
1224 'keyword': {1535 'keyword': {
1225 // The lookbehind prevents wrong highlighting of e.g. kotlin.properties.get1536 // 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/,
1227 lookbehind: true1538 lookbehind: true
1228 },1539 },
1229 'function': [1540 'function': [
@@ -1233,7 +1544,7 @@ Prism.languages.jsonp = Prism.languages.json; · 3 unmodified lines
1233 lookbehind: true1544 lookbehind: true
1234 }1545 }
1235 ],1546 ],
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/,
1237 'operator': /\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/1548 'operator': /\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/
1238 });1549 });
1239 1550
@@ -1241,7 +1552,7 @@ Prism.languages.jsonp = Prism.languages.json; · 1 unmodified line
1241 1552
1242 Prism.languages.insertBefore('kotlin', 'string', {1553 Prism.languages.insertBefore('kotlin', 'string', {
1243 'raw-string': {1554 'raw-string': {
1244 pattern: /(["'])\1\1[\s\S]*?\1{3}/,1555 pattern: /("""|''')[\s\S]*?\1/,
1245 alias: 'string'1556 alias: 'string'
1246 // See interpolation below1557 // See interpolation below
1247 }1558 }
@@ -1267,7 +1578,7 @@ Prism.languages.jsonp = Prism.languages.json; · 19 unmodified lines
1267 pattern: /^\$\{|\}$/,1578 pattern: /^\$\{|\}$/,
1268 alias: 'variable'1579 alias: 'variable'
1269 },1580 },
1270 rest: Prism.util.clone(Prism.languages.kotlin)1581 rest: Prism.languages.kotlin
1271 }1582 }
1272 },1583 },
1273 {1584 {
@@ -1281,6 +1592,7 @@ Prism.languages.jsonp = Prism.languages.json; · 7 unmodified lines
1281 };1592 };
1282 1593
1283}(Prism));1594}(Prism));
1595
1284Prism.languages.lua = {1596Prism.languages.lua = {
1285 'comment': /^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,1597 'comment': /^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,
1286 // \z may be used to skip the following space1598 // \z may be used to skip the following space
@@ -1301,16 +1613,68 @@ Prism.languages.lua = { · 14 unmodified lines
1301 ],1613 ],
1302 'punctuation': /[\[\](){},;]|\.+|:+/1614 'punctuation': /[\[\](){},;]|\.+|:+/
1303};1615};
1616(function(Prism) {
1617 Prism.languages.crystal = Prism.languages.extend('ruby', {
1618 keyword: [
1619 /\b(?:abstract|alias|as|asm|begin|break|case|class|def|do|else|elsif|end|ensure|enum|extend|for|fun|if|include|instance_sizeof|lib|macro|module|next|of|out|pointerof|private|protected|rescue|return|require|select|self|sizeof|struct|super|then|type|typeof|uninitialized|union|unless|until|when|while|with|yield|__DIR__|__END_LINE__|__FILE__|__LINE__)\b/,
1620 {
1621 pattern: /(\.\s*)(?:is_a|responds_to)\?/,
1622 lookbehind: true
1623 }
1624 ],
1625
1626 number: /\b(?:0b[01_]*[01]|0o[0-7_]*[0-7]|0x[\da-fA-F_]*[\da-fA-F]|(?:\d(?:[\d_]*\d)?)(?:\.[\d_]*\d)?(?:[eE][+-]?[\d_]*\d)?)(?:_(?:[uif](?:8|16|32|64))?)?\b/
1627 });
1628
1629 Prism.languages.insertBefore('crystal', 'string', {
1630 attribute: {
1631 pattern: /@\[.+?\]/,
1632 alias: 'attr-name',
1633 inside: {
1634 delimiter: {
1635 pattern: /^@\[|\]$/,
1636 alias: 'tag'
1637 },
1638 rest: Prism.languages.crystal
1639 }
1640 },
1641
1642 expansion: [
1643 {
1644 pattern: /\{\{.+?\}\}/,
1645 inside: {
1646 delimiter: {
1647 pattern: /^\{\{|\}\}$/,
1648 alias: 'tag'
1649 },
1650 rest: Prism.languages.crystal
1651 }
1652 },
1653 {
1654 pattern: /\{%.+?%\}/,
1655 inside: {
1656 delimiter: {
1657 pattern: /^\{%|%\}$/,
1658 alias: 'tag'
1659 },
1660 rest: Prism.languages.crystal
1661 }
1662 }
1663 ]
1664 });
1665
1666}(Prism));
1667
1304Prism.languages.nginx = Prism.languages.extend('clike', {1668Prism.languages.nginx = Prism.languages.extend('clike', {
1305 'comment': {1669 'comment': {
1306 pattern: /(^|[^"{\\])#.*/,1670 pattern: /(^|[^"{\\])#.*/,
1307 lookbehind: true1671 lookbehind: true
1308 },1672 },
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,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
1310});1674});
1311 1675
1312Prism.languages.insertBefore('nginx', 'keyword', {1676Prism.languages.insertBefore('nginx', 'keyword', {
1313 'variable': /\$[a-z_]+/i1677 'variable': /\$[a-z_]+/i
1314});1678});
1315Prism.languages.nim = {1679Prism.languages.nim = {
1316 'comment': /#.*/,1680 'comment': /#.*/,
@@ -1361,13 +1725,13 @@ Prism.languages.perl = { · 44 unmodified lines
1361 'string': [1725 'string': [
1362 // q/.../1726 // q/.../
1363 {1727 {
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/,
1365 greedy: true1729 greedy: true
1366 },1730 },
1367 1731
1368 // q a...a1732 // q a...a
1369 {1733 {
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/,
1371 greedy: true1735 greedy: true
1372 },1736 },
1373 1737
@@ -1397,7 +1761,7 @@ Prism.languages.perl = { · 23 unmodified lines
1397 1761
1398 // "...", `...`1762 // "...", `...`
1399 {1763 {
1400 pattern: /("|`)(?:[^\\]|\\[\s\S])*?\1/,1764 pattern: /("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,
1401 greedy: true1765 greedy: true
1402 },1766 },
1403 1767
@@ -1411,13 +1775,13 @@ Prism.languages.perl = { · 7 unmodified lines
1411 'regex': [1775 'regex': [
1412 // m/.../1776 // m/.../
1413 {1777 {
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]*/,
1415 greedy: true1779 greedy: true
1416 },1780 },
1417 1781
1418 // m a...a1782 // m a...a
1419 {1783 {
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]*/,
1421 greedy: true1785 greedy: true
1422 },1786 },
1423 1787
@@ -1449,14 +1813,14 @@ Prism.languages.perl = { · 25 unmodified lines
1449 // FIXME We don't handle change of separator like s(...)[...]1813 // FIXME We don't handle change of separator like s(...)[...]
1450 // s/.../.../1814 // s/.../.../
1451 {1815 {
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]*/,
1453 lookbehind: true,1817 lookbehind: true,
1454 greedy: true1818 greedy: true
1455 },1819 },
1456 1820
1457 // s a...a...a1821 // s a...a...a
1458 {1822 {
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]*/,
1460 lookbehind: true,1824 lookbehind: true,
1461 greedy: true1825 greedy: true
1462 },1826 },
@@ -1508,7 +1872,7 @@ Prism.languages.perl = { · 45 unmodified lines
1508 // ${...}1872 // ${...}
1509 /[&*$@%]#?(?=\{)/,1873 /[&*$@%]#?(?=\{)/,
1510 // $foo1874 // $foo
1511 /[&*$@%]#?((::)*'?(?!\d)[\w$]+)+(::)*/i,1875 /[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+)+(?:::)*/i,
1512 // $11876 // $1
1513 /[&*$@%]\d+/,1877 /[&*$@%]\d+/,
1514 // $_, @_, %!1878 // $_, @_, %!
@@ -1522,7 +1886,7 @@ Prism.languages.perl = { · 7 unmodified lines
1522 },1886 },
1523 'vstring': {1887 'vstring': {
1524 // v1.2, 1.2.31888 // v1.2, 1.2.3
1525 pattern: /v\d+(\.\d+)*|\d+(\.\d+){2,}/,1889 pattern: /v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,
1526 alias: 'string'1890 alias: 'string'
1527 },1891 },
1528 'function': {1892 'function': {
@@ -1531,9 +1895,9 @@ Prism.languages.perl = { · 2 unmodified lines
1531 keyword: /sub/1895 keyword: /sub/
1532 }1896 }
1533 },1897 },
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/,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/,
1535 'number': /\b-?(0x[\dA-Fa-f](_?[\dA-Fa-f])*|0b[01](_?[01])*|(\d(_?\d)*)?\.?\d(_?\d)*([Ee][+-]?\d+)?)\b/,1899 'number': /\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,
1536 'operator': /-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,1900 'operator': /-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,
1537 'punctuation': /[{}[\];(),:]/1901 'punctuation': /[{}[\];(),:]/
1538};1902};
1539 1903
@@ -1549,136 +1913,239 @@ Prism.languages.perl = { · 9 unmodified lines
1549 * Adds the following new token classes:1913 * Adds the following new token classes:
1550 * constant, delimiter, variable, function, package1914 * constant, delimiter, variable, function, package
1551 */1915 */
1552 1916(function (Prism) {
1553Prism.languages.php = Prism.languages.extend('clike', {1917 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,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,
1555 'constant': /\b[A-Z0-9_]{2,}\b/,1919 'constant': /\b[A-Z0-9_]{2,}\b/,
1556 'comment': {1920 'comment': {
1557 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,1921 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,
1558 lookbehind: true1922 lookbehind: true
1559 }
1560});
1561
1562// Shell-like comments are matched after strings, because they are less
1563// common than strings containing hashes...
1564Prism.languages.insertBefore('php', 'class-name', {
1565 'shell-comment': {
1566 pattern: /(^|[^\\])#.*/,
1567 lookbehind: true,
1568 alias: 'comment'
1569 }
1570});
1571
1572Prism.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
1588Prism.languages.insertBefore('php', 'operator', {
1589 'property': {
1590 pattern: /(->)[\w]+/,
1591 lookbehind: true
1592 }
1593});
1594
1595// Add HTML support if the markup language exists
1596if (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;
1603 }1923 }
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;
1622 });1924 });
1623 1925
1624 // Restore env.code for other plugins (e.g. line-numbers)1926 Prism.languages.insertBefore('php', 'string', {
1625 Prism.hooks.add('before-insert', function(env) {1927 'shell-comment': {
1626 if (env.language === 'php' && env.backupCode) {1928 pattern: /(^|[^\\])#.*/,
1627 env.code = env.backupCode;1929 lookbehind: true,
1628 delete env.backupCode;1930 alias: 'comment'
1629 }1931 }
1630 });1932 });
1631 1933
1632 // Re-insert the tokens after highlighting1934 Prism.languages.insertBefore('php', 'keyword', {
1633 Prism.hooks.add('after-highlight', function(env) {1935 'delimiter': {
1634 if (env.language !== 'php' || !env.tokenStack) {1936 pattern: /\?>|<\?(?:php|=)?/i,
1635 return;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 }
1636 }1946 }
1947 });
1637 1948
1638 // Switch the grammar back1949 // Must be defined after the function pattern
1639 env.grammar = Prism.languages.php;1950 Prism.languages.insertBefore('php', 'operator', {
1640 1951 'property': {
1641 for (var i = 0, keys = Object.keys(env.tokenStack); i < keys.length; ++i) {1952 pattern: /(->)[\w]+/,
1642 var k = keys[i];1953 lookbehind: true
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>");
1650 }1954 }
1651
1652 env.element.innerHTML = env.highlightedCode;
1653 });1955 });
1654}1956
1655;1957 Prism.languages.insertBefore('php', 'string', {
1656Prism.languages.python= {1958 'nowdoc-string': {
1657 'triple-quoted-string': {1959 pattern: /<<<'([^']+)'(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;/,
1658 pattern: /"""[\s\S]+?"""|'''[\s\S]+?'''/,1960 greedy: true,
1659 alias: 'string'1961 alias: 'string',
1660 },1962 inside: {
1661 'comment': {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));
2028Prism.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};
2046Prism.languages.scss = Prism.languages.extend('css', {
2047 'comment': {
2048 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,
2049 lookbehind: true
2050 },
2051 'atrule': {
2052 pattern: /@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,
2053 inside: {
2054 'rule': /@[\w-]+/
2055 // See rest below
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
2081Prism.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
2091Prism.languages.scss.property = {
2092 pattern: /(?:[\w-]|\$[-\w]+|#\{\$[-\w]+\})+(?=\s*:)/i,
2093 inside: {
2094 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2095 }
2096};
2097
2098Prism.languages.insertBefore('scss', 'important', {
2099 // var and interpolated vars
2100 'variable': /\$[-\w]+|#\{\$[-\w]+\}/
2101});
2102
2103Prism.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
2120Prism.languages.scss['atrule'].inside.rest = Prism.languages.scss;
2121Prism.languages.python = {
2122 'comment': {
1662 pattern: /(^|[^\\])#.*/,2123 pattern: /(^|[^\\])#.*/,
1663 lookbehind: true2124 lookbehind: true
1664 },2125 },
2126 'triple-quoted-string': {
2127 pattern: /("""|''')[\s\S]+?\1/,
2128 greedy: true,
2129 alias: 'string'
2130 },
1665 'string': {2131 'string': {
1666 pattern: /("|')(?:\\\\|\\?[^\\\r\n])*?\1/,2132 pattern: /("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,
1667 greedy: true2133 greedy: true
1668 },2134 },
1669 'function' : {2135 'function': {
1670 pattern: /((?:^|\s)def[ \t]+)[a-zA-Z_][a-zA-Z0-9_]*(?=\()/g,2136 pattern: /((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,
1671 lookbehind: true2137 lookbehind: true
1672 },2138 },
1673 'class-name': {2139 'class-name': {
1674 pattern: /(\bclass\s+)[a-z0-9_]+/i,2140 pattern: /(\bclass\s+)\w+/i,
1675 lookbehind: true2141 lookbehind: true
1676 },2142 },
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/,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/,
1678 'boolean' : /\b(?:True|False)\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/,
1679 'number' : /\b-?(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,2145 'boolean': /\b(?:True|False|None)\b/,
1680 'operator' : /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,2146 'number': /(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,
1681 'punctuation' : /[{}[\];(),.:]/2147 'operator': /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,
2148 'punctuation': /[{}[\];(),.:]/
1682};2149};
1683 2150
1684(function(Prism) {2151(function(Prism) {
@@ -1686,93 +2153,134 @@ Prism.languages.python= { · 1 unmodified line
1686var javascript = Prism.util.clone(Prism.languages.javascript);2153var javascript = Prism.util.clone(Prism.languages.javascript);
1687 2154
1688Prism.languages.jsx = Prism.languages.extend('markup', javascript);2155Prism.languages.jsx = Prism.languages.extend('markup', javascript);
1689Prism.languages.jsx.tag.pattern= /<\/?[\w\.:-]+\s*(?:\s+(?:[\w\.:-]+(?:=(?:("|')(\\?[\s\S])*?\1|[^\s'">=]+|(\{[\s\S]*?\})))?|\{\.{3}\w+\}))*\s*\/?>/i;2156Prism.languages.jsx.tag.pattern= /<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s{'">=]+|\{(?:\{(?:\{[^}]*\}|[^{}])*\}|[^{}])+\}))?|\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}))*\s*\/?)?>/i;
1690 2157
1691Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:('|")[\s\S]*?(\1)|[^\s>]+)/i;2158Prism.languages.jsx.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/i;
2159Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i;
1692 2160
1693Prism.languages.insertBefore('inside', 'attr-name', {2161Prism.languages.insertBefore('inside', 'attr-name', {
1694 'spread': {2162 'spread': {
1695 pattern: /\{\.{3}\w+\}/,2163 pattern: /\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}/,
1696 inside: {2164 inside: {
1697 'punctuation': /\{|\}|\./,2165 'punctuation': /\.{3}|[{}.]/,
1698 'attr-value': /\w+/2166 'attr-value': /\w+/
1699 }2167 }
1700 }2168 }
1701}, Prism.languages.jsx.tag);2169}, Prism.languages.jsx.tag);
1702 2170
1703var jsxExpression = Prism.util.clone(Prism.languages.jsx);
1704
1705delete jsxExpression.punctuation
1706
1707jsxExpression = Prism.languages.insertBefore('jsx', 'operator', {
1708 'punctuation': /=(?={)|[{}[\];(),.:]/
1709}, { jsx: jsxExpression });
1710
1711Prism.languages.insertBefore('inside', 'attr-value',{2171Prism.languages.insertBefore('inside', 'attr-value',{
1712 'script': {2172 'script': {
1713 // Allow for one level of nesting2173 // Allow for two levels of nesting
1714 pattern: /=(\{(?:\{[^}]*\}|[^}])+\})/i,2174 pattern: /=(\{(?:\{(?:\{[^}]*\}|[^}])*\}|[^}])+\})/i,
1715 inside: jsxExpression,2175 inside: {
2176 'script-punctuation': {
2177 pattern: /^=(?={)/,
2178 alias: 'punctuation'
2179 },
2180 rest: Prism.languages.jsx
2181 },
1716 'alias': 'language-javascript'2182 'alias': 'language-javascript'
1717 }2183 }
1718}, Prism.languages.jsx.tag);2184}, Prism.languages.jsx.tag);
1719 2185
1720}(Prism));2186// The following will handle plain text inside tags
2187var stringifyToken = function (token) {
2188 if (!token) {
2189 return '';
2190 }
2191 if (typeof token === 'string') {
2192 return token;
2193 }
2194 if (typeof token.content === 'string') {
2195 return token.content;
2196 }
2197 return token.content.map(stringifyToken).join('');
2198};
1721 2199
1722(function(Prism) {2200var walkTokens = function (tokens) {
1723 Prism.languages.crystal = Prism.languages.extend('ruby', {2201 var openedTags = [];
1724 keyword: [2202 for (var i = 0; i < tokens.length; i++) {
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/,2203 var token = tokens[i];
1726 {2204 var notTagNorBrace = false;
1727 pattern: /(\.\s*)(?:is_a|responds_to)\?/,2205
1728 lookbehind: true2206 if (typeof token !== 'string') {
1729 }2207 if (token.type === 'tag' && token.content[0] && token.content[0].type === 'tag') {
1730 ],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 === '{') {
1731 2228
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/,2229 // Here we might have entered a JSX context inside a tag
1733 });2230 openedTags[openedTags.length - 1].openedBraces++;
1734 2231
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 === '}') {
1736 2233
1737 Prism.languages.insertBefore('crystal', 'string', {2234 // Here we might have left a JSX context inside a tag
1738 attribute: {2235 openedTags[openedTags.length - 1].openedBraces--;
1739 pattern: /@\[.+?\]/,
1740 alias: 'attr-name',
1741 inside: {
1742 delimiter: {
1743 pattern: /^@\[|\]$/,
1744 alias: 'tag'
1745 },
1746 rest: rest
1747 }
1748 },
1749 2236
1750 expansion: [2237 } else {
1751 {2238 notTagNorBrace = true
1752 pattern: /\{\{.+?\}\}/,
1753 inside: {
1754 delimiter: {
1755 pattern: /^\{\{|\}\}$/,
1756 alias: 'tag'
1757 },
1758 rest: rest
1759 }2239 }
1760 },2240 }
1761 {2241 if (notTagNorBrace || typeof token === 'string') {
1762 pattern: /\{%.+?%\}/,2242 if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces === 0) {
1763 inside: {2243 // Here we are inside a tag, and not inside a JSX context.
1764 delimiter: {2244 // That's plain text: drop any tokens matched.
1765 pattern: /^\{%|%\}$/,2245 var plainText = stringifyToken(token);
1766 alias: 'tag'2246
1767 },2247 // And merge text with adjacent text
1768 rest: rest2248 if (i < tokens.length - 1 && (typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')) {
2249 plainText += stringifyToken(tokens[i + 1]);
2250 tokens.splice(i + 1, 1);
2251 }
2252 if (i > 0 && (typeof tokens[i - 1] === 'string' || tokens[i - 1].type === 'plain-text')) {
2253 plainText = stringifyToken(tokens[i - 1]) + plainText;
2254 tokens.splice(i - 1, 1);
2255 i--;
2256 }
2257
2258 tokens[i] = new Prism.Token('plain-text', plainText, null, plainText);
1769 }2259 }
1770 }2260 }
1771 ]2261
1772 });2262 if (token.content && typeof token.content !== 'string') {
2263 walkTokens(token.content);
2264 }
2265 }
2266};
2267
2268Prism.hooks.add('after-tokenize', function (env) {
2269 if (env.language !== 'jsx' && env.language !== 'tsx') {
2270 return;
2271 }
2272 walkTokens(env.tokens);
2273});
1773 2274
1774}(Prism));2275}(Prism));
1775 2276
2277Prism.languages.typescript = Prism.languages.extend('javascript', {
2278 // From JavaScript Prism keyword list and TypeScript language spec: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#221-reserved-words
2279 'keyword': /\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield|module|declare|constructor|namespace|abstract|require|type)\b/,
2280 'builtin': /\b(?:string|Function|any|number|boolean|Array|symbol|console)\b/,
2281});
2282
2283Prism.languages.ts = Prism.languages.typescript;
1776/* TODO2284/* TODO
1777 Add support for Markdown notation inside doc comments2285 Add support for Markdown notation inside doc comments
1778 Add support for nested block comments...2286 Add support for nested block comments...
@@ -1793,14 +2301,22 @@ Prism.languages.rust = { · 14 unmodified lines
1793 ],2301 ],
1794 'string': [2302 'string': [
1795 {2303 {
1796 pattern: /b?r(#*)"(?:\\?.)*?"\1/,2304 pattern: /b?r(#*)"(?:\\.|(?!"\1)[^\\\r\n])*"\1/,
1797 greedy: true2305 greedy: true
1798 },2306 },
1799 {2307 {
1800 pattern: /b?("|')(?:\\?.)*?\1/,2308 pattern: /b?"(?:\\.|[^\\\r\n"])*"/,
1801 greedy: true2309 greedy: true
1802 }2310 }
1803 ],2311 ],
2312 'char': {
2313 pattern: /b?'(?:\\(?:x[0-7][\da-fA-F]|u{(?:[\da-fA-F]_*){1,6}|.)|[^\\\r\n\t'])'/,
2314 alias: 'string'
2315 },
2316 'lifetime-annotation': {
2317 pattern: /'[^\s>']+/,
2318 alias: 'symbol'
2319 },
1804 'keyword': /\b(?:abstract|alignof|as|be|box|break|const|continue|crate|do|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|match|mod|move|mut|offsetof|once|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/,2320 'keyword': /\b(?:abstract|alignof|as|be|box|break|const|continue|crate|do|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|match|mod|move|mut|offsetof|once|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/,
1805 2321
1806 'attribute': {2322 'attribute': {
@@ -1810,125 +2326,71 @@ Prism.languages.rust = { · 3 unmodified lines
1810 },2326 },
1811 2327
1812 'function': [2328 'function': [
1813 /[a-z0-9_]+(?=\s*\()/i,2329 /\w+(?=\s*\()/,
1814 // Macros can use parens or brackets2330 // Macros can use parens or brackets
1815 /[a-z0-9_]+!(?=\s*\(|\[)/i2331 /\w+!(?=\s*\(|\[)/
1816 ],2332 ],
1817 'macro-rules': {2333 'macro-rules': {
1818 pattern: /[a-z0-9_]+!/i,2334 pattern: /\w+!/,
1819 alias: 'function'2335 alias: 'function'
1820 },2336 },
1821 2337
1822 // Hex, oct, bin, dec numbers with visual separators and type suffix2338 // 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/,
1824 2340
1825 // Closure params should not be confused with bitwise OR |2341 // Closure params should not be confused with bitwise OR |
1826 'closure-params': {2342 'closure-params': {
1827 pattern: /\|[^|]*\|(?=\s*[{-])/,2343 pattern: /\|[^|]*\|(?=\s*[{-])/,
1828 inside: {2344 inside: {
1829 'punctuation': /[\|:,]/,2345 'punctuation': /[|:,]/,
1830 'operator': /[&*]/2346 'operator': /[&*]/
1831 }2347 }
1832 },2348 },
1833 'punctuation': /[{}[\];(),:]|\.+|->/,2349 'punctuation': /[{}[\];(),:]|\.+|->/,
1834 'operator': /[-+*\/%!^=]=?|@|&[&=]?|\|[|=]?|<<?=?|>>?=?/2350 'operator': /[-+*\/%!^]=?|=[=>]?|@|&[&=]?|\|[|=]?|<<?=?|>>?=?/
1835};2351};
1836Prism.languages.scss = Prism.languages.extend('css', {2352Prism.languages.yaml = {
1837 'comment': {2353 'scalar': {
1838 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,2354 pattern: /([\-:]\s*(?:![^\s]+)?[ \t]*[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\2[^\r\n]+)*)/,
1839 lookbehind: true2355 lookbehind: true,
2356 alias: 'string'
1840 },2357 },
1841 'atrule': {2358 'comment': /#.*/,
1842 pattern: /@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,2359 'key': {
1843 inside: {2360 pattern: /(\s*(?:^|[:\-,[{\r\n?])[ \t]*(?:![^\s]+)?[ \t]*)[^\r\n{[\]},#\s]+?(?=\s*:\s)/,
1844 'rule': /@[\w-]+/2361 lookbehind: true,
1845 // See rest below2362 alias: 'atrule'
1846 }
1847 },2363 },
1848 // url, compassified2364 'directive': {
1849 'url': /(?:[-a-z]+-)*url(?=\()/i,2365 pattern: /(^[ \t]*)%.+/m,
1850 // CSS selector regex is not appropriate for Sass2366 lookbehind: true,
1851 // since there can be lot more things (var, @ directive, nesting..)2367 alias: 'important'
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
1871Prism.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
1881Prism.languages.scss.property = {
1882 pattern: /(?:[\w-]|\$[-_\w]+|#\{\$[-_\w]+\})+(?=\s*:)/i,
1883 inside: {
1884 'variable': /\$[-_\w]+|#\{\$[-_\w]+\}/
1885 }
1886};
1887
1888Prism.languages.insertBefore('scss', 'important', {
1889 // var and interpolated vars
1890 'variable': /\$[-_\w]+|#\{\$[-_\w]+\}/
1891});
1892
1893Prism.languages.insertBefore('scss', 'function', {
1894 'placeholder': {
1895 pattern: /%[-_\w]+/,
1896 alias: 'selector'
1897 },2368 },
1898 'statement': {2369 'datetime': {
1899 pattern: /\B!(?:default|optional)\b/i,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,
1900 alias: 'keyword'2371 lookbehind: true,
2372 alias: 'number'
1901 },2373 },
1902 'boolean': /\b(?:true|false)\b/,2374 'boolean': {
1903 'null': /\bnull\b/,2375 pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)(?:true|false)[ \t]*(?=$|,|]|})/im,
1904 'operator': {2376 lookbehind: true,
1905 pattern: /(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,2377 alias: 'important'
1906 lookbehind: true
1907 }
1908});
1909
1910Prism.languages.scss['atrule'].inside.rest = Prism.util.clone(Prism.languages.scss);
1911Prism.languages.sql= {
1912 'comment': {
1913 pattern: /(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,
1914 lookbehind: true
1915 },2378 },
1916 'string' : {2379 'null': {
1917 pattern: /(^|[^@\\])("|')(?:\\?[\s\S])*?\2/,2380 pattern: /([:\-,[{]\s*(?:![^\s]+)?[ \t]*)(?:null|~)[ \t]*(?=$|,|]|})/im,
1918 greedy: true,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,
1919 lookbehind: true2391 lookbehind: true
1920 },2392 },
1921 'variable': /@[\w.$]+|@("|'|`)(?:\\?[\s\S])+?\1/,2393 'tag': /![^\s]+/,
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?2394 'important': /[&*][\w]+/,
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,2395 'punctuation': /---|[:[\]{}\-,|>?]|\.\.\./
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': /[;[\]()`,.]/
1928};2396};
1929Prism.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
1934Prism.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 */
2 2
3/*3/*
4 * Includes TraceKit4 * Includes TraceKit
@@ -10,322 +10,270 @@ · 5 unmodified lines
10 *10 *
11 */11 */
12 12
13(function(f) {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 if (typeof exports === 'object' && typeof module !== 'undefined') {14function RavenConfigError(message) {
15 module.exports = f();15 this.name = 'RavenConfigError';
16 } else if (typeof define === 'function' && define.amd) {16 this.message = message;
17 define([], f);17}
18 } else {18RavenConfigError.prototype = new Error();
19 var g;19RavenConfigError.prototype.constructor = RavenConfigError;
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 }
87 20
88 var sentryLevel = level === 'warn' ? 'warning' : level;21module.exports = RavenConfigError;
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*/
135 22
136 var TraceKit = _dereq_(6);23},{}],2:[function(_dereq_,module,exports){
137 var stringify = _dereq_(7);24var wrapMethod = function(console, level, callback) {
138 var RavenConfigError = _dereq_(1);25 var originalConsoleLevel = console[level];
139 var utils = _dereq_(5);26 var originalConsole = console;
140 27
141 var isError = utils.isError,28 if (!(level in console)) {
142 isObject = utils.isObject;29 return;
30 }
143 31
144 var wrapConsoleMethod = _dereq_(2).wrapMethod;32 var sentryLevel = level === 'warn' ? 'warning' : level;
145 33
146 var dsnKeys = 'source protocol user pass host port path'.split(' '),34 console[level] = function() {
147 dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;35 var args = [].slice.call(arguments);
148 36
149 function now() {37 var msg = '' + args.join(' ');
150 return +new Date();38 var data = {level: sentryLevel, logger: 'console', extra: {arguments: args}};
151 }
152 39
153 // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)40 if (level === 'assert') {
154 var _window =41 if (args[0] === false) {
155 typeof window !== 'undefined'42 // Default browsers message
156 ? window43 msg = 'Assertion failed: ' + (args.slice(1).join(' ') || 'console.assert');
157 : typeof global !== 'undefined'44 data.extra.arguments = args.slice(1);
158 ? global45 callback && callback(msg, data);
159 : typeof self !== 'undefined' ? self : {};46 }
160 var _document = _window.document;47 } else {
161 var _navigator = _window.navigator;48 callback && callback(msg, data);
162 49 }
163 function keepOriginalCallback(original, callback) {
164 return isFunction(callback)
165 ? function(data) {
166 return callback(data, original);
167 }
168 : callback;
169 }
170 50
171 // First, check for JSON support51 // this fails for some browsers. :(
172 // If there is no JSON, we no-op the core features of Raven52 if (originalConsoleLevel) {
173 // since JSON is required to encode the payload53 // IE9 doesn't allow calling apply on console functions directly
174 function Raven() {54 // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193
175 this._hasJSON = !!(typeof JSON === 'object' && JSON.stringify);55 Function.prototype.apply.call(originalConsoleLevel, originalConsole, args);
176 // Raven can run in contexts where there's no document (react-native)56 }
177 this._hasDocument = !isUndefined(_document);57 };
178 this._hasNavigator = !isUndefined(_navigator);58};
179 this._lastCapturedException = null;59
180 this._lastData = null;60module.exports = {
181 this._lastEventId = null;61 wrapMethod: wrapMethod
182 this._globalServer = null;62};
183 this._globalKey = null;63
184 this._globalProject = null;64},{}],3:[function(_dereq_,module,exports){
185 this._globalContext = {};65(function (global){
186 this._globalOptions = {66/*global XDomainRequest:false */
187 logger: 'javascript',67
188 ignoreErrors: [],68var TraceKit = _dereq_(6);
189 ignoreUrls: [],69var stringify = _dereq_(7);
190 whitelistUrls: [],70var RavenConfigError = _dereq_(1);
191 includePaths: [],71
192 collectWindowErrors: true,72var utils = _dereq_(5);
193 maxMessageLength: 0,73var isError = utils.isError;
194 74var isObject = utils.isObject;
195 // By default, truncates URL values to 250 chars75var isErrorEvent = utils.isErrorEvent;
196 maxUrlLength: 250,76var isUndefined = utils.isUndefined;
197 stackTraceLimit: 50,77var isFunction = utils.isFunction;
198 autoBreadcrumbs: true,78var isString = utils.isString;
199 instrument: true,79var isArray = utils.isArray;
200 sampleRate: 180var isEmptyObject = utils.isEmptyObject;
201 };81var each = utils.each;
202 this._ignoreOnError = 0;82var objectMerge = utils.objectMerge;
203 this._isRavenInstalled = false;83var truncate = utils.truncate;
204 this._originalErrorStackTraceLimit = Error.stackTraceLimit;84var objectFrozen = utils.objectFrozen;
205 // capture references to window.console *and* all its methods first85var hasKey = utils.hasKey;
206 // before the console plugin has a chance to monkey patch86var joinRegExp = utils.joinRegExp;
207 this._originalConsole = _window.console || {};87var urlencode = utils.urlencode;
208 this._originalConsoleMethods = {};88var uuid4 = utils.uuid4;
209 this._plugins = [];89var htmlTreeAsString = utils.htmlTreeAsString;
210 this._startTime = now();90var isSameException = utils.isSameException;
211 this._wrappedBuiltIns = [];91var isSameStacktrace = utils.isSameStacktrace;
212 this._breadcrumbs = [];92var parseUrl = utils.parseUrl;
213 this._lastCapturedEvent = null;93var fill = utils.fill;
214 this._keypressTimeout;94
215 this._location = _window.location;95var wrapConsoleMethod = _dereq_(2).wrapMethod;
216 this._lastHref = this._location && this._location.href;96
217 this._resetBackoff();97var dsnKeys = 'source protocol user pass host port path'.split(' '),
218 98 dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;
219 // eslint-disable-next-line guard-for-in99
220 for (var method in this._originalConsole) {100function now() {
221 this._originalConsoleMethods[method] = this._originalConsole[method];101 return +new Date();
222 }102}
223 }103
104// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
105var _window =
106 typeof window !== 'undefined'
107 ? window
108 : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
109var _document = _window.document;
110var _navigator = _window.navigator;
111
112function 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
123function 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}
224 173
225 /*174/*
226 * The core Raven singleton175 * The core Raven singleton
227 *176 *
228 * @this {Raven}177 * @this {Raven}
229 */178 */
230 179
231 Raven.prototype = {180Raven.prototype = {
232 // Hardcode version string so that raven source can be loaded directly via181 // Hardcode version string so that raven source can be loaded directly via
233 // webpack (using a build step causes webpack #1617). Grunt verifies that182 // webpack (using a build step causes webpack #1617). Grunt verifies that
234 // this value matches package.json during build.183 // this value matches package.json during build.
235 // See: https://github.com/getsentry/raven-js/issues/465184 // See: https://github.com/getsentry/raven-js/issues/465
236 VERSION: '3.18.0',185 VERSION: '3.20.1',
237 186
238 debug: false,187 debug: false,
239 188
240 TraceKit: TraceKit, // alias to TraceKit189 TraceKit: TraceKit, // alias to TraceKit
241 190
242 /*191 /*
243 * Configure Raven with a DSN and extra options192 * Configure Raven with a DSN and extra options
244 *193 *
245 * @param {string} dsn The public Sentry DSN194 * @param {string} dsn The public Sentry DSN
246 * @param {object} options Set of global options [optional]195 * @param {object} options Set of global options [optional]
247 * @return {Raven}196 * @return {Raven}
248 */197 */
249 config: function(dsn, options) {198 config: function(dsn, options) {
250 var self = this;199 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 }
271 200
272 self.setDSN(dsn);201 if (self._globalServer) {
273 202 this._logDebug('error', 'Error: Raven has already been configured');
274 // "Script error." is hard coded into browsers for errors that it can't read.203 return self;
275 // this is the result of a script being pulled in from an external domain and CORS.204 }
276 globalOptions.ignoreErrors.push(/^Script error\.?$/);205 if (!dsn) return self;
277 globalOptions.ignoreErrors.push(206
278 /^Javascript error: Script error\.? on line 0$/207 var globalOptions = self._globalOptions;
279 );208
280 209 // merge in options
281 // join regexp rules into one big rule210 if (options) {
282 globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors);211 each(options, function(key, value) {
283 globalOptions.ignoreUrls = globalOptions.ignoreUrls.length212 // tags and extra are special and need to be put into context
284 ? joinRegExp(globalOptions.ignoreUrls)213 if (key === 'tags' || key === 'extra' || key === 'user') {
285 : false;214 self._globalContext[key] = value;
286 globalOptions.whitelistUrls = globalOptions.whitelistUrls.length215 } else {
287 ? joinRegExp(globalOptions.whitelistUrls)216 globalOptions[key] = value;
288 : false;217 }
289 globalOptions.includePaths = joinRegExp(globalOptions.includePaths);218 });
290 globalOptions.maxBreadcrumbs = Math.max(219 }
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 };
301 220
302 var autoBreadcrumbs = globalOptions.autoBreadcrumbs;221 self.setDSN(dsn);
303 if ({}.toString.call(autoBreadcrumbs) === '[object Object]') {222
304 autoBreadcrumbs = objectMerge(autoBreadcrumbDefaults, autoBreadcrumbs);223 // "Script error." is hard coded into browsers for errors that it can't read.
305 } else if (autoBreadcrumbs !== false) {224 // this is the result of a script being pulled in from an external domain and CORS.
306 autoBreadcrumbs = autoBreadcrumbDefaults;225 globalOptions.ignoreErrors.push(/^Script error\.?$/);
307 }226 globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/);
308 globalOptions.autoBreadcrumbs = autoBreadcrumbs;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;
309 257
310 var instrumentDefaults = {258 var instrumentDefaults = {
311 tryCatch: true259 tryCatch: true
312 };260 };
313 261
314 var instrument = globalOptions.instrument;262 var instrument = globalOptions.instrument;
315 if ({}.toString.call(instrument) === '[object Object]') {263 if ({}.toString.call(instrument) === '[object Object]') {
316 instrument = objectMerge(instrumentDefaults, instrument);264 instrument = objectMerge(instrumentDefaults, instrument);
317 } else if (instrument !== false) {265 } else if (instrument !== false) {
318 instrument = instrumentDefaults;266 instrument = instrumentDefaults;
319 }267 }
320 globalOptions.instrument = instrument;268 globalOptions.instrument = instrument;
321 269
322 TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors;270 TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors;
323 271
324 // return for chaining272 // return for chaining
325 return self;273 return self;
326 },274 },
327 275
328 /*276 /*
329 * Installs a global window.onerror error handler277 * Installs a global window.onerror error handler
330 * to capture and report uncaught exceptions.278 * to capture and report uncaught exceptions.
331 * At this point, install() is required to be called due279 * At this point, install() is required to be called due
@@ -333,63 +281,58 @@ · 1 unmodified line
333 *281 *
334 * @return {Raven}282 * @return {Raven}
335 */283 */
336 install: function() {284 install: function() {
337 var self = this;285 var self = this;
338 if (self.isSetup() && !self._isRavenInstalled) {286 if (self.isSetup() && !self._isRavenInstalled) {
339 TraceKit.report.subscribe(function() {287 TraceKit.report.subscribe(function() {
340 self._handleOnErrorStackInfo.apply(self, arguments);288 self._handleOnErrorStackInfo.apply(self, arguments);
341 });289 });
342 if (
343 self._globalOptions.instrument &&
344 self._globalOptions.instrument.tryCatch
345 ) {
346 self._instrumentTryCatch();
347 }
348 290
349 if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs();291 self._patchFunctionToString();
350 292
351 // Install all of the plugins293 if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch) {
352 self._drainPlugins();294 self._instrumentTryCatch();
295 }
353 296
354 self._isRavenInstalled = true;297 if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs();
355 }
356 298
357 Error.stackTraceLimit = self._globalOptions.stackTraceLimit;299 // Install all of the plugins
358 return this;300 self._drainPlugins();
359 },
360 301
361 /*302 self._isRavenInstalled = true;
303 }
304
305 Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
306 return this;
307 },
308
309 /*
362 * Set the DSN (can be called multiple time unlike config)310 * Set the DSN (can be called multiple time unlike config)
363 *311 *
364 * @param {string} dsn The public Sentry DSN312 * @param {string} dsn The public Sentry DSN
365 */313 */
366 setDSN: function(dsn) {314 setDSN: function(dsn) {
367 var self = this,315 var self = this,
368 uri = self._parseDSN(dsn),316 uri = self._parseDSN(dsn),
369 lastSlash = uri.path.lastIndexOf('/'),317 lastSlash = uri.path.lastIndexOf('/'),
370 path = uri.path.substr(1, lastSlash);318 path = uri.path.substr(1, lastSlash);
371 319
372 self._dsn = dsn;320 self._dsn = dsn;
373 self._globalKey = uri.user;321 self._globalKey = uri.user;
374 self._globalSecret = uri.pass && uri.pass.substr(1);322 self._globalSecret = uri.pass && uri.pass.substr(1);
375 self._globalProject = uri.path.substr(lastSlash + 1);323 self._globalProject = uri.path.substr(lastSlash + 1);
376 324
377 self._globalServer = self._getGlobalServer(uri);325 self._globalServer = self._getGlobalServer(uri);
378 326
379 self._globalEndpoint =327 self._globalEndpoint =
380 self._globalServer +328 self._globalServer + '/' + path + 'api/' + self._globalProject + '/store/';
381 '/' +329
382 path +330 // Reset backoff state since we may be pointing at a
383 'api/' +331 // new project/server
384 self._globalProject +332 this._resetBackoff();
385 '/store/';333 },
386 334
387 // Reset backoff state since we may be pointing at a335 /*
388 // new project/server
389 this._resetBackoff();
390 },
391
392 /*
393 * Wrap code within a context so Raven can capture errors336 * Wrap code within a context so Raven can capture errors
394 * reliably across domains that is executed immediately.337 * reliably across domains that is executed immediately.
395 *338 *
@@ -397,17 +340,17 @@ · 1 unmodified line
397 * @param {function} func The callback to be immediately executed within the context340 * @param {function} func The callback to be immediately executed within the context
398 * @param {array} args An array of arguments to be called with the callback [optional]341 * @param {array} args An array of arguments to be called with the callback [optional]
399 */342 */
400 context: function(options, func, args) {343 context: function(options, func, args) {
401 if (isFunction(options)) {344 if (isFunction(options)) {
402 args = func || [];345 args = func || [];
403 func = options;346 func = options;
404 options = undefined;347 options = undefined;
405 }348 }
406 349
407 return this.wrap(options, func).apply(this, args);350 return this.wrap(options, func).apply(this, args);
408 },351 },
409 352
410 /*353 /*
411 * Wrap code within a context and returns back a new function to be executed354 * Wrap code within a context and returns back a new function to be executed
412 *355 *
413 * @param {object} options A specific set of options for this context [optional]356 * @param {object} options A specific set of options for this context [optional]
@@ -415,381 +358,397 @@ · 1 unmodified line
415 * @param {function} func A function to call before the try/catch wrapper [optional, private]358 * @param {function} func A function to call before the try/catch wrapper [optional, private]
416 * @return {function} The newly wrapped functions with a context359 * @return {function} The newly wrapped functions with a context
417 */360 */
418 wrap: function(options, func, _before) {361 wrap: function(options, func, _before) {
419 var self = this;362 var self = this;
420 // 1 argument has been passed, and it's not a function363 // 1 argument has been passed, and it's not a function
421 // so just return it364 // so just return it
422 if (isUndefined(func) && !isFunction(options)) {365 if (isUndefined(func) && !isFunction(options)) {
423 return options;366 return options;
424 }367 }
425
426 // options is optional
427 if (isFunction(options)) {
428 func = options;
429 options = undefined;
430 }
431 368
432 // At this point, we've passed along 2 arguments, and the second one369 // options is optional
433 // is not a function either, so we'll just return the second argument.370 if (isFunction(options)) {
434 if (!isFunction(func)) {371 func = options;
435 return func;372 options = undefined;
436 }373 }
437 374
438 // We don't wanna wrap it twice!375 // At this point, we've passed along 2 arguments, and the second one
439 try {376 // is not a function either, so we'll just return the second argument.
440 if (func.__raven__) {377 if (!isFunction(func)) {
441 return func;378 return func;
442 }379 }
443 380
444 // If this has already been wrapped in the past, return that381 // We don't wanna wrap it twice!
445 if (func.__raven_wrapper__) {382 try {
446 return func.__raven_wrapper__;383 if (func.__raven__) {
447 }384 return func;
448 } catch (e) {385 }
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 }
454 386
455 function wrapped() {387 // If this has already been wrapped in the past, return that
456 var args = [],388 if (func.__raven_wrapper__) {
457 i = arguments.length,389 return func.__raven_wrapper__;
458 deep = !options || (options && options.deep !== false);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 }
459 397
460 if (_before && isFunction(_before)) {398 function wrapped() {
461 _before.apply(this, arguments);399 var args = [],
462 }400 i = arguments.length,
401 deep = !options || (options && options.deep !== false);
463 402
464 // Recursively wrap all of a function's arguments that are403 if (_before && isFunction(_before)) {
465 // functions themselves.404 _before.apply(this, arguments);
466 while (i--)405 }
467 args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i];
468 406
469 try {407 // Recursively wrap all of a function's arguments that are
470 // Attempt to invoke user-land function408 // functions themselves.
471 // NOTE: If you are a Sentry user, and you are seeing this stack frame, it409 while (i--) args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i];
472 // means Raven caught an error invoking your application code. This is410
473 // expected behavior and NOT indicative of a bug with Raven.js.411 try {
474 return func.apply(this, args);412 // Attempt to invoke user-land function
475 } catch (e) {413 // NOTE: If you are a Sentry user, and you are seeing this stack frame, it
476 self._ignoreNextOnError();414 // means Raven caught an error invoking your application code. This is
477 self.captureException(e, options);415 // expected behavior and NOT indicative of a bug with Raven.js.
478 throw e;416 return func.apply(this, args);
479 }417 } catch (e) {
480 }418 self._ignoreNextOnError();
419 self.captureException(e, options);
420 throw e;
421 }
422 }
481 423
482 // copy over properties of the old function424 // copy over properties of the old function
483 for (var property in func) {425 for (var property in func) {
484 if (hasKey(func, property)) {426 if (hasKey(func, property)) {
485 wrapped[property] = func[property];427 wrapped[property] = func[property];
486 }428 }
487 }429 }
488 wrapped.prototype = func.prototype;430 wrapped.prototype = func.prototype;
489 431
490 func.__raven_wrapper__ = wrapped;432 func.__raven_wrapper__ = wrapped;
491 // Signal that this function has been wrapped already433 // Signal that this function has been wrapped/filled already
492 // for both debugging and to prevent it to being wrapped twice434 // for both debugging and to prevent it to being wrapped/filled twice
493 wrapped.__raven__ = true;435 wrapped.__raven__ = true;
494 wrapped.__inner__ = func;436 wrapped.__orig__ = func;
495 437
496 return wrapped;438 return wrapped;
497 },439 },
498 440
499 /*441 /*
500 * Uninstalls the global error handler.442 * Uninstalls the global error handler.
501 *443 *
502 * @return {Raven}444 * @return {Raven}
503 */445 */
504 uninstall: function() {446 uninstall: function() {
505 TraceKit.report.uninstall();447 TraceKit.report.uninstall();
506 448
507 this._restoreBuiltIns();449 this._unpatchFunctionToString();
450 this._restoreBuiltIns();
508 451
509 Error.stackTraceLimit = this._originalErrorStackTraceLimit;452 Error.stackTraceLimit = this._originalErrorStackTraceLimit;
510 this._isRavenInstalled = false;453 this._isRavenInstalled = false;
511 454
512 return this;455 return this;
513 },456 },
514 457
515 /*458 /*
516 * Manually capture an exception and send it over to Sentry459 * Manually capture an exception and send it over to Sentry
517 *460 *
518 * @param {error} ex An exception to be logged461 * @param {error} ex An exception to be logged
519 * @param {object} options A specific set of options for this error [optional]462 * @param {object} options A specific set of options for this error [optional]
520 * @return {Raven}463 * @return {Raven}
521 */464 */
522 captureException: function(ex, options) {465 captureException: function(ex, options) {
523 // If not an Error is passed through, recall as a message instead466 // Cases for sending ex as a message, rather than an exception
524 if (!isError(ex)) {467 var isNotError = !isError(ex);
525 return this.captureMessage(468 var isNotErrorEvent = !isErrorEvent(ex);
526 ex,469 var isErrorEventWithoutError = isErrorEvent(ex) && !ex.error;
527 objectMerge(470
528 {471 if ((isNotError && isNotErrorEvent) || isErrorEventWithoutError) {
529 trimHeadFrames: 1,472 return this.captureMessage(
530 stacktrace: true // if we fall back to captureMessage, default to attempting a new trace473 ex,
531 },474 objectMerge(
532 options475 {
533 )476 trimHeadFrames: 1,
534 );477 stacktrace: true // if we fall back to captureMessage, default to attempting a new trace
535 }478 },
536 479 options
537 // Store the raw exception object for potential debugging and introspection480 )
538 this._lastCapturedException = ex;481 );
482 }
539 483
540 // TraceKit.report will re-raise any exception passed to it,484 // Get actual Error from ErrorEvent
541 // which means you have to wrap it in try/catch. Instead, we485 if (isErrorEvent(ex)) ex = ex.error;
542 // can wrap it here and only re-raise if TraceKit.report486
543 // raises an exception different from the one we asked to487 // Store the raw exception object for potential debugging and introspection
544 // report on.488 this._lastCapturedException = ex;
545 try {489
546 var stack = TraceKit.computeStackTrace(ex);490 // TraceKit.report will re-raise any exception passed to it,
547 this._handleStackInfo(stack, options);491 // which means you have to wrap it in try/catch. Instead, we
548 } catch (ex1) {492 // can wrap it here and only re-raise if TraceKit.report
549 if (ex !== ex1) {493 // raises an exception different from the one we asked to
550 throw ex1;494 // report on.
551 }495 try {
552 }496 var stack = TraceKit.computeStackTrace(ex);
497 this._handleStackInfo(stack, options);
498 } catch (ex1) {
499 if (ex !== ex1) {
500 throw ex1;
501 }
502 }
553 503
554 return this;504 return this;
555 },505 },
556 506
557 /*507 /*
558 * Manually send a message to Sentry508 * Manually send a message to Sentry
559 *509 *
560 * @param {string} msg A plain message to be captured in Sentry510 * @param {string} msg A plain message to be captured in Sentry
561 * @param {object} options A specific set of options for this message [optional]511 * @param {object} options A specific set of options for this message [optional]
562 * @return {Raven}512 * @return {Raven}
563 */513 */
564 captureMessage: function(msg, options) {514 captureMessage: function(msg, options) {
565 // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an515 // 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's516 // early call; we'll error on the side of logging anything called before configuration since it's
567 // probably something you should see:517 // probably something you should see:
568 if (518 if (
569 !!this._globalOptions.ignoreErrors.test &&519 !!this._globalOptions.ignoreErrors.test &&
570 this._globalOptions.ignoreErrors.test(msg)520 this._globalOptions.ignoreErrors.test(msg)
571 ) {521 ) {
572 return;522 return;
573 }523 }
574 524
575 options = options || {};525 options = options || {};
576 526
577 var data = objectMerge(527 var data = objectMerge(
578 {528 {
579 message: msg + '' // Make sure it's actually a string529 message: msg + '' // Make sure it's actually a string
580 },530 },
581 options531 options
582 );532 );
583 533
584 if (this._globalOptions.stacktrace || (options && options.stacktrace)) {534 var ex;
585 var ex;535 // Generate a "synthetic" stack trace from this point.
586 // 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
587 // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative537 // of a bug with Raven.js. Sentry generates synthetic traces either by configuration,
588 // 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.
589 // or if it catches a thrown object without a "stack" property.539 try {
590 try {540 throw new Error(msg);
591 throw new Error(msg);541 } catch (ex1) {
592 } catch (ex1) {542 ex = ex1;
593 ex = ex1;543 }
594 }
595 544
596 // null exception name so `Error` isn't prefixed to msg545 // null exception name so `Error` isn't prefixed to msg
597 ex.name = null;546 ex.name = null;
598 547 var stack = TraceKit.computeStackTrace(ex);
599 options = objectMerge(548
600 {549 // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1]
601 // fingerprint on msg, not stack trace (legacy behavior, could be550 var initialCall = isArray(stack.stack) && stack.stack[1];
602 // revisited)551 var fileurl = (initialCall && initialCall.url) || '';
603 fingerprint: msg,552
604 // since we know this is a synthetic trace, the top N-most frames553 if (
605 // MUST be from Raven.js, so mark them as in_app later by setting554 !!this._globalOptions.ignoreUrls.test &&
606 // trimHeadFrames555 this._globalOptions.ignoreUrls.test(fileurl)
607 trimHeadFrames: (options.trimHeadFrames || 0) + 1556 ) {
608 },557 return;
609 options558 }
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 }
619 559
620 // Fire away!560 if (
621 this._send(data);561 !!this._globalOptions.whitelistUrls.test &&
562 !this._globalOptions.whitelistUrls.test(fileurl)
563 ) {
564 return;
565 }
622 566
623 return this;567 if (this._globalOptions.stacktrace || (options && options.stacktrace)) {
624 },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 }
625 587
626 captureBreadcrumb: function(obj) {588 // Fire away!
627 var crumb = objectMerge(589 this._send(data);
628 {
629 timestamp: now() / 1000
630 },
631 obj
632 );
633 590
634 if (isFunction(this._globalOptions.breadcrumbCallback)) {591 return this;
635 var result = this._globalOptions.breadcrumbCallback(crumb);592 },
636 593
637 if (isObject(result) && !isEmptyObject(result)) {594 captureBreadcrumb: function(obj) {
638 crumb = result;595 var crumb = objectMerge(
639 } else if (result === false) {596 {
640 return this;597 timestamp: now() / 1000
641 }598 },
642 }599 obj
600 );
643 601
644 this._breadcrumbs.push(crumb);602 if (isFunction(this._globalOptions.breadcrumbCallback)) {
645 if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) {603 var result = this._globalOptions.breadcrumbCallback(crumb);
646 this._breadcrumbs.shift();604
647 }605 if (isObject(result) && !isEmptyObject(result)) {
648 return this;606 crumb = result;
649 },607 } else if (result === false) {
608 return this;
609 }
610 }
650 611
651 addPlugin: function(plugin /*arg1, arg2, ... argN*/) {612 this._breadcrumbs.push(crumb);
652 var pluginArgs = [].slice.call(arguments, 1);613 if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) {
614 this._breadcrumbs.shift();
615 }
616 return this;
617 },
653 618
654 this._plugins.push([plugin, pluginArgs]);619 addPlugin: function(plugin /*arg1, arg2, ... argN*/) {
655 if (this._isRavenInstalled) {620 var pluginArgs = [].slice.call(arguments, 1);
656 this._drainPlugins();
657 }
658 621
659 return this;622 this._plugins.push([plugin, pluginArgs]);
660 },623 if (this._isRavenInstalled) {
624 this._drainPlugins();
625 }
626
627 return this;
628 },
661 629
662 /*630 /*
663 * Set/clear a user to be sent along with the payload.631 * Set/clear a user to be sent along with the payload.
664 *632 *
665 * @param {object} user An object representing user data [optional]633 * @param {object} user An object representing user data [optional]
666 * @return {Raven}634 * @return {Raven}
667 */635 */
668 setUserContext: function(user) {636 setUserContext: function(user) {
669 // Intentionally do not merge here since that's an unexpected behavior.637 // Intentionally do not merge here since that's an unexpected behavior.
670 this._globalContext.user = user;638 this._globalContext.user = user;
671 639
672 return this;640 return this;
673 },641 },
674 642
675 /*643 /*
676 * Merge extra attributes to be sent along with the payload.644 * Merge extra attributes to be sent along with the payload.
677 *645 *
678 * @param {object} extra An object representing extra data [optional]646 * @param {object} extra An object representing extra data [optional]
679 * @return {Raven}647 * @return {Raven}
680 */648 */
681 setExtraContext: function(extra) {649 setExtraContext: function(extra) {
682 this._mergeContext('extra', extra);650 this._mergeContext('extra', extra);
683 651
684 return this;652 return this;
685 },653 },
686 654
687 /*655 /*
688 * Merge tags to be sent along with the payload.656 * Merge tags to be sent along with the payload.
689 *657 *
690 * @param {object} tags An object representing tags [optional]658 * @param {object} tags An object representing tags [optional]
691 * @return {Raven}659 * @return {Raven}
692 */660 */
693 setTagsContext: function(tags) {661 setTagsContext: function(tags) {
694 this._mergeContext('tags', tags);662 this._mergeContext('tags', tags);
695 663
696 return this;664 return this;
697 },665 },
698 666
699 /*667 /*
700 * Clear all of the context.668 * Clear all of the context.
701 *669 *
702 * @return {Raven}670 * @return {Raven}
703 */671 */
704 clearContext: function() {672 clearContext: function() {
705 this._globalContext = {};673 this._globalContext = {};
706 674
707 return this;675 return this;
708 },676 },
709 677
710 /*678 /*
711 * Get a copy of the current context. This cannot be mutated.679 * Get a copy of the current context. This cannot be mutated.
712 *680 *
713 * @return {object} copy of context681 * @return {object} copy of context
714 */682 */
715 getContext: function() {683 getContext: function() {
716 // lol javascript684 // lol javascript
717 return JSON.parse(stringify(this._globalContext));685 return JSON.parse(stringify(this._globalContext));
718 },686 },
719 687
720 /*688 /*
721 * Set environment of application689 * Set environment of application
722 *690 *
723 * @param {string} environment Typically something like 'production'.691 * @param {string} environment Typically something like 'production'.
724 * @return {Raven}692 * @return {Raven}
725 */693 */
726 setEnvironment: function(environment) {694 setEnvironment: function(environment) {
727 this._globalOptions.environment = environment;695 this._globalOptions.environment = environment;
728 696
729 return this;697 return this;
730 },698 },
731 699
732 /*700 /*
733 * Set release version of application701 * Set release version of application
734 *702 *
735 * @param {string} release Typically something like a git SHA to identify version703 * @param {string} release Typically something like a git SHA to identify version
736 * @return {Raven}704 * @return {Raven}
737 */705 */
738 setRelease: function(release) {706 setRelease: function(release) {
739 this._globalOptions.release = release;707 this._globalOptions.release = release;
740 708
741 return this;709 return this;
742 },710 },
743 711
744 /*712 /*
745 * Set the dataCallback option713 * Set the dataCallback option
746 *714 *
747 * @param {function} callback The callback to run which allows the715 * @param {function} callback The callback to run which allows the
748 * data blob to be mutated before sending716 * data blob to be mutated before sending
749 * @return {Raven}717 * @return {Raven}
750 */718 */
751 setDataCallback: function(callback) {719 setDataCallback: function(callback) {
752 var original = this._globalOptions.dataCallback;720 var original = this._globalOptions.dataCallback;
753 this._globalOptions.dataCallback = keepOriginalCallback(721 this._globalOptions.dataCallback = keepOriginalCallback(original, callback);
754 original,722 return this;
755 callback723 },
756 );724
757 return this;725 /*
758 },
759
760 /*
761 * Set the breadcrumbCallback option726 * Set the breadcrumbCallback option
762 *727 *
763 * @param {function} callback The callback to run which allows filtering728 * @param {function} callback The callback to run which allows filtering
764 * or mutating breadcrumbs729 * or mutating breadcrumbs
765 * @return {Raven}730 * @return {Raven}
766 */731 */
767 setBreadcrumbCallback: function(callback) {732 setBreadcrumbCallback: function(callback) {
768 var original = this._globalOptions.breadcrumbCallback;733 var original = this._globalOptions.breadcrumbCallback;
769 this._globalOptions.breadcrumbCallback = keepOriginalCallback(734 this._globalOptions.breadcrumbCallback = keepOriginalCallback(original, callback);
770 original,735 return this;
771 callback736 },
772 );737
773 return this;738 /*
774 },
775
776 /*
777 * Set the shouldSendCallback option739 * Set the shouldSendCallback option
778 *740 *
779 * @param {function} callback The callback to run which allows741 * @param {function} callback The callback to run which allows
780 * introspecting the blob before sending742 * introspecting the blob before sending
781 * @return {Raven}743 * @return {Raven}
782 */744 */
783 setShouldSendCallback: function(callback) {745 setShouldSendCallback: function(callback) {
784 var original = this._globalOptions.shouldSendCallback;746 var original = this._globalOptions.shouldSendCallback;
785 this._globalOptions.shouldSendCallback = keepOriginalCallback(747 this._globalOptions.shouldSendCallback = keepOriginalCallback(original, callback);
786 original,748 return this;
787 callback749 },
788 );750
789 return this;751 /**
790 },
791
792 /**
793 * Override the default HTTP transport mechanism that transmits data752 * Override the default HTTP transport mechanism that transmits data
794 * to the Sentry server.753 * to the Sentry server.
795 *754 *
@@ -798,438 +757,443 @@ · 2 unmodified lines
798 *757 *
799 * @return {Raven}758 * @return {Raven}
800 */759 */
801 setTransport: function(transport) {760 setTransport: function(transport) {
802 this._globalOptions.transport = transport;761 this._globalOptions.transport = transport;
803 762
804 return this;763 return this;
805 },764 },
806 765
807 /*766 /*
808 * Get the latest raw exception that was captured by Raven.767 * Get the latest raw exception that was captured by Raven.
809 *768 *
810 * @return {error}769 * @return {error}
811 */770 */
812 lastException: function() {771 lastException: function() {
813 return this._lastCapturedException;772 return this._lastCapturedException;
814 },773 },
815 774
816 /*775 /*
817 * Get the last event id776 * Get the last event id
818 *777 *
819 * @return {string}778 * @return {string}
820 */779 */
821 lastEventId: function() {780 lastEventId: function() {
822 return this._lastEventId;781 return this._lastEventId;
823 },782 },
824 783
825 /*784 /*
826 * Determine if Raven is setup and ready to go.785 * Determine if Raven is setup and ready to go.
827 *786 *
828 * @return {boolean}787 * @return {boolean}
829 */788 */
830 isSetup: function() {789 isSetup: function() {
831 if (!this._hasJSON) return false; // needs JSON support790 if (!this._hasJSON) return false; // needs JSON support
832 if (!this._globalServer) {791 if (!this._globalServer) {
833 if (!this.ravenNotConfiguredError) {792 if (!this.ravenNotConfiguredError) {
834 this.ravenNotConfiguredError = true;793 this.ravenNotConfiguredError = true;
835 this._logDebug('error', 'Error: Raven has not been configured.');794 this._logDebug('error', 'Error: Raven has not been configured.');
836 }795 }
837 return false;796 return false;
838 }797 }
839 return true;798 return true;
840 },799 },
841 800
842 afterLoad: function() {801 afterLoad: function() {
843 // TODO: remove window dependence?802 // TODO: remove window dependence?
844 803
845 // Attempt to initialize Raven on load804 // Attempt to initialize Raven on load
846 var RavenConfig = _window.RavenConfig;805 var RavenConfig = _window.RavenConfig;
847 if (RavenConfig) {806 if (RavenConfig) {
848 this.config(RavenConfig.dsn, RavenConfig.config).install();807 this.config(RavenConfig.dsn, RavenConfig.config).install();
849 }808 }
850 },809 },
851 810
852 showReportDialog: function(options) {811 showReportDialog: function(options) {
853 if (812 if (
854 !_document // doesn't work without a document (React native)813 !_document // doesn't work without a document (React native)
855 )814 )
856 return;815 return;
857 816
858 options = options || {};817 options = options || {};
859 818
860 var lastEventId = options.eventId || this.lastEventId();819 var lastEventId = options.eventId || this.lastEventId();
861 if (!lastEventId) {820 if (!lastEventId) {
862 throw new RavenConfigError('Missing eventId');821 throw new RavenConfigError('Missing eventId');
863 }822 }
864 823
865 var dsn = options.dsn || this._dsn;824 var dsn = options.dsn || this._dsn;
866 if (!dsn) {825 if (!dsn) {
867 throw new RavenConfigError('Missing DSN');826 throw new RavenConfigError('Missing DSN');
868 }827 }
869 828
870 var encode = encodeURIComponent;829 var encode = encodeURIComponent;
871 var qs = '';830 var qs = '';
872 qs += '?eventId=' + encode(lastEventId);831 qs += '?eventId=' + encode(lastEventId);
873 qs += '&dsn=' + encode(dsn);832 qs += '&dsn=' + encode(dsn);
874 833
875 var user = options.user || this._globalContext.user;834 var user = options.user || this._globalContext.user;
876 if (user) {835 if (user) {
877 if (user.name) qs += '&name=' + encode(user.name);836 if (user.name) qs += '&name=' + encode(user.name);
878 if (user.email) qs += '&email=' + encode(user.email);837 if (user.email) qs += '&email=' + encode(user.email);
879 }838 }
880 839
881 var globalServer = this._getGlobalServer(this._parseDSN(dsn));840 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 },
898 841
899 _triggerEvent: function(eventType, options) {842 var script = _document.createElement('script');
900 // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it843 script.async = true;
901 var evt, key;844 script.src = globalServer + '/api/embed/error-page/' + qs;
845 (_document.head || _document.body).appendChild(script);
846 },
902 847
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 },
904 857
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;
906 861
907 eventType =862 if (!this._hasDocument) return;
908 'raven' + eventType.substr(0, 1).toUpperCase() + eventType.substr(1);
909 863
910 if (_document.createEvent) {864 options = options || {};
911 evt = _document.createEvent('HTMLEvents');
912 evt.initEvent(eventType, true, true);
913 } else {
914 evt = _document.createEventObject();
915 evt.eventType = eventType;
916 }
917 865
918 for (key in options)866 eventType = 'raven' + eventType.substr(0, 1).toUpperCase() + eventType.substr(1);
919 if (hasKey(options, key)) {
920 evt[key] = options[key];
921 }
922 867
923 if (_document.createEvent) {868 if (_document.createEvent) {
924 // IE9 if standards869 evt = _document.createEvent('HTMLEvents');
925 _document.dispatchEvent(evt);870 evt.initEvent(eventType, true, true);
926 } else {871 } else {
927 // IE8 regardless of Quirks or Standards872 evt = _document.createEventObject();
928 // IE9 if quirks873 evt.eventType = eventType;
929 try {874 }
930 _document.fireEvent('on' + evt.eventType.toLowerCase(), evt);875
931 } catch (e) {876 for (key in options)
932 // Do nothing877 if (hasKey(options, key)) {
933 }878 evt[key] = options[key];
934 }879 }
935 },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 },
936 894
937 /**895 /**
938 * Wraps addEventListener to capture UI breadcrumbs896 * Wraps addEventListener to capture UI breadcrumbs
939 * @param evtName the event name (e.g. "click")897 * @param evtName the event name (e.g. "click")
940 * @returns {Function}898 * @returns {Function}
941 * @private899 * @private
942 */900 */
943 _breadcrumbEventHandler: function(evtName) {901 _breadcrumbEventHandler: function(evtName) {
944 var self = this;902 var self = this;
945 return function(evt) {903 return function(evt) {
946 // reset keypress timeout; e.g. triggering a 'click' after904 // reset keypress timeout; e.g. triggering a 'click' after
947 // a 'keypress' will reset the keypress debounce so that a new905 // a 'keypress' will reset the keypress debounce so that a new
948 // set of keypresses can be recorded906 // set of keypresses can be recorded
949 self._keypressTimeout = null;907 self._keypressTimeout = null;
950 908
951 // It's possible this handler might trigger multiple times for the same909 // It's possible this handler might trigger multiple times for the same
952 // event (e.g. event propagation through node ancestors). Ignore if we've910 // event (e.g. event propagation through node ancestors). Ignore if we've
953 // already captured the event.911 // already captured the event.
954 if (self._lastCapturedEvent === evt) return;912 if (self._lastCapturedEvent === evt) return;
955 913
956 self._lastCapturedEvent = evt;914 self._lastCapturedEvent = evt;
957 915
958 // try/catch both:916 // try/catch both:
959 // - accessing evt.target (see getsentry/raven-js#838, #768)917 // - accessing evt.target (see getsentry/raven-js#838, #768)
960 // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly918 // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly
961 // can throw an exception in some circumstances.919 // can throw an exception in some circumstances.
962 var target;920 var target;
963 try {921 try {
964 target = htmlTreeAsString(evt.target);922 target = htmlTreeAsString(evt.target);
965 } catch (e) {923 } catch (e) {
966 target = '<unknown>';924 target = '<unknown>';
967 }925 }
968 926
969 self.captureBreadcrumb({927 self.captureBreadcrumb({
970 category: 'ui.' + evtName, // e.g. ui.click, ui.input928 category: 'ui.' + evtName, // e.g. ui.click, ui.input
971 message: target929 message: target
972 });930 });
973 };931 };
974 },932 },
975 933
976 /**934 /**
977 * Wraps addEventListener to capture keypress UI events935 * Wraps addEventListener to capture keypress UI events
978 * @returns {Function}936 * @returns {Function}
979 * @private937 * @private
980 */938 */
981 _keypressEventHandler: function() {939 _keypressEventHandler: function() {
982 var self = this,940 var self = this,
983 debounceDuration = 1000; // milliseconds941 debounceDuration = 1000; // milliseconds
984 942
985 // TODO: if somehow user switches keypress target before943 // TODO: if somehow user switches keypress target before
986 // debounce timeout is triggered, we will only capture944 // debounce timeout is triggered, we will only capture
987 // a single breadcrumb from the FIRST target (acceptable?)945 // a single breadcrumb from the FIRST target (acceptable?)
988 return function(evt) {946 return function(evt) {
989 var target;947 var target;
990 try {948 try {
991 target = evt.target;949 target = evt.target;
992 } catch (e) {950 } catch (e) {
993 // just accessing event properties can throw an exception in some rare circumstances951 // just accessing event properties can throw an exception in some rare circumstances
994 // see: https://github.com/getsentry/raven-js/issues/838952 // see: https://github.com/getsentry/raven-js/issues/838
995 return;953 return;
996 }954 }
997 var tagName = target && target.tagName;955 var tagName = target && target.tagName;
998 956
999 // only consider keypress events on actual input elements957 // only consider keypress events on actual input elements
1000 // this will disregard keypresses targeting body (e.g. tabbing958 // this will disregard keypresses targeting body (e.g. tabbing
1001 // through elements, hotkeys, etc)959 // through elements, hotkeys, etc)
1002 if (960 if (
1003 !tagName ||961 !tagName ||
1004 (tagName !== 'INPUT' &&962 (tagName !== 'INPUT' && tagName !== 'TEXTAREA' && !target.isContentEditable)
1005 tagName !== 'TEXTAREA' &&963 )
1006 !target.isContentEditable)964 return;
1007 )965
1008 return;966 // record first keypress in a series, but ignore subsequent
1009 967 // keypresses until debounce clears
1010 // record first keypress in a series, but ignore subsequent968 var timeout = self._keypressTimeout;
1011 // keypresses until debounce clears969 if (!timeout) {
1012 var timeout = self._keypressTimeout;970 self._breadcrumbEventHandler('input')(evt);
1013 if (!timeout) {971 }
1014 self._breadcrumbEventHandler('input')(evt);972 clearTimeout(timeout);
1015 }973 self._keypressTimeout = setTimeout(function() {
1016 clearTimeout(timeout);974 self._keypressTimeout = null;
1017 self._keypressTimeout = setTimeout(function() {975 }, debounceDuration);
1018 self._keypressTimeout = null;976 };
1019 }, debounceDuration);977 },
1020 };978
1021 },979 /**
1022
1023 /**
1024 * Captures a breadcrumb of type "navigation", normalizing input URLs980 * Captures a breadcrumb of type "navigation", normalizing input URLs
1025 * @param to the originating URL981 * @param to the originating URL
1026 * @param from the target URL982 * @param from the target URL
1027 * @private983 * @private
1028 */984 */
1029 _captureUrlChange: function(from, to) {985 _captureUrlChange: function(from, to) {
1030 var parsedLoc = parseUrl(this._location.href);986 var parsedLoc = parseUrl(this._location.href);
1031 var parsedTo = parseUrl(to);987 var parsedTo = parseUrl(to);
1032 var parsedFrom = parseUrl(from);988 var parsedFrom = parseUrl(from);
1033 989
1034 // because onpopstate only tells you the "new" (to) value of location.href, and990 // 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 URL991 // not the previous (from) value, we need to track the value of the current URL
1036 // state ourselves992 // state ourselves
1037 this._lastHref = to;993 this._lastHref = to;
1038 994
1039 // Use only the path component of the URL if the URL matches the current995 // Use only the path component of the URL if the URL matches the current
1040 // document (almost all the time when using pushState)996 // document (almost all the time when using pushState)
1041 if (997 if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host)
1042 parsedLoc.protocol === parsedTo.protocol &&998 to = parsedTo.relative;
1043 parsedLoc.host === parsedTo.host999 if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host)
1044 )1000 from = parsedFrom.relative;
1045 to = parsedTo.relative;1001
1046 if (1002 this.captureBreadcrumb({
1047 parsedLoc.protocol === parsedFrom.protocol &&1003 category: 'navigation',
1048 parsedLoc.host === parsedFrom.host1004 data: {
1049 )1005 to: to,
1050 from = parsedFrom.relative;1006 from: from
1051 1007 }
1052 this.captureBreadcrumb({1008 });
1053 category: 'navigation',1009 },
1054 data: {1010
1055 to: to,1011 _patchFunctionToString: function() {
1056 from: from1012 var self = this;
1057 }1013 self._originalFunctionToString = Function.prototype.toString;
1058 });1014 // eslint-disable-next-line no-extend-native
1059 },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 },
1060 1029
1061 /**1030 /**
1062 * Wrap timer functions and event targets to catch errors and provide1031 * Wrap timer functions and event targets to catch errors and provide
1063 * better metadata.1032 * better metadata.
1064 */1033 */
1065 _instrumentTryCatch: function() {1034 _instrumentTryCatch: function() {
1066 var self = this;1035 var self = this;
1067 1036
1068 var wrappedBuiltIns = self._wrappedBuiltIns;1037 var wrappedBuiltIns = self._wrappedBuiltIns;
1069 1038
1070 function wrapTimeFn(orig) {1039 function wrapTimeFn(orig) {
1071 return function(fn, t) {1040 return function(fn, t) {
1072 // preserve arity1041 // preserve arity
1073 // Make a copy of the arguments to prevent deoptimization1042 // Make a copy of the arguments to prevent deoptimization
1074 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments1043 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
1075 var args = new Array(arguments.length);1044 var args = new Array(arguments.length);
1076 for (var i = 0; i < args.length; ++i) {1045 for (var i = 0; i < args.length; ++i) {
1077 args[i] = arguments[i];1046 args[i] = arguments[i];
1078 }1047 }
1079 var originalCallback = args[0];1048 var originalCallback = args[0];
1080 if (isFunction(originalCallback)) {1049 if (isFunction(originalCallback)) {
1081 args[0] = self.wrap(originalCallback);1050 args[0] = self.wrap(originalCallback);
1082 }1051 }
1083 1052
1084 // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it1053 // 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 we1054 // also supports only two arguments and doesn't care what this is, so we
1086 // can just call the original function directly.1055 // can just call the original function directly.
1087 if (orig.apply) {1056 if (orig.apply) {
1088 return orig.apply(this, args);1057 return orig.apply(this, args);
1089 } else {1058 } else {
1090 return orig(args[0], args[1]);1059 return orig(args[0], args[1]);
1091 }1060 }
1092 };1061 };
1093 }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 }
1094 1082
1095 var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;1083 // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`
1096 1084 // so that we don't have more than one wrapper function
1097 function wrapEventTarget(global) {1085 var before, clickHandler, keypressHandler;
1098 var proto = _window[global] && _window[global].prototype;1086
1099 if (1087 if (
1100 proto &&1088 autoBreadcrumbs &&
1101 proto.hasOwnProperty &&1089 autoBreadcrumbs.dom &&
1102 proto.hasOwnProperty('addEventListener')1090 (global === 'EventTarget' || global === 'Node')
1103 ) {1091 ) {
1104 fill(1092 // NOTE: generating multiple handlers per addEventListener invocation, should
1105 proto,1093 // revisit and verify we can just use one (almost certainly)
1106 'addEventListener',1094 clickHandler = self._breadcrumbEventHandler('click');
1107 function(orig) {1095 keypressHandler = self._keypressEventHandler();
1108 return function(evtName, fn, capture, secure) {1096 before = function(evt) {
1109 // preserve arity1097 // need to intercept every DOM event in `before` argument, in case that
1110 try {1098 // same wrapped method is re-used for different events (e.g. mousemove THEN click)
1111 if (fn && fn.handleEvent) {1099 // see #724
1112 fn.handleEvent = self.wrap(fn.handleEvent);1100 if (!evt) return;
1113 }1101
1114 } catch (err) {1102 var eventType;
1115 // can sometimes get 'Permission denied to access property "handle Event'1103 try {
1116 }1104 eventType = evt.type;
1117 1105 } catch (e) {
1118 // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`1106 // just accessing event properties can throw an exception in some rare circumstances
1119 // so that we don't have more than one wrapper function1107 // see: https://github.com/getsentry/raven-js/issues/838
1120 var before, clickHandler, keypressHandler;1108 return;
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 );
1176 }1109 }
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 }
1178 1142
1179 fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns);1143 fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns);
1180 fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns);1144 fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns);
1181 if (_window.requestAnimationFrame) {1145 if (_window.requestAnimationFrame) {
1182 fill(1146 fill(
1183 _window,1147 _window,
1184 'requestAnimationFrame',1148 'requestAnimationFrame',
1185 function(orig) {1149 function(orig) {
1186 return function(cb) {1150 return function(cb) {
1187 return orig(self.wrap(cb));1151 return orig(self.wrap(cb));
1188 };1152 };
1189 },1153 },
1190 wrappedBuiltIns1154 wrappedBuiltIns
1191 );1155 );
1192 }1156 }
1193 1157
1194 // event targets borrowed from bugsnag-js:1158 // event targets borrowed from bugsnag-js:
1195 // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L6661159 // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666
1196 var eventTargets = [1160 var eventTargets = [
1197 'EventTarget',1161 'EventTarget',
1198 'Window',1162 'Window',
1199 'Node',1163 'Node',
1200 'ApplicationCache',1164 'ApplicationCache',
1201 'AudioTrackList',1165 'AudioTrackList',
1202 'ChannelMergerNode',1166 'ChannelMergerNode',
1203 'CryptoOperation',1167 'CryptoOperation',
1204 'EventSource',1168 'EventSource',
1205 'FileReader',1169 'FileReader',
1206 'HTMLUnknownElement',1170 'HTMLUnknownElement',
1207 'IDBDatabase',1171 'IDBDatabase',
1208 'IDBRequest',1172 'IDBRequest',
1209 'IDBTransaction',1173 'IDBTransaction',
1210 'KeyOperation',1174 'KeyOperation',
1211 'MediaController',1175 'MediaController',
1212 'MessagePort',1176 'MessagePort',
1213 'ModalWindow',1177 'ModalWindow',
1214 'Notification',1178 'Notification',
1215 'SVGElementInstance',1179 'SVGElementInstance',
1216 'Screen',1180 'Screen',
1217 'TextTrack',1181 'TextTrack',
1218 'TextTrackCue',1182 'TextTrackCue',
1219 'TextTrackList',1183 'TextTrackList',
1220 'WebSocket',1184 'WebSocket',
1221 'WebSocketWorker',1185 'WebSocketWorker',
1222 'Worker',1186 'Worker',
1223 'XMLHttpRequest',1187 'XMLHttpRequest',
1224 'XMLHttpRequestEventTarget',1188 'XMLHttpRequestEventTarget',
1225 'XMLHttpRequestUpload'1189 'XMLHttpRequestUpload'
1226 ];1190 ];
1227 for (var i = 0; i < eventTargets.length; i++) {1191 for (var i = 0; i < eventTargets.length; i++) {
1228 wrapEventTarget(eventTargets[i]);1192 wrapEventTarget(eventTargets[i]);
1229 }1193 }
1230 },1194 },
1231 1195
1232 /**1196 /**
1233 * Instrument browser built-ins w/ breadcrumb capturing1197 * Instrument browser built-ins w/ breadcrumb capturing
1234 * - XMLHttpRequests1198 * - XMLHttpRequests
1235 * - DOM interactions (click/typing)1199 * - DOM interactions (click/typing)
@@ -1238,546 +1202,515 @@ · 2 unmodified lines
1238 *1202 *
1239 * Can be disabled or individually configured via the `autoBreadcrumbs` config option1203 * Can be disabled or individually configured via the `autoBreadcrumbs` config option
1240 */1204 */
1241 _instrumentBreadcrumbs: function() {1205 _instrumentBreadcrumbs: function() {
1242 var self = this;1206 var self = this;
1243 var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;1207 var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
1244 1208
1245 var wrappedBuiltIns = self._wrappedBuiltIns;1209 var wrappedBuiltIns = self._wrappedBuiltIns;
1246 1210
1247 function wrapProp(prop, xhr) {1211 function wrapProp(prop, xhr) {
1248 if (prop in xhr && isFunction(xhr[prop])) {1212 if (prop in xhr && isFunction(xhr[prop])) {
1249 fill(xhr, prop, function(orig) {1213 fill(xhr, prop, function(orig) {
1250 return self.wrap(orig);1214 return self.wrap(orig);
1251 }); // intentionally don't track filled methods on XHR instances1215 }); // intentionally don't track filled methods on XHR instances
1252 }1216 }
1253 }1217 }
1254 1218
1255 if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) {1219 if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) {
1256 var xhrproto = XMLHttpRequest.prototype;1220 var xhrproto = XMLHttpRequest.prototype;
1257 fill(1221 fill(
1258 xhrproto,1222 xhrproto,
1259 'open',1223 'open',
1260 function(origOpen) {1224 function(origOpen) {
1261 return function(method, url) {1225 return function(method, url) {
1262 // preserve arity1226 // preserve arity
1263 1227
1264 // if Sentry key appears in URL, don't capture1228 // if Sentry key appears in URL, don't capture
1265 if (isString(url) && url.indexOf(self._globalKey) === -1) {1229 if (isString(url) && url.indexOf(self._globalKey) === -1) {
1266 this.__raven_xhr = {1230 this.__raven_xhr = {
1267 method: method,1231 method: method,
1268 url: url,1232 url: url,
1269 status_code: null1233 status_code: null
1270 };1234 };
1271 }1235 }
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 }
1337 1236
1338 if (autoBreadcrumbs.xhr && 'fetch' in _window) {1237 return origOpen.apply(this, arguments);
1339 fill(1238 };
1340 _window,1239 },
1341 'fetch',1240 wrappedBuiltIns
1342 function(origFetch) {1241 );
1343 return function(fn, t) {1242
1344 // preserve arity1243 fill(
1345 // Make a copy of the arguments to prevent deoptimization1244 xhrproto,
1346 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments1245 'send',
1347 var args = new Array(arguments.length);1246 function(origSend) {
1348 for (var i = 0; i < args.length; ++i) {1247 return function(data) {
1349 args[i] = arguments[i];1248 // preserve arity
1350 }1249 var xhr = this;
1351 1250
1352 var fetchInput = args[0];1251 function onreadystatechangeHandler() {
1353 var method = 'GET';1252 if (xhr.__raven_xhr && xhr.readyState === 4) {
1354 var url;1253 try {
1355 1254 // touching statusCode in some platforms throws
1356 if (typeof fetchInput === 'string') {1255 // an exception
1357 url = fetchInput;1256 xhr.__raven_xhr.status_code = xhr.status;
1358 } else {1257 } catch (e) {
1359 url = fetchInput.url;1258 /* do nothing */
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 );
1390 }1259 }
1391 1260
1392 // Capture breadcrumbs from any click that is unhandled / bubbled up all the way1261 self.captureBreadcrumb({
1393 // to the document. Do this before we instrument addEventListener.1262 type: 'http',
1394 if (autoBreadcrumbs.dom && this._hasDocument) {1263 category: 'xhr',
1395 if (_document.addEventListener) {1264 data: xhr.__raven_xhr
1396 _document.addEventListener(1265 });
1397 'click',1266 }
1398 self._breadcrumbEventHandler('click'),1267 }
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 }
1415 1268
1416 // record navigation (URL) changes1269 var props = ['onload', 'onerror', 'onprogress'];
1417 // NOTE: in Chrome App environment, touching history.pushState, *even inside1270 for (var j = 0; j < props.length; j++) {
1418 // a try/catch block*, will cause Chrome to output an error to console.error1271 wrapProp(props[j], xhr);
1419 // borrowed from: https://github.com/angular/angular.js/pull/13945/files1272 }
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 }
1457 1273
1458 if (autoBreadcrumbs.console && 'console' in _window && console.log) {1274 if ('onreadystatechange' in xhr && isFunction(xhr.onreadystatechange)) {
1459 // console1275 fill(
1460 var consoleMethodCallback = function(msg, data) {1276 xhr,
1461 self.captureBreadcrumb({1277 'onreadystatechange',
1462 message: msg,1278 function(orig) {
1463 level: data.level,1279 return self.wrap(orig, undefined, onreadystatechangeHandler);
1464 category: 'console'1280 } /* intentionally don't track this instrumentation */
1465 });1281 );
1466 };1282 } else {
1467 1283 // if onreadystatechange wasn't actually set by the page on this xhr, we
1468 each(['debug', 'info', 'warn', 'error', 'log'], function(_, level) {1284 // are free to set our own and capture the breadcrumb
1469 wrapConsoleMethod(console, level, consoleMethodCallback);1285 xhr.onreadystatechange = onreadystatechangeHandler;
1470 });1286 }
1471 }1287
1472 },1288 return origSend.apply(this, arguments);
1289 };
1290 },
1291 wrappedBuiltIns
1292 );
1293 }
1473 1294
1474 _restoreBuiltIns: function() {1295 if (autoBreadcrumbs.xhr && 'fetch' in _window) {
1475 // restore any wrapped builtins1296 fill(
1476 var builtin;1297 _window,
1477 while (this._wrappedBuiltIns.length) {1298 'fetch',
1478 builtin = this._wrappedBuiltIns.shift();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 }
1479 1308
1480 var obj = builtin[0],1309 var fetchInput = args[0];
1481 name = builtin[1],1310 var method = 'GET';
1482 orig = builtin[2];1311 var url;
1483 1312
1484 obj[name] = orig;1313 if (typeof fetchInput === 'string') {
1485 }1314 url = fetchInput;
1486 },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 }
1487 1323
1488 _drainPlugins: function() {1324 if (args[1] && args[1].method) {
1489 var self = this;1325 method = args[1].method;
1326 }
1490 1327
1491 // FIX ME TODO1328 var fetchData = {
1492 each(this._plugins, function(_, plugin) {1329 method: method,
1493 var installer = plugin[0];1330 url: url,
1494 var args = plugin[1];1331 status_code: null
1495 installer.apply(self, [self].concat(args));1332 };
1496 });
1497 },
1498 1333
1499 _parseDSN: function(str) {1334 self.captureBreadcrumb({
1500 var m = dsnPattern.exec(str),1335 type: 'http',
1501 dsn = {},1336 category: 'fetch',
1502 i = 7;1337 data: fetchData
1338 });
1503 1339
1504 try {1340 return origFetch.apply(this, args).then(function(response) {
1505 while (i--) dsn[dsnKeys[i]] = m[i] || '';1341 fetchData.status_code = response.status;
1506 } catch (e) {
1507 throw new RavenConfigError('Invalid DSN: ' + str);
1508 }
1509 1342
1510 if (dsn.pass && !this._globalOptions.allowSecretKey) {1343 return response;
1511 throw new RavenConfigError(1344 });
1512 'Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'1345 };
1513 );1346 },
1514 }1347 wrappedBuiltIns
1348 );
1349 }
1515 1350
1516 return dsn;1351 // Capture breadcrumbs from any click that is unhandled / bubbled up all the way
1517 },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 }
1518 1363
1519 _getGlobalServer: function(uri) {1364 // record navigation (URL) changes
1520 // assemble the endpoint from the uri pieces1365 // NOTE: in Chrome App environment, touching history.pushState, *even inside
1521 var globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : '');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 }
1522 1398
1523 if (uri.protocol) {1399 return origHistFunction.apply(this, arguments);
1524 globalServer = uri.protocol + ':' + globalServer;1400 };
1525 }1401 };
1526 return globalServer;
1527 },
1528 1402
1529 _handleOnErrorStackInfo: function() {1403 fill(history, 'pushState', historyReplacementFunction, wrappedBuiltIns);
1530 // if we are intentionally ignoring errors via onerror, bail out1404 fill(history, 'replaceState', historyReplacementFunction, wrappedBuiltIns);
1531 if (!this._ignoreOnError) {1405 }
1532 this._handleStackInfo.apply(this, arguments);
1533 }
1534 },
1535 1406
1536 _handleStackInfo: function(stackInfo, options) {1407 if (autoBreadcrumbs.console && 'console' in _window && console.log) {
1537 var frames = this._prepareFrames(stackInfo, options);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 },
1538 1422
1539 this._triggerEvent('handle', {1423 _restoreBuiltIns: function() {
1540 stackInfo: stackInfo,1424 // restore any wrapped builtins
1541 options: options1425 var builtin;
1542 });1426 while (this._wrappedBuiltIns.length) {
1427 builtin = this._wrappedBuiltIns.shift();
1543 1428
1544 this._processException(1429 var obj = builtin[0],
1545 stackInfo.name,1430 name = builtin[1],
1546 stackInfo.message,1431 orig = builtin[2];
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 };
1588 1432
1589 // Case when we don't have any information about the error1433 obj[name] = orig;
1590 // E.g. throwing a string or raw object, instead of an `Error` in Firefox1434 }
1591 // Generating synthetic error doesn't add any value here1435 },
1592 //1436
1593 // We should probably somehow let a user know that they should fix their code1437 _drainPlugins: function() {
1594 if (!frame.url) {1438 var self = this;
1595 normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler1439
1596 }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 }
1597 1458
1598 normalized.in_app = !// determine if an exception came from outside of our app1459 if (dsn.pass && !this._globalOptions.allowSecretKey) {
1599 // first we check the global includePaths list.1460 throw new RavenConfigError(
1600 (1461 'Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'
1601 (!!this._globalOptions.includePaths.test &&1462 );
1602 !this._globalOptions.includePaths.test(normalized.filename)) ||1463 }
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 }
1647 1464
1648 if (1465 return dsn;
1649 !!this._globalOptions.ignoreUrls.test &&1466 },
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 }
1691 1467
1692 var request = data.request;1468 _getGlobalServer: function(uri) {
1693 if (request) {1469 // assemble the endpoint from the uri pieces
1694 if (request.url) {1470 var globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : '');
1695 request.url = truncate(request.url, this._globalOptions.maxUrlLength);1471
1696 }1472 if (uri.protocol) {
1697 if (request.Referer) {1473 globalServer = uri.protocol + ':' + globalServer;
1698 request.Referer = truncate(1474 }
1699 request.Referer,1475 return globalServer;
1700 this._globalOptions.maxUrlLength1476 },
1701 );1477
1702 }1478 _handleOnErrorStackInfo: function() {
1703 }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 }
1704 1593
1705 if (data.breadcrumbs && data.breadcrumbs.values)1594 if (
1706 this._trimBreadcrumbs(data.breadcrumbs);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 }
1707 1633
1708 return data;1634 var request = data.request;
1709 },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 }
1710 1643
1711 /**1644 if (data.breadcrumbs && data.breadcrumbs.values)
1645 this._trimBreadcrumbs(data.breadcrumbs);
1646
1647 return data;
1648 },
1649
1650 /**
1712 * Truncate breadcrumb values (right now just URLs)1651 * Truncate breadcrumb values (right now just URLs)
1713 */1652 */
1714 _trimBreadcrumbs: function(breadcrumbs) {1653 _trimBreadcrumbs: function(breadcrumbs) {
1715 // known breadcrumb properties with urls1654 // known breadcrumb properties with urls
1716 // TODO: also consider arbitrary prop values that start with (https?)?://1655 // TODO: also consider arbitrary prop values that start with (https?)?://
1717 var urlProps = ['to', 'from', 'url'],1656 var urlProps = ['to', 'from', 'url'],
1718 urlProp,1657 urlProp,
1719 crumb,1658 crumb,
1720 data;1659 data;
1721 1660
1722 for (var i = 0; i < breadcrumbs.values.length; ++i) {1661 for (var i = 0; i < breadcrumbs.values.length; ++i) {
1723 crumb = breadcrumbs.values[i];1662 crumb = breadcrumbs.values[i];
1724 if (1663 if (
1725 !crumb.hasOwnProperty('data') ||1664 !crumb.hasOwnProperty('data') ||
1726 !isObject(crumb.data) ||1665 !isObject(crumb.data) ||
1727 objectFrozen(crumb.data)1666 objectFrozen(crumb.data)
1728 )1667 )
1729 continue;1668 continue;
1730 1669
1731 data = objectMerge({}, crumb.data);1670 data = objectMerge({}, crumb.data);
1732 for (var j = 0; j < urlProps.length; ++j) {1671 for (var j = 0; j < urlProps.length; ++j) {
1733 urlProp = urlProps[j];1672 urlProp = urlProps[j];
1734 if (data.hasOwnProperty(urlProp) && data[urlProp]) {1673 if (data.hasOwnProperty(urlProp) && data[urlProp]) {
1735 data[urlProp] = truncate(1674 data[urlProp] = truncate(data[urlProp], this._globalOptions.maxUrlLength);
1736 data[urlProp],1675 }
1737 this._globalOptions.maxUrlLength1676 }
1738 );1677 breadcrumbs.values[i].data = data;
1739 }1678 }
1740 }1679 },
1741 breadcrumbs.values[i].data = data;
1742 }
1743 },
1744 1680
1745 _getHttpData: function() {1681 _getHttpData: function() {
1746 if (!this._hasNavigator && !this._hasDocument) return;1682 if (!this._hasNavigator && !this._hasDocument) return;
1747 var httpData = {};1683 var httpData = {};
1748 1684
1749 if (this._hasNavigator && _navigator.userAgent) {1685 if (this._hasNavigator && _navigator.userAgent) {
1750 httpData.headers = {1686 httpData.headers = {
1751 'User-Agent': navigator.userAgent1687 'User-Agent': navigator.userAgent
1752 };1688 };
1753 }1689 }
1754 1690
1755 if (this._hasDocument) {1691 if (this._hasDocument) {
1756 if (_document.location && _document.location.href) {1692 if (_document.location && _document.location.href) {
1757 httpData.url = _document.location.href;1693 httpData.url = _document.location.href;
1758 }1694 }
1759 if (_document.referrer) {1695 if (_document.referrer) {
1760 if (!httpData.headers) httpData.headers = {};1696 if (!httpData.headers) httpData.headers = {};
1761 httpData.headers.Referer = _document.referrer;1697 httpData.headers.Referer = _document.referrer;
1762 }1698 }
1763 }1699 }
1764 1700
1765 return httpData;1701 return httpData;
1766 },1702 },
1767 1703
1768 _resetBackoff: function() {1704 _resetBackoff: function() {
1769 this._backoffDuration = 0;1705 this._backoffDuration = 0;
1770 this._backoffStart = null;1706 this._backoffStart = null;
1771 },1707 },
1772 1708
1773 _shouldBackoff: function() {1709 _shouldBackoff: function() {
1774 return (1710 return this._backoffDuration && now() - this._backoffStart < this._backoffDuration;
1775 this._backoffDuration &&1711 },
1776 now() - this._backoffStart < this._backoffDuration
1777 );
1778 },
1779 1712
1780 /**1713 /**
1781 * Returns true if the in-process data payload matches the signature1714 * Returns true if the in-process data payload matches the signature
1782 * of the previously-sent data1715 * of the previously-sent data
1783 *1716 *
@@ -1786,362 +1719,449 @@ · 2 unmodified lines
1786 * other old browsers). This can take the form of an "exception"1719 * other old browsers). This can take the form of an "exception"
1787 * data object with a single frame (derived from the onerror args).1720 * data object with a single frame (derived from the onerror args).
1788 */1721 */
1789 _isRepeatData: function(current) {1722 _isRepeatData: function(current) {
1790 var last = this._lastData;1723 var last = this._lastData;
1791 1724
1792 if (1725 if (
1793 !last ||1726 !last ||
1794 current.message !== last.message || // defined for captureMessage1727 current.message !== last.message || // defined for captureMessage
1795 current.culprit !== last.culprit // defined for captureException/onerror1728 current.culprit !== last.culprit // defined for captureException/onerror
1796 )1729 )
1797 return false;1730 return false;
1798 1731
1799 // Stacktrace interface (i.e. from captureMessage)1732 // Stacktrace interface (i.e. from captureMessage)
1800 if (current.stacktrace || last.stacktrace) {1733 if (current.stacktrace || last.stacktrace) {
1801 return isSameStacktrace(current.stacktrace, last.stacktrace);1734 return isSameStacktrace(current.stacktrace, last.stacktrace);
1802 } else if (current.exception || last.exception) {1735 } else if (current.exception || last.exception) {
1803 // Exception interface (i.e. from captureException/onerror)1736 // Exception interface (i.e. from captureException/onerror)
1804 return isSameException(current.exception, last.exception);1737 return isSameException(current.exception, last.exception);
1805 }1738 }
1806 1739
1807 return true;1740 return true;
1808 },1741 },
1809 1742
1810 _setBackoffState: function(request) {1743 _setBackoffState: function(request) {
1811 // If we are already in a backoff state, don't change anything1744 // If we are already in a backoff state, don't change anything
1812 if (this._shouldBackoff()) {1745 if (this._shouldBackoff()) {
1813 return;1746 return;
1814 }1747 }
1815 1748
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 }
1817 1765
1818 // 400 - project_id doesn't exist or some other fatal1766 this._backoffDuration = retry
1819 // 401 - invalid/revoked dsn1767 ? // If Sentry server returned a Retry-After value, use it
1820 // 429 - too many requests1768 retry
1821 if (!(status === 400 || status === 401 || status === 429)) return;1769 : // Otherwise, double the last backoff duration (starts at 1 sec)
1770 this._backoffDuration * 2 || 1000;
1822 1771
1823 var retry;1772 this._backoffStart = now();
1824 try {1773 },
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 }
1832 1774
1833 this._backoffDuration = retry1775 _send: function(data) {
1834 ? // If Sentry server returned a Retry-After value, use it1776 var globalOptions = this._globalOptions;
1835 retry
1836 : // Otherwise, double the last backoff duration (starts at 1 sec)
1837 this._backoffDuration * 2 || 1000;
1838 1777
1839 this._backoffStart = now();1778 var baseData = {
1840 },1779 project: this._globalProject,
1780 logger: globalOptions.logger,
1781 platform: 'javascript'
1782 },
1783 httpData = this._getHttpData();
1841 1784
1842 _send: function(data) {1785 if (httpData) {
1843 var globalOptions = this._globalOptions;1786 baseData.request = httpData;
1787 }
1844 1788
1845 var baseData = {1789 // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload
1846 project: this._globalProject,1790 if (data.trimHeadFrames) delete data.trimHeadFrames;
1847 logger: globalOptions.logger,
1848 platform: 'javascript'
1849 },
1850 httpData = this._getHttpData();
1851 1791
1852 if (httpData) {1792 data = objectMerge(baseData, data);
1853 baseData.request = httpData;
1854 }
1855 1793
1856 // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload1794 // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge
1857 if (data.trimHeadFrames) delete data.trimHeadFrames;1795 data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags);
1858 1796 data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra);
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 }
1881 1797
1882 // If there are no tags/extra, strip the key from the payload alltogther.1798 // Send along our own collected metadata with extra
1883 if (isEmptyObject(data.tags)) delete data.tags;1799 data.extra['session:duration'] = now() - this._startTime;
1884 1800
1885 if (this._globalContext.user) {1801 if (this._breadcrumbs && this._breadcrumbs.length > 0) {
1886 // sentry.interfaces.User1802 // intentionally make shallow copy so that additions
1887 data.user = this._globalContext.user;1803 // to breadcrumbs aren't accidentally sent in this request
1888 }1804 data.breadcrumbs = {
1805 values: [].slice.call(this._breadcrumbs, 0)
1806 };
1807 }
1889 1808
1890 // Include the environment if it's defined in globalOptions1809 // If there are no tags/extra, strip the key from the payload alltogther.
1891 if (globalOptions.environment)1810 if (isEmptyObject(data.tags)) delete data.tags;
1892 data.environment = globalOptions.environment;
1893 1811
1894 // Include the release if it's defined in globalOptions1812 if (this._globalContext.user) {
1895 if (globalOptions.release) data.release = globalOptions.release;1813 // sentry.interfaces.User
1814 data.user = this._globalContext.user;
1815 }
1896 1816
1897 // Include server_name if it's defined in globalOptions1817 // Include the environment if it's defined in globalOptions
1898 if (globalOptions.serverName) data.server_name = globalOptions.serverName;1818 if (globalOptions.environment) data.environment = globalOptions.environment;
1899 1819
1900 if (isFunction(globalOptions.dataCallback)) {1820 // Include the release if it's defined in globalOptions
1901 data = globalOptions.dataCallback(data) || data;1821 if (globalOptions.release) data.release = globalOptions.release;
1902 }
1903 1822
1904 // Why??????????1823 // Include server_name if it's defined in globalOptions
1905 if (!data || isEmptyObject(data)) {1824 if (globalOptions.serverName) data.server_name = globalOptions.serverName;
1906 return;
1907 }
1908 1825
1909 // Check if the request should be filtered or not1826 if (isFunction(globalOptions.dataCallback)) {
1910 if (1827 data = globalOptions.dataCallback(data) || data;
1911 isFunction(globalOptions.shouldSendCallback) &&1828 }
1912 !globalOptions.shouldSendCallback(data)
1913 ) {
1914 return;
1915 }
1916 1829
1917 // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests),1830 // Why??????????
1918 // so drop requests until "cool-off" period has elapsed.1831 if (!data || isEmptyObject(data)) {
1919 if (this._shouldBackoff()) {1832 return;
1920 this._logDebug('warn', 'Raven dropped error due to backoff: ', data);1833 }
1921 return;
1922 }
1923 1834
1924 if (typeof globalOptions.sampleRate === 'number') {1835 // Check if the request should be filtered or not
1925 if (Math.random() < globalOptions.sampleRate) {1836 if (
1926 this._sendProcessedPayload(data);1837 isFunction(globalOptions.shouldSendCallback) &&
1927 }1838 !globalOptions.shouldSendCallback(data)
1928 } else {1839 ) {
1929 this._sendProcessedPayload(data);1840 return;
1930 }1841 }
1931 },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 }
1932 1849
1933 _getUuid: function() {1850 if (typeof globalOptions.sampleRate === 'number') {
1934 return uuid4();1851 if (Math.random() < globalOptions.sampleRate) {
1935 },1852 this._sendProcessedPayload(data);
1853 }
1854 } else {
1855 this._sendProcessedPayload(data);
1856 }
1857 },
1936 1858
1937 _sendProcessedPayload: function(data, callback) {1859 _getUuid: function() {
1938 var self = this;1860 return uuid4();
1939 var globalOptions = this._globalOptions;1861 },
1940 1862
1941 if (!this.isSetup()) return;1863 _sendProcessedPayload: function(data, callback) {
1864 var self = this;
1865 var globalOptions = this._globalOptions;
1942 1866
1943 // Try and clean up the packet before sending by truncating long values1867 if (!this.isSetup()) return;
1944 data = this._trimPacket(data);
1945 1868
1946 // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback,1869 // Try and clean up the packet before sending by truncating long values
1947 // but this would require copying an un-truncated copy of the data packet, which can be1870 data = this._trimPacket(data);
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 }
1953 1871
1954 // Send along an event_id if not explicitly passed.1872 // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback,
1955 // This event_id can be used to reference the error within Sentry itself.1873 // but this would require copying an un-truncated copy of the data packet, which can be
1956 // Set lastEventId after we know the error should actually be sent1874 // arbitrarily deep (extra_data) -- could be worthwhile? will revisit
1957 this._lastEventId = data.event_id || (data.event_id = this._getUuid());1875 if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) {
1876 this._logDebug('warn', 'Raven dropped repeat event: ', data);
1877 return;
1878 }
1958 1879
1959 // Store outbound payload after trim1880 // Send along an event_id if not explicitly passed.
1960 this._lastData = data;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());
1961 1884
1962 this._logDebug('debug', 'Raven about to send:', data);1885 // Store outbound payload after trim
1886 this._lastData = data;
1963 1887
1964 var auth = {1888 this._logDebug('debug', 'Raven about to send:', data);
1965 sentry_version: '7',
1966 sentry_client: 'raven-js/' + this.VERSION,
1967 sentry_key: this._globalKey
1968 };
1969 1889
1970 if (this._globalSecret) {1890 var auth = {
1971 auth.sentry_secret = this._globalSecret;1891 sentry_version: '7',
1972 }1892 sentry_client: 'raven-js/' + this.VERSION,
1893 sentry_key: this._globalKey
1894 };
1973 1895
1974 var exception = data.exception && data.exception.values[0];1896 if (this._globalSecret) {
1975 this.captureBreadcrumb({1897 auth.sentry_secret = this._globalSecret;
1976 category: 'sentry',1898 }
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 });
1983 1899
1984 var url = this._globalEndpoint;1900 var exception = data.exception && data.exception.values[0];
1985 (globalOptions.transport || this._makeRequest).call(this, {1901
1986 url: url,1902 // only capture 'sentry' breadcrumb is autoBreadcrumbs is truthy
1987 auth: auth,1903 if (
1988 data: data,1904 this._globalOptions.autoBreadcrumbs &&
1989 options: globalOptions,1905 this._globalOptions.autoBreadcrumbs.sentry
1990 onSuccess: function success() {1906 ) {
1991 self._resetBackoff();1907 this.captureBreadcrumb({
1992 1908 category: 'sentry',
1993 self._triggerEvent('success', {1909 message: exception
1994 data: data,1910 ? (exception.type ? exception.type + ': ' : '') + exception.value
1995 src: url1911 : data.message,
1996 });1912 event_id: data.event_id,
1997 callback && callback();1913 level: data.level || 'error' // presume error unless specified
1998 },1914 });
1999 onError: function failure(error) {1915 }
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 }
2060 1916
2061 // NOTE: auth is intentionally sent as part of query string (NOT as custom1917 var url = this._globalEndpoint;
2062 // HTTP header) so as to avoid preflight CORS requests1918 (globalOptions.transport || this._makeRequest).call(this, {
2063 request.open('POST', url + '?' + urlencode(opts.auth));1919 url: url,
2064 request.send(stringify(opts.data));1920 auth: auth,
2065 },1921 data: data,
2066 1922 options: globalOptions,
2067 _logDebug: function(level) {1923 onSuccess: function success() {
2068 if (this._originalConsoleMethods[level] && this.debug) {1924 self._resetBackoff();
2069 // In IE<10 console methods do not have their own 'apply' method1925
2070 Function.prototype.apply.call(1926 self._triggerEvent('success', {
2071 this._originalConsoleMethods[level],1927 data: data,
2072 this._originalConsole,1928 src: url
2073 [].slice.call(arguments, 1)1929 });
2074 );1930 callback && callback();
2075 }1931 },
2076 },1932 onError: function failure(error) {
2077 1933 self._logDebug('error', 'Raven transport failed to send: ', error);
2078 _mergeContext: function(key, context) {1934
2079 if (isUndefined(context)) {1935 if (error.request) {
2080 delete this._globalContext[key];1936 self._setBackoffState(error.request);
2081 } else {1937 }
2082 this._globalContext[key] = objectMerge(1938
2083 this._globalContext[key] || {},1939 self._triggerEvent('failure', {
2084 context1940 data: data,
2085 );1941 src: url
2086 }1942 });
2087 }1943 error = error || new Error('Raven send failed (no additional details provided)');
2088 };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
2018Raven.prototype.setUser = Raven.prototype.setUserContext;
2019Raven.prototype.setReleaseContext = Raven.prototype.setRelease;
2020
2021module.exports = Raven;
2089 2022
2090 /*------------------------------------------------2023}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
2091 * utils2024},{"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
2032var 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)
2035var _window =
2036 typeof window !== 'undefined'
2037 ? window
2038 : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2039var _Raven = _window.Raven;
2040
2041var 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.
2092 *2046 *
2093 * conditionally exported for test via Raven.utils2047 * @return {Raven}
2094 =================================================
2095 */2048 */
2096 var objectPrototype = Object.prototype;2049Raven.noConflict = function() {
2050 _window.Raven = _Raven;
2051 return Raven;
2052};
2053
2054Raven.afterLoad();
2055
2056module.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){
2061var _window =
2062 typeof window !== 'undefined'
2063 ? window
2064 : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2065
2066function 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
2072function 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}
2097 2084
2098 function isUndefined(what) {2085function isErrorEvent(value) {
2099 return what === void 0;2086 return supportsErrorEvent() && {}.toString.call(value) === '[object ErrorEvent]';
2100 }2087}
2101 2088
2102 function isFunction(what) {2089function isUndefined(what) {
2103 return typeof what === 'function';2090 return what === void 0;
2104 }2091}
2105 2092
2106 function isString(what) {2093function isFunction(what) {
2107 return objectPrototype.toString.call(what) === '[object String]';2094 return typeof what === 'function';
2108 }2095}
2109 2096
2110 function isEmptyObject(what) {2097function isString(what) {
2111 for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars2098 return Object.prototype.toString.call(what) === '[object String]';
2112 return true;2099}
2113 }
2114 2100
2115 function each(obj, callback) {2101function isArray(what) {
2116 var i, j;2102 return Object.prototype.toString.call(what) === '[object Array]';
2103}
2117 2104
2118 if (isUndefined(obj.length)) {2105function isEmptyObject(what) {
2119 for (i in obj) {2106 for (var _ in what) {
2120 if (hasKey(obj, i)) {2107 if (what.hasOwnProperty(_)) {
2121 callback.call(null, i, obj[i]);2108 return false;
2122 }2109 }
2123 }2110 }
2124 } else {2111 return true;
2125 j = obj.length;2112}
2126 if (j) {2113
2127 for (i = 0; i < j; i++) {2114function supportsErrorEvent() {
2128 callback.call(null, i, obj[i]);2115 try {
2129 }2116 new ErrorEvent(''); // eslint-disable-line no-new
2130 }2117 return true;
2131 }2118 } catch (e) {
2132 }2119 return false;
2120 }
2121}
2133 2122
2134 function objectMerge(obj1, obj2) {2123function wrappedCallback(callback) {
2135 if (!obj2) {2124 function dataCallback(data, original) {
2136 return obj1;2125 var normalizedData = callback(data) || data;
2137 }2126 if (original) {
2138 each(obj2, function(key, value) {2127 return original(normalizedData) || normalizedData;
2139 obj1[key] = value;2128 }
2140 });2129 return normalizedData;
2141 return obj1;2130 }
2142 }2131
2132 return dataCallback;
2133}
2134
2135function 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
2154function 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}
2143 2163
2144 /**2164/**
2145 * This function is only used for react-native.2165 * This function is only used for react-native.
2146 * react-native freezes object that have already been sent over the2166 * react-native freezes object that have already been sent over the
2147 * js bridge. We need this function in order to check if the object is frozen.2167 * js bridge. We need this function in order to check if the object is frozen.
@@ -2149,398 +2169,298 @@ · 1 unmodified line
2149 * supported because it's not relevant for other "platforms". See related issue:2169 * supported because it's not relevant for other "platforms". See related issue:
2150 * https://github.com/getsentry/react-native-sentry/issues/572170 * https://github.com/getsentry/react-native-sentry/issues/57
2151 */2171 */
2152 function objectFrozen(obj) {2172function objectFrozen(obj) {
2153 if (!Object.isFrozen) {2173 if (!Object.isFrozen) {
2154 return false;2174 return false;
2155 }2175 }
2156 return Object.isFrozen(obj);2176 return Object.isFrozen(obj);
2157 }2177}
2158 2178
2159 function truncate(str, max) {2179function truncate(str, max) {
2160 return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';2180 return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';
2161 }2181}
2162 2182
2163 /**2183/**
2164 * hasKey, a better form of hasOwnProperty2184 * hasKey, a better form of hasOwnProperty
2165 * Example: hasKey(MainHostObject, property) === true/false2185 * Example: hasKey(MainHostObject, property) === true/false
2166 *2186 *
2167 * @param {Object} host object to check property2187 * @param {Object} host object to check property
2168 * @param {string} key to check2188 * @param {string} key to check
2169 */2189 */
2170 function hasKey(object, key) {2190function hasKey(object, key) {
2171 return objectPrototype.hasOwnProperty.call(object, key);2191 return Object.prototype.hasOwnProperty.call(object, key);
2172 }2192}
2173 2193
2174 function joinRegExp(patterns) {2194function joinRegExp(patterns) {
2175 // Combine an array of regular expressions and strings into one large regexp2195 // Combine an array of regular expressions and strings into one large regexp
2176 // Be mad.2196 // Be mad.
2177 var sources = [],2197 var sources = [],
2178 i = 0,2198 i = 0,
2179 len = patterns.length,2199 len = patterns.length,
2180 pattern;2200 pattern;
2181 2201
2182 for (; i < len; i++) {2202 for (; i < len; i++) {
2183 pattern = patterns[i];2203 pattern = patterns[i];
2184 if (isString(pattern)) {2204 if (isString(pattern)) {
2185 // If it's a string, we need to escape it2205 // If it's a string, we need to escape it
2186 // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions2206 // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
2187 sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));2207 sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
2188 } else if (pattern && pattern.source) {2208 } else if (pattern && pattern.source) {
2189 // If it's a regexp already, we want to extract the source2209 // If it's a regexp already, we want to extract the source
2190 sources.push(pattern.source);2210 sources.push(pattern.source);
2191 }2211 }
2192 // Intentionally skip other cases2212 // Intentionally skip other cases
2193 }2213 }
2194 return new RegExp(sources.join('|'), 'i');2214 return new RegExp(sources.join('|'), 'i');
2195 }2215}
2196 2216
2197 function urlencode(o) {2217function urlencode(o) {
2198 var pairs = [];2218 var pairs = [];
2199 each(o, function(key, value) {2219 each(o, function(key, value) {
2200 pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));2220 pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
2201 });2221 });
2202 return pairs.join('&');2222 return pairs.join('&');
2203 }2223}
2204 2224
2205 // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B2225// borrowed from https://tools.ietf.org/html/rfc3986#appendix-B
2206 // intentionally using regex and not <a/> href parsing trick because React Native and other2226// intentionally using regex and not <a/> href parsing trick because React Native and other
2207 // environments where DOM might not be available2227// environments where DOM might not be available
2208 function parseUrl(url) {2228function parseUrl(url) {
2209 var match = url.match(2229 var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);
2210 /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/2230 if (!match) return {};
2211 );2231
2212 if (!match) return {};2232 // coerce to undefined values to empty string so we don't get 'undefined'
2213 2233 var query = match[6] || '';
2214 // coerce to undefined values to empty string so we don't get 'undefined'2234 var fragment = match[8] || '';
2215 var query = match[6] || '';2235 return {
2216 var fragment = match[8] || '';2236 protocol: match[2],
2217 return {2237 host: match[4],
2218 protocol: match[2],2238 path: match[5],
2219 host: match[4],2239 relative: match[5] + query + fragment // everything minus origin
2220 path: match[5],2240 };
2221 relative: match[5] + query + fragment // everything minus origin2241}
2222 };2242function uuid4() {
2223 }2243 var crypto = _window.crypto || _window.msCrypto;
2224 function uuid4() {2244
2225 var crypto = _window.crypto || _window.msCrypto;2245 if (!isUndefined(crypto) && crypto.getRandomValues) {
2226 2246 // Use window.crypto API if available
2227 if (!isUndefined(crypto) && crypto.getRandomValues) {2247 // eslint-disable-next-line no-undef
2228 // Use window.crypto API if available2248 var arr = new Uint16Array(8);
2229 // eslint-disable-next-line no-undef2249 crypto.getRandomValues(arr);
2230 var arr = new Uint16Array(8);2250
2231 crypto.getRandomValues(arr);2251 // set 4 in byte 7
2232 2252 arr[3] = (arr[3] & 0xfff) | 0x4000;
2233 // set 4 in byte 72253 // set 2 most significant bits of byte 9 to '10'
2234 arr[3] = (arr[3] & 0xfff) | 0x4000;2254 arr[4] = (arr[4] & 0x3fff) | 0x8000;
2235 // set 2 most significant bits of byte 9 to '10'2255
2236 arr[4] = (arr[4] & 0x3fff) | 0x8000;2256 var pad = function(num) {
2237 2257 var v = num.toString(16);
2238 var pad = function(num) {2258 while (v.length < 4) {
2239 var v = num.toString(16);2259 v = '0' + v;
2240 while (v.length < 4) {2260 }
2241 v = '0' + v;2261 return v;
2242 }2262 };
2243 return v;2263
2244 };2264 return (
2245 2265 pad(arr[0]) +
2246 return (2266 pad(arr[1]) +
2247 pad(arr[0]) +2267 pad(arr[2]) +
2248 pad(arr[1]) +2268 pad(arr[3]) +
2249 pad(arr[2]) +2269 pad(arr[4]) +
2250 pad(arr[3]) +2270 pad(arr[5]) +
2251 pad(arr[4]) +2271 pad(arr[6]) +
2252 pad(arr[5]) +2272 pad(arr[7])
2253 pad(arr[6]) +2273 );
2254 pad(arr[7])2274 } else {
2255 );2275 // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
2256 } else {2276 return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
2257 // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#21175232277 var r = (Math.random() * 16) | 0,
2258 return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {2278 v = c === 'x' ? r : (r & 0x3) | 0x8;
2259 var r = (Math.random() * 16) | 0,2279 return v.toString(16);
2260 v = c === 'x' ? r : (r & 0x3) | 0x8;2280 });
2261 return v.toString(16);2281 }
2262 });2282}
2263 }
2264 }
2265 2283
2266 /**2284/**
2267 * Given a child DOM element, returns a query-selector statement describing that2285 * Given a child DOM element, returns a query-selector statement describing that
2268 * and its ancestors2286 * and its ancestors
2269 * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]2287 * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
2270 * @param elem2288 * @param elem
2271 * @returns {string}2289 * @returns {string}
2272 */2290 */
2273 function htmlTreeAsString(elem) {2291function htmlTreeAsString(elem) {
2274 /* eslint no-extra-parens:0*/2292 /* eslint no-extra-parens:0*/
2275 var MAX_TRAVERSE_HEIGHT = 5,2293 var MAX_TRAVERSE_HEIGHT = 5,
2276 MAX_OUTPUT_LEN = 80,2294 MAX_OUTPUT_LEN = 80,
2277 out = [],2295 out = [],
2278 height = 0,2296 height = 0,
2279 len = 0,2297 len = 0,
2280 separator = ' > ',2298 separator = ' > ',
2281 sepLength = separator.length,2299 sepLength = separator.length,
2282 nextStr;2300 nextStr;
2283 2301
2284 while (elem && height++ < MAX_TRAVERSE_HEIGHT) {2302 while (elem && height++ < MAX_TRAVERSE_HEIGHT) {
2285 nextStr = htmlElementAsString(elem);2303 nextStr = htmlElementAsString(elem);
2286 // bail out if2304 // bail out if
2287 // - nextStr is the 'html' element2305 // - nextStr is the 'html' element
2288 // - the length of the string that would be created exceeds MAX_OUTPUT_LEN2306 // - the length of the string that would be created exceeds MAX_OUTPUT_LEN
2289 // (ignore this limit if we are on the first iteration)2307 // (ignore this limit if we are on the first iteration)
2290 if (2308 if (
2291 nextStr === 'html' ||2309 nextStr === 'html' ||
2292 (height > 1 &&2310 (height > 1 && len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN)
2293 len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN)2311 ) {
2294 ) {2312 break;
2295 break;2313 }
2296 }
2297 2314
2298 out.push(nextStr);2315 out.push(nextStr);
2299 2316
2300 len += nextStr.length;2317 len += nextStr.length;
2301 elem = elem.parentNode;2318 elem = elem.parentNode;
2302 }2319 }
2303 2320
2304 return out.reverse().join(separator);2321 return out.reverse().join(separator);
2305 }2322}
2306 2323
2307 /**2324/**
2308 * Returns a simple, query-selector representation of a DOM element2325 * Returns a simple, query-selector representation of a DOM element
2309 * e.g. [HTMLElement] => input#foo.btn[name=baz]2326 * e.g. [HTMLElement] => input#foo.btn[name=baz]
2310 * @param HTMLElement2327 * @param HTMLElement
2311 * @returns {string}2328 * @returns {string}
2312 */2329 */
2313 function htmlElementAsString(elem) {2330function htmlElementAsString(elem) {
2314 var out = [],2331 var out = [],
2315 className,2332 className,
2316 classes,2333 classes,
2317 key,2334 key,
2318 attr,2335 attr,
2319 i;2336 i;
2320 2337
2321 if (!elem || !elem.tagName) {2338 if (!elem || !elem.tagName) {
2322 return '';2339 return '';
2323 }2340 }
2324 2341
2325 out.push(elem.tagName.toLowerCase());2342 out.push(elem.tagName.toLowerCase());
2326 if (elem.id) {2343 if (elem.id) {
2327 out.push('#' + elem.id);2344 out.push('#' + elem.id);
2328 }2345 }
2329 2346
2330 className = elem.className;2347 className = elem.className;
2331 if (className && isString(className)) {2348 if (className && isString(className)) {
2332 classes = className.split(/\s+/);2349 classes = className.split(/\s+/);
2333 for (i = 0; i < classes.length; i++) {2350 for (i = 0; i < classes.length; i++) {
2334 out.push('.' + classes[i]);2351 out.push('.' + classes[i]);
2335 }2352 }
2336 }2353 }
2337 var attrWhitelist = ['type', 'name', 'title', 'alt'];2354 var attrWhitelist = ['type', 'name', 'title', 'alt'];
2338 for (i = 0; i < attrWhitelist.length; i++) {2355 for (i = 0; i < attrWhitelist.length; i++) {
2339 key = attrWhitelist[i];2356 key = attrWhitelist[i];
2340 attr = elem.getAttribute(key);2357 attr = elem.getAttribute(key);
2341 if (attr) {2358 if (attr) {
2342 out.push('[' + key + '="' + attr + '"]');2359 out.push('[' + key + '="' + attr + '"]');
2343 }2360 }
2344 }2361 }
2345 return out.join('');2362 return out.join('');
2346 }2363}
2347 2364
2348 /**2365/**
2349 * Returns true if either a OR b is truthy, but not both2366 * Returns true if either a OR b is truthy, but not both
2350 */2367 */
2351 function isOnlyOneTruthy(a, b) {2368function isOnlyOneTruthy(a, b) {
2352 return !!(!!a ^ !!b);2369 return !!(!!a ^ !!b);
2353 }2370}
2354 2371
2355 /**2372/**
2356 * Returns true if the two input exception interfaces have the same content2373 * Returns true if the two input exception interfaces have the same content
2357 */2374 */
2358 function isSameException(ex1, ex2) {2375function isSameException(ex1, ex2) {
2359 if (isOnlyOneTruthy(ex1, ex2)) return false;2376 if (isOnlyOneTruthy(ex1, ex2)) return false;
2360 2377
2361 ex1 = ex1.values[0];2378 ex1 = ex1.values[0];
2362 ex2 = ex2.values[0];2379 ex2 = ex2.values[0];
2363 2380
2364 if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false;2381 if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false;
2365 2382
2366 return isSameStacktrace(ex1.stacktrace, ex2.stacktrace);2383 return isSameStacktrace(ex1.stacktrace, ex2.stacktrace);
2367 }2384}
2368 2385
2369 /**2386/**
2370 * Returns true if the two input stack trace interfaces have the same content2387 * Returns true if the two input stack trace interfaces have the same content
2371 */2388 */
2372 function isSameStacktrace(stack1, stack2) {2389function isSameStacktrace(stack1, stack2) {
2373 if (isOnlyOneTruthy(stack1, stack2)) return false;2390 if (isOnlyOneTruthy(stack1, stack2)) return false;
2374 2391
2375 var frames1 = stack1.frames;2392 var frames1 = stack1.frames;
2376 var frames2 = stack2.frames;2393 var frames2 = stack2.frames;
2377 2394
2378 // Exit early if frame count differs2395 // Exit early if frame count differs
2379 if (frames1.length !== frames2.length) return false;2396 if (frames1.length !== frames2.length) return false;
2380 2397
2381 // Iterate through every frame; bail out if anything differs2398 // Iterate through every frame; bail out if anything differs
2382 var a, b;2399 var a, b;
2383 for (var i = 0; i < frames1.length; i++) {2400 for (var i = 0; i < frames1.length; i++) {
2384 a = frames1[i];2401 a = frames1[i];
2385 b = frames2[i];2402 b = frames2[i];
2386 if (2403 if (
2387 a.filename !== b.filename ||2404 a.filename !== b.filename ||
2388 a.lineno !== b.lineno ||2405 a.lineno !== b.lineno ||
2389 a.colno !== b.colno ||2406 a.colno !== b.colno ||
2390 a['function'] !== b['function']2407 a['function'] !== b['function']
2391 )2408 )
2392 return false;2409 return false;
2393 }2410 }
2394 return true;2411 return true;
2395 }2412}
2396 2413
2397 /**2414/**
2398 * Polyfill a method2415 * Polyfill a method
2399 * @param obj object e.g. `document`2416 * @param obj object e.g. `document`
2400 * @param name method name present on object e.g. `addEventListener`2417 * @param name method name present on object e.g. `addEventListener`
2401 * @param replacement replacement function2418 * @param replacement replacement function
2402 * @param track {optional} record instrumentation to an array2419 * @param track {optional} record instrumentation to an array
2403 */2420 */
2404 function fill(obj, name, replacement, track) {2421function fill(obj, name, replacement, track) {
2405 var orig = obj[name];2422 var orig = obj[name];
2406 obj[name] = replacement(orig);2423 obj[name] = replacement(orig);
2407 if (track) {2424 obj[name].__raven__ = true;
2408 track.push([obj, name, orig]);2425 obj[name].__orig__ = orig;
2409 }2426 if (track) {
2410 }2427 track.push([obj, name, orig]);
2411 2428 }
2412 if (typeof __DEV__ !== 'undefined' && __DEV__) {2429}
2413 Raven.utils = {2430
2414 isUndefined: isUndefined,2431module.exports = {
2415 isFunction: isFunction,2432 isObject: isObject,
2416 isString: isString,2433 isError: isError,
2417 isObject: isObject,2434 isErrorEvent: isErrorEvent,
2418 isEmptyObject: isEmptyObject,2435 isUndefined: isUndefined,
2419 isError: isError,2436 isFunction: isFunction,
2420 each: each,2437 isString: isString,
2421 objectMerge: objectMerge,2438 isArray: isArray,
2422 truncate: truncate,2439 isEmptyObject: isEmptyObject,
2423 hasKey: hasKey,2440 supportsErrorEvent: supportsErrorEvent,
2424 joinRegExp: joinRegExp,2441 wrappedCallback: wrappedCallback,
2425 urlencode: urlencode,2442 each: each,
2426 uuid4: uuid4,2443 objectMerge: objectMerge,
2427 htmlTreeAsString: htmlTreeAsString,2444 truncate: truncate,
2428 htmlElementAsString: htmlElementAsString,2445 objectFrozen: objectFrozen,
2429 parseUrl: parseUrl,2446 hasKey: hasKey,
2430 fill: fill2447 joinRegExp: joinRegExp,
2431 };2448 urlencode: urlencode,
2432 }2449 uuid4: uuid4,
2433 2450 htmlTreeAsString: htmlTreeAsString,
2434 // Deprecations2451 htmlElementAsString: htmlElementAsString,
2435 Raven.prototype.setUser = Raven.prototype.setUserContext;2452 isSameException: isSameException,
2436 Raven.prototype.setReleaseContext = Raven.prototype.setRelease;2453 isSameStacktrace: isSameStacktrace,
2437 2454 parseUrl: parseUrl,
2438 module.exports = Raven;2455 fill: fill
2439 }.call(2456};
2440 this,2457
2441 typeof global !== 'undefined'2458}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
2442 ? global2459},{}],6:[function(_dereq_,module,exports){
2443 : typeof self !== 'undefined'2460(function (global){
2444 ? self2461var utils = _dereq_(5);
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 }
2529 2462
2530 module.exports = {2463/*
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 /*
2544 TraceKit - Cross brower stack traces2464 TraceKit - Cross brower stack traces
2545 2465
2546 This was originally forked from github.com/occ/TraceKit, but has since been2466 This was originally forked from github.com/occ/TraceKit, but has since been
@@ -2550,33 +2470,31 @@ · 3 unmodified lines
2550 MIT license2470 MIT license
2551*/2471*/
2552 2472
2553 var TraceKit = {2473var TraceKit = {
2554 collectWindowErrors: true,2474 collectWindowErrors: true,
2555 debug: false2475 debug: false
2556 };2476};
2557 2477
2558 // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)2478// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
2559 var _window =2479var _window =
2560 typeof window !== 'undefined'2480 typeof window !== 'undefined'
2561 ? window2481 ? window
2562 : typeof global !== 'undefined'2482 : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2563 ? global
2564 : typeof self !== 'undefined' ? self : {};
2565 2483
2566 // global reference to slice2484// global reference to slice
2567 var _slice = [].slice;2485var _slice = [].slice;
2568 var UNKNOWN_FUNCTION = '?';2486var UNKNOWN_FUNCTION = '?';
2569 2487
2570 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types2488// 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): )?(.*)$/;2489var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/;
2572 2490
2573 function getLocationHref() {2491function getLocationHref() {
2574 if (typeof document === 'undefined' || document.location == null) return '';2492 if (typeof document === 'undefined' || document.location == null) return '';
2575 2493
2576 return document.location.href;2494 return document.location.href;
2577 }2495}
2578 2496
2579 /**2497/**
2580 * TraceKit.report: cross-browser processing of unhandled exceptions2498 * TraceKit.report: cross-browser processing of unhandled exceptions
2581 *2499 *
2582 * Syntax:2500 * Syntax:
@@ -2615,68 +2533,68 @@ · 32 unmodified lines
2615 * Handlers receive a stackInfo object as described in the2533 * Handlers receive a stackInfo object as described in the
2616 * TraceKit.computeStackTrace docs.2534 * TraceKit.computeStackTrace docs.
2617 */2535 */
2618 TraceKit.report = (function reportModuleWrapper() {2536TraceKit.report = (function reportModuleWrapper() {
2619 var handlers = [],2537 var handlers = [],
2620 lastArgs = null,2538 lastArgs = null,
2621 lastException = null,2539 lastException = null,
2622 lastExceptionStack = null;2540 lastExceptionStack = null;
2623 2541
2624 /**2542 /**
2625 * Add a crash handler.2543 * Add a crash handler.
2626 * @param {Function} handler2544 * @param {Function} handler
2627 */2545 */
2628 function subscribe(handler) {2546 function subscribe(handler) {
2629 installGlobalHandler();2547 installGlobalHandler();
2630 handlers.push(handler);2548 handlers.push(handler);
2631 }2549 }
2632 2550
2633 /**2551 /**
2634 * Remove a crash handler.2552 * Remove a crash handler.
2635 * @param {Function} handler2553 * @param {Function} handler
2636 */2554 */
2637 function unsubscribe(handler) {2555 function unsubscribe(handler) {
2638 for (var i = handlers.length - 1; i >= 0; --i) {2556 for (var i = handlers.length - 1; i >= 0; --i) {
2639 if (handlers[i] === handler) {2557 if (handlers[i] === handler) {
2640 handlers.splice(i, 1);2558 handlers.splice(i, 1);
2641 }2559 }
2642 }2560 }
2643 }2561 }
2644 2562
2645 /**2563 /**
2646 * Remove all crash handlers.2564 * Remove all crash handlers.
2647 */2565 */
2648 function unsubscribeAll() {2566 function unsubscribeAll() {
2649 uninstallGlobalHandler();2567 uninstallGlobalHandler();
2650 handlers = [];2568 handlers = [];
2651 }2569 }
2652 2570
2653 /**2571 /**
2654 * Dispatch stack information to all handlers.2572 * Dispatch stack information to all handlers.
2655 * @param {Object.<string, *>} stack2573 * @param {Object.<string, *>} stack
2656 */2574 */
2657 function notifyHandlers(stack, isWindowError) {2575 function notifyHandlers(stack, isWindowError) {
2658 var exception = null;2576 var exception = null;
2659 if (isWindowError && !TraceKit.collectWindowErrors) {2577 if (isWindowError && !TraceKit.collectWindowErrors) {
2660 return;2578 return;
2661 }2579 }
2662 for (var i in handlers) {2580 for (var i in handlers) {
2663 if (handlers.hasOwnProperty(i)) {2581 if (handlers.hasOwnProperty(i)) {
2664 try {2582 try {
2665 handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));2583 handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
2666 } catch (inner) {2584 } catch (inner) {
2667 exception = inner;2585 exception = inner;
2668 }2586 }
2669 }2587 }
2670 }2588 }
2671 2589
2672 if (exception) {2590 if (exception) {
2673 throw exception;2591 throw exception;
2674 }2592 }
2675 }2593 }
2676 2594
2677 var _oldOnerrorHandler, _onErrorHandlerInstalled;2595 var _oldOnerrorHandler, _onErrorHandlerInstalled;
2678 2596
2679 /**2597 /**
2680 * Ensures all global unhandled exceptions are recorded.2598 * Ensures all global unhandled exceptions are recorded.
2681 * Supported by Gecko and IE.2599 * Supported by Gecko and IE.
2682 * @param {string} message Error message.2600 * @param {string} message Error message.
@@ -2687,135 +2605,132 @@ · 4 unmodified lines
2687 * occurred.2605 * occurred.
2688 * @param {?Error} ex The actual Error object.2606 * @param {?Error} ex The actual Error object.
2689 */2607 */
2690 function traceKitWindowOnError(message, url, lineNo, colNo, ex) {2608 function traceKitWindowOnError(message, url, lineNo, colNo, ex) {
2691 var stack = null;2609 var stack = null;
2692 2610
2693 if (lastExceptionStack) {2611 if (lastExceptionStack) {
2694 TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(2612 TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(
2695 lastExceptionStack,2613 lastExceptionStack,
2696 url,2614 url,
2697 lineNo,2615 lineNo,
2698 message2616 message
2699 );2617 );
2700 processLastException();2618 processLastException();
2701 } else if (ex && utils.isError(ex)) {2619 } else if (ex && utils.isError(ex)) {
2702 // non-string `ex` arg; attempt to extract stack trace2620 // non-string `ex` arg; attempt to extract stack trace
2703 2621
2704 // New chrome and blink send along a real error object2622 // New chrome and blink send along a real error object
2705 // Let's just report that like a normal error.2623 // Let's just report that like a normal error.
2706 // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror2624 // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
2707 stack = TraceKit.computeStackTrace(ex);2625 stack = TraceKit.computeStackTrace(ex);
2708 notifyHandlers(stack, true);2626 notifyHandlers(stack, true);
2709 } else {2627 } else {
2710 var location = {2628 var location = {
2711 url: url,2629 url: url,
2712 line: lineNo,2630 line: lineNo,
2713 column: colNo2631 column: colNo
2714 };2632 };
2715 2633
2716 var name = undefined;2634 var name = undefined;
2717 var msg = message; // must be new var or will modify original `arguments`2635 var msg = message; // must be new var or will modify original `arguments`
2718 var groups;2636 var groups;
2719 if ({}.toString.call(message) === '[object String]') {2637 if ({}.toString.call(message) === '[object String]') {
2720 var groups = message.match(ERROR_TYPES_RE);2638 var groups = message.match(ERROR_TYPES_RE);
2721 if (groups) {2639 if (groups) {
2722 name = groups[1];2640 name = groups[1];
2723 msg = groups[2];2641 msg = groups[2];
2724 }2642 }
2725 }2643 }
2726 2644
2727 location.func = UNKNOWN_FUNCTION;2645 location.func = UNKNOWN_FUNCTION;
2728 2646
2729 stack = {2647 stack = {
2730 name: name,2648 name: name,
2731 message: msg,2649 message: msg,
2732 url: getLocationHref(),2650 url: getLocationHref(),
2733 stack: [location]2651 stack: [location]
2734 };2652 };
2735 notifyHandlers(stack, true);2653 notifyHandlers(stack, true);
2736 }2654 }
2737 2655
2738 if (_oldOnerrorHandler) {2656 if (_oldOnerrorHandler) {
2739 return _oldOnerrorHandler.apply(this, arguments);2657 return _oldOnerrorHandler.apply(this, arguments);
2740 }2658 }
2741 2659
2742 return false;2660 return false;
2743 }2661 }
2744 2662
2745 function installGlobalHandler() {2663 function installGlobalHandler() {
2746 if (_onErrorHandlerInstalled) {2664 if (_onErrorHandlerInstalled) {
2747 return;2665 return;
2748 }2666 }
2749 _oldOnerrorHandler = _window.onerror;2667 _oldOnerrorHandler = _window.onerror;
2750 _window.onerror = traceKitWindowOnError;2668 _window.onerror = traceKitWindowOnError;
2751 _onErrorHandlerInstalled = true;2669 _onErrorHandlerInstalled = true;
2752 }2670 }
2753 2671
2754 function uninstallGlobalHandler() {2672 function uninstallGlobalHandler() {
2755 if (!_onErrorHandlerInstalled) {2673 if (!_onErrorHandlerInstalled) {
2756 return;2674 return;
2757 }2675 }
2758 _window.onerror = _oldOnerrorHandler;2676 _window.onerror = _oldOnerrorHandler;
2759 _onErrorHandlerInstalled = false;2677 _onErrorHandlerInstalled = false;
2760 _oldOnerrorHandler = undefined;2678 _oldOnerrorHandler = undefined;
2761 }2679 }
2762 2680
2763 function processLastException() {2681 function processLastException() {
2764 var _lastExceptionStack = lastExceptionStack,2682 var _lastExceptionStack = lastExceptionStack,
2765 _lastArgs = lastArgs;2683 _lastArgs = lastArgs;
2766 lastArgs = null;2684 lastArgs = null;
2767 lastExceptionStack = null;2685 lastExceptionStack = null;
2768 lastException = null;2686 lastException = null;
2769 notifyHandlers.apply(2687 notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs));
2770 null,2688 }
2771 [_lastExceptionStack, false].concat(_lastArgs)
2772 );
2773 }
2774 2689
2775 /**2690 /**
2776 * Reports an unhandled Error to TraceKit.2691 * Reports an unhandled Error to TraceKit.
2777 * @param {Error} ex2692 * @param {Error} ex
2778 * @param {?boolean} rethrow If false, do not re-throw the exception.2693 * @param {?boolean} rethrow If false, do not re-throw the exception.
2779 * Only used for window.onerror to not cause an infinite loop of2694 * Only used for window.onerror to not cause an infinite loop of
2780 * rethrowing.2695 * rethrowing.
2781 */2696 */
2782 function report(ex, rethrow) {2697 function report(ex, rethrow) {
2783 var args = _slice.call(arguments, 1);2698 var args = _slice.call(arguments, 1);
2784 if (lastExceptionStack) {2699 if (lastExceptionStack) {
2785 if (lastException === ex) {2700 if (lastException === ex) {
2786 return; // already caught by an inner catch block, ignore2701 return; // already caught by an inner catch block, ignore
2787 } else {2702 } else {
2788 processLastException();2703 processLastException();
2789 }2704 }
2790 }2705 }
2791 2706
2792 var stack = TraceKit.computeStackTrace(ex);2707 var stack = TraceKit.computeStackTrace(ex);
2793 lastExceptionStack = stack;2708 lastExceptionStack = stack;
2794 lastException = ex;2709 lastException = ex;
2795 lastArgs = args;2710 lastArgs = args;
2796 2711
2797 // If the stack trace is incomplete, wait for 2 seconds for2712 // If the stack trace is incomplete, wait for 2 seconds for
2798 // slow slow IE to see if onerror occurs or not before reporting2713 // slow slow IE to see if onerror occurs or not before reporting
2799 // this exception; otherwise, we will end up with an incomplete2714 // this exception; otherwise, we will end up with an incomplete
2800 // stack trace2715 // stack trace
2801 setTimeout(function() {2716 setTimeout(function() {
2802 if (lastException === ex) {2717 if (lastException === ex) {
2803 processLastException();2718 processLastException();
2804 }2719 }
2805 }, stack.incomplete ? 2000 : 0);2720 }, stack.incomplete ? 2000 : 0);
2806 2721
2807 if (rethrow !== false) {2722 if (rethrow !== false) {
2808 throw ex; // re-throw to propagate to the top level (and cause window.onerror)2723 throw ex; // re-throw to propagate to the top level (and cause window.onerror)
2809 }2724 }
2810 }2725 }
2811 2726
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
19 after: 'afterRoute'19 after: 'afterRoute'
20 20
21 init: ->21 init: ->
22 @scrollEl = if app.isMobile() then document.body else @el22 @scrollEl = if app.isMobile()
23 (document.scrollingElement || document.body)
24 else
25 @el
23 @scrollMap = {}26 @scrollMap = {}
24 @scrollStack = []27 @scrollStack = []
25 28
@@ -150,6 +153,9 @@ class app.views.Content extends app.View · 124 unmodified lines
150 return153 return
151 154
152 afterRoute: (route, context) =>155 afterRoute: (route, context) =>
156 if route != 'entry' and route != 'type'
157 resetFavicon()
158
153 switch route159 switch route
154 when 'root'160 when 'root'
155 @show @rootPage161 @show @rootPage
assets/javascripts/views/content/entry_page.coffee+2−1
@@ -40,6 +40,7 @@ class app.views.EntryPage extends app.View · 39 unmodified lines
40 if app.disabledDocs.findBy 'slug', @entry.doc.slug40 if app.disabledDocs.findBy 'slug', @entry.doc.slug
41 @hiddenView = new app.views.HiddenPage @el, @entry41 @hiddenView = new app.views.HiddenPage @el, @entry
42 42
43 setFaviconForDoc(@entry.doc)
43 @delay @polyfillMathML44 @delay @polyfillMathML
44 @trigger 'loaded'45 @trigger 'loaded'
45 return46 return
@@ -123,7 +124,7 @@ class app.views.EntryPage extends app.View · 77 unmodified lines
123 @render @tmpl('pageLoadError')124 @render @tmpl('pageLoadError')
124 @resetClass()125 @resetClass()
125 @addClass @constructor.errorClass126 @addClass @constructor.errorClass
126 app.appCache?.update()127 app.serviceWorker?.update()
127 return128 return
128 129
129 cache: ->130 cache: ->
assets/javascripts/views/content/offline_page.coffee+1−0
@@ -57,6 +57,7 @@ class app.views.OfflinePage extends app.View · 56 unmodified lines
57 doc[action](@onInstallSuccess.bind(@, doc), @onInstallError.bind(@, doc), @onInstallProgress.bind(@, doc))57 doc[action](@onInstallSuccess.bind(@, doc), @onInstallError.bind(@, doc), @onInstallProgress.bind(@, doc))
58 el.parentNode.innerHTML = "#{el.textContent.replace(/e$/, '')}ing…"58 el.parentNode.innerHTML = "#{el.textContent.replace(/e$/, '')}ing…"
59 else if action = el.getAttribute('data-action-all')59 else if action = el.getAttribute('data-action-all')
60 return unless action isnt 'uninstall' or window.confirm('Uninstall all docs?')
60 app.db.migrate()61 app.db.migrate()
61 $.click(el) for el in @findAll("[data-action='#{action}']")62 $.click(el) for el in @findAll("[data-action='#{action}']")
62 return63 return
assets/javascripts/views/content/root_page.coffee+10−3
@@ -9,10 +9,17 @@ class app.views.RootPage extends app.View · 8 unmodified lines
9 9
10 render: ->10 render: ->
11 @empty()11 @empty()
12 if app.isAndroidWebview()12
13 @append @tmpl('androidWarning')13 tmpl = if app.isAndroidWebview()
14 'androidWarning'
15 else if @isHidden()
16 'splash'
17 else if app.isMobile()
18 'mobileIntro'
14 else19 else
15 @append @tmpl if @isHidden() then 'splash' else if app.isMobile() then 'mobileIntro' else 'intro'20 'intro'
21
22 @append @tmpl(tmpl)
16 return23 return
17 24
18 hideIntro: ->25 hideIntro: ->
assets/javascripts/views/content/settings_page.coffee+10−12
@@ -1,7 +1,4 @@
1class app.views.SettingsPage extends app.View1class app.views.SettingsPage extends app.View
2 LAYOUTS = ['_max-width', '_sidebar-hidden', '_native-scrollbars']
3 SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden'
4
5 @className: '_static'2 @className: '_static'
6 3
7 @events:4 @events:
@@ -17,32 +14,31 @@ class app.views.SettingsPage extends app.View · 9 unmodified lines
17 settings.dark = app.settings.get('dark')14 settings.dark = app.settings.get('dark')
18 settings.smoothScroll = !app.settings.get('fastScroll')15 settings.smoothScroll = !app.settings.get('fastScroll')
19 settings.arrowScroll = app.settings.get('arrowScroll')16 settings.arrowScroll = app.settings.get('arrowScroll')
20 settings[layout] = app.settings.hasLayout(layout) for layout in LAYOUTS17 settings.autoInstall = app.settings.get('autoInstall')
18 settings.analyticsConsent = app.settings.get('analyticsConsent')
19 settings[layout] = app.settings.hasLayout(layout) for layout in app.settings.LAYOUTS
21 settings20 settings
22 21
23 getTitle: ->22 getTitle: ->
24 'Preferences'23 'Preferences'
25 24
26 toggleDark: (enable) ->25 toggleDark: (enable) ->
27 css = $('link[rel="stylesheet"][data-alt]')
28 alt = css.getAttribute('data-alt')
29 css.setAttribute('data-alt', css.getAttribute('href'))
30 css.setAttribute('href', alt)
31 app.settings.set('dark', !!enable)26 app.settings.set('dark', !!enable)
32 app.appCache?.updateInBackground()
33 return27 return
34 28
35 toggleLayout: (layout, enable) ->29 toggleLayout: (layout, enable) ->
36 document.body.classList[if enable then 'add' else 'remove'](layout) unless layout is SIDEBAR_HIDDEN_LAYOUT
37 document.body.classList[if $.overlayScrollbarsEnabled() then 'add' else 'remove']('_overlay-scrollbars')
38 app.settings.setLayout(layout, enable)30 app.settings.setLayout(layout, enable)
39 app.appCache?.updateInBackground()
40 return31 return
41 32
42 toggleSmoothScroll: (enable) ->33 toggleSmoothScroll: (enable) ->
43 app.settings.set('fastScroll', !enable)34 app.settings.set('fastScroll', !enable)
44 return35 return
45 36
37 toggleAnalyticsConsent: (enable) ->
38 app.settings.set('analyticsConsent', if enable then '1' else '0')
39 resetAnalytics() unless enable
40 return
41
46 toggle: (name, enable) ->42 toggle: (name, enable) ->
47 app.settings.set(name, enable)43 app.settings.set(name, enable)
48 return44 return
@@ -86,6 +82,8 @@ class app.views.SettingsPage extends app.View · 37 unmodified lines
86 @toggleSmoothScroll input.checked82 @toggleSmoothScroll input.checked
87 when 'import'83 when 'import'
88 @import input.files[0], input84 @import input.files[0], input
85 when 'analyticsConsent'
86 @toggleAnalyticsConsent input.checked
89 else87 else
90 @toggle input.name, input.checked88 @toggle input.name, input.checked
91 return89 return
assets/javascripts/views/content/type_page.coffee+1−0
@@ -9,6 +9,7 @@ class app.views.TypePage extends app.View · 8 unmodified lines
9 9
10 render: (@type) ->10 render: (@type) ->
11 @html @tmpl('typePage', @type)11 @html @tmpl('typePage', @type)
12 setFaviconForDoc(@type.doc)
12 return13 return
13 14
14 getTitle: ->15 getTitle: ->
assets/javascripts/views/layout/document.coffee+8−6
@@ -28,7 +28,7 @@ class app.views.Document extends app.View · 27 unmodified lines
28 return28 return
29 29
30 setTitle: (title) ->30 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'
32 32
33 afterRoute: (route) =>33 afterRoute: (route) =>
34 if route is 'settings'34 if route is 'settings'
@@ -75,9 +75,11 @@ class app.views.Document extends app.View · 40 unmodified lines
75 return unless target.hasAttribute('data-behavior')75 return unless target.hasAttribute('data-behavior')
76 $.stopEvent(event)76 $.stopEvent(event)
77 switch target.getAttribute('data-behavior')77 switch target.getAttribute('data-behavior')
78 when 'back' then history.back()78 when 'back' then history.back()
79 when 'reload' then window.location.reload()79 when 'reload' then window.location.reload()
80 when 'reboot' then window.location = '/'80 when 'reboot' then app.reboot()
81 when 'hard-reload' then app.reload()81 when 'hard-reload' then app.reload()
82 when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?')82 when 'reset' then app.reset() if confirm('Are you sure you want to reset DevDocs?')
83 when 'accept-analytics' then Cookies.set('analyticsConsent', '1') && app.reboot()
84 when 'decline-analytics' then Cookies.set('analyticsConsent', '0') && app.reboot()
83 return85 return
assets/javascripts/views/layout/mobile.coffee+2−2
@@ -16,8 +16,8 @@ class app.views.Mobile extends app.View · 15 unmodified lines
16 @detect: ->16 @detect: ->
17 try17 try
18 (window.matchMedia('(max-width: 480px)').matches) or18 (window.matchMedia('(max-width: 480px)').matches) or
19 (window.matchMedia('(max-device-width: 767px)').matches) or19 (window.matchMedia('(max-width: 767px)').matches) or
20 (window.matchMedia('(max-device-height: 767px) and (max-device-width: 1024px)').matches) or20 (window.matchMedia('(max-height: 767px) and (max-width: 1024px)').matches) or
21 # Need to sniff the user agent because some Android and Windows Phone devices don't take21 # Need to sniff the user agent because some Android and Windows Phone devices don't take
22 # resolution (dpi) into account when reporting device width/height.22 # resolution (dpi) into account when reporting device width/height.
23 (navigator.userAgent.indexOf('Android') isnt -1 and navigator.userAgent.indexOf('Mobile') isnt -1) or23 (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
11 init: ->11 init: ->
12 @el.setAttribute('draggable', 'true')12 @el.setAttribute('draggable', 'true')
13 @appendTo $('._app')13 @appendTo $('._app')
14
15 @style = $('style[data-resizer]')
16 @size = @style.getAttribute('data-size')
17 return14 return
18 15
19 MIN = 26016 MIN = 260
@@ -24,15 +21,11 @@ class app.views.Resizer extends app.View · 4 unmodified lines
24 return unless value > 021 return unless value > 0
25 value = Math.min(Math.max(Math.round(value), MIN), MAX)22 value = Math.min(Math.max(Math.round(value), MIN), MAX)
26 newSize = "#{value}px"23 newSize = "#{value}px"
27 @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize)24 document.documentElement.style.setProperty('--sidebarWidth', newSize)
28 @size = newSize25 app.settings.setSize(value) if save
29 if save
30 app.settings.setSize(value)
31 app.appCache?.updateInBackground()
32 return26 return
33 27
34 onDragStart: (event) =>28 onDragStart: (event) =>
35 @style.removeAttribute('disabled')
36 event.dataTransfer.effectAllowed = 'link'29 event.dataTransfer.effectAllowed = 'link'
37 event.dataTransfer.setData('Text', '')30 event.dataTransfer.setData('Text', '')
38 $.on(window, 'dragover', @onDrag)31 $.on(window, 'dragover', @onDrag)
@@ -50,7 +43,7 @@ class app.views.Resizer extends app.View · 11 unmodified lines
50 onDragEnd: (event) =>43 onDragEnd: (event) =>
51 $.off(window, 'dragover', @onDrag)44 $.off(window, 'dragover', @onDrag)
52 value = event.pageX or (event.screenX - window.screenX)45 value = event.pageX or (event.screenX - window.screenX)
53 if @lastDragValue and not (@lastDragValue - 5 < value < @lastDragValue + 5) # https://github.com/Thibaut/devdocs/issues/26546 if @lastDragValue and not (@lastDragValue - 5 < value < @lastDragValue + 5) # https://github.com/freeCodeCamp/devdocs/issues/265
54 value = @lastDragValue47 value = @lastDragValue
55 @resize(value, true)48 @resize(value, true)
56 return49 return
assets/javascripts/views/layout/settings.coffee+1−9
@@ -25,7 +25,6 @@ class app.views.Settings extends app.View · 24 unmodified lines
25 if super25 if super
26 @render()26 @render()
27 document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT)27 document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT)
28 app.appCache?.on 'progress', @onAppCacheProgress
29 return28 return
30 29
31 deactivate: ->30 deactivate: ->
@@ -33,7 +32,6 @@ class app.views.Settings extends app.View · 1 unmodified line
33 @resetClass()32 @resetClass()
34 @docPicker.detach()33 @docPicker.detach()
35 document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)34 document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)
36 app.appCache?.off 'progress', @onAppCacheProgress
37 return35 return
38 36
39 render: ->37 render: ->
@@ -52,7 +50,7 @@ class app.views.Settings extends app.View · 12 unmodified lines
52 docs = @docPicker.getSelectedDocs()50 docs = @docPicker.getSelectedDocs()
53 app.settings.setDocs(docs)51 app.settings.setDocs(docs)
54 52
55 @saveBtn.textContent = if app.appCache then 'Downloading\u2026' else 'Saving\u2026'53 @saveBtn.textContent = 'Saving\u2026'
56 disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1)54 disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1)
57 disabledDocs.uninstall ->55 disabledDocs.uninstall ->
58 app.db.migrate()56 app.db.migrate()
@@ -83,9 +81,3 @@ class app.views.Settings extends app.View · 24 unmodified lines
83 $.stopEvent(event)81 $.stopEvent(event)
84 app.router.show '/'82 app.router.show '/'
85 return83 return
86
87 onAppCacheProgress: (event) =>
88 if event.lengthComputable
89 percentage = Math.round event.loaded * 100 / event.total
90 @saveBtn.textContent = "Downloading\u2026 (#{percentage}%)"
91 return
assets/javascripts/views/search/search.coffee+13−1
@@ -17,6 +17,7 @@ class app.views.Search extends app.View · 16 unmodified lines
17 typing: 'focus'17 typing: 'focus'
18 altG: 'google'18 altG: 'google'
19 altS: 'stackoverflow'19 altS: 'stackoverflow'
20 altD: 'duckduckgo'
20 21
21 @routes:22 @routes:
22 after: 'afterRoute'23 after: 'afterRoute'
@@ -29,6 +30,9 @@ class app.views.Search extends app.View · 6 unmodified lines
29 .on 'results', @onResults30 .on 'results', @onResults
30 .on 'end', @onEnd31 .on 'end', @onEnd
31 32
33 @scope
34 .on 'change', @onScopeChange
35
32 app.on 'ready', @onReady36 app.on 'ready', @onReady
33 $.on window, 'hashchange', @searchUrl37 $.on window, 'hashchange', @searchUrl
34 $.on window, 'focus', @onWindowFocus38 $.on window, 'focus', @onWindowFocus
@@ -113,6 +117,10 @@ class app.views.Search extends app.View · 78 unmodified lines
113 @externalSearch "https://stackoverflow.com/search?q="117 @externalSearch "https://stackoverflow.com/search?q="
114 return118 return
115 119
120 duckduckgo: =>
121 @externalSearch "https://duckduckgo.com/?t=devdocs&q="
122 return
123
116 onResults: (results) =>124 onResults: (results) =>
117 @hasResults = true if results.length125 @hasResults = true if results.length
118 @trigger 'results', results, @flags126 @trigger 'results', results, @flags
@@ -127,13 +135,17 @@ class app.views.Search extends app.View · 8 unmodified lines
127 if event.target is @resetLink135 if event.target is @resetLink
128 $.stopEvent(event)136 $.stopEvent(event)
129 @reset()137 @reset()
130 app.document.onEscape()
131 return138 return
132 139
133 onSubmit: (event) ->140 onSubmit: (event) ->
134 $.stopEvent(event)141 $.stopEvent(event)
135 return142 return
136 143
144 onScopeChange: =>
145 @value = ''
146 @onInput()
147 return
148
137 afterRoute: (name, context) =>149 afterRoute: (name, context) =>
138 return if app.shortcuts.eventInProgress?.name is 'escape'150 return if app.shortcuts.eventInProgress?.name is 'escape'
139 @reset(true) if not context.init and app.router.isIndex()151 @reset(true) if not context.init and app.router.isIndex()
assets/javascripts/views/search/search_scope.coffee+23−5
@@ -6,7 +6,9 @@ class app.views.SearchScope extends app.View · 5 unmodified lines
6 tag: '._search-tag'6 tag: '._search-tag'
7 7
8 @events:8 @events:
9 click: 'onClick'
9 keydown: 'onKeydown'10 keydown: 'onKeydown'
11 textInput: 'onTextInput'
10 12
11 @routes:13 @routes:
12 after: 'afterRoute'14 after: 'afterRoute'
@@ -87,17 +89,33 @@ class app.views.SearchScope extends app.View · 74 unmodified lines
87 @trigger 'change', null, previousDoc89 @trigger 'change', null, previousDoc
88 return90 return
89 91
92 doScopeSearch: (event) =>
93 @search @input.value[0...@input.selectionStart]
94 $.stopEvent(event) if @doc
95 return
96
97 onClick: (event) =>
98 if event.target is @tag
99 @reset()
100 $.stopEvent(event)
101 return
102
90 onKeydown: (event) =>103 onKeydown: (event) =>
91 if event.which is 8 # backspace104 if event.which is 8 # backspace
92 if @doc and not @input.value105 if @doc and @input.selectionEnd is 0
93 $.stopEvent(event)
94 @reset()106 @reset()
95 else if not @doc and @input.value107 $.stopEvent(event)
108 else if not @doc and @input.value and not $.isChromeForAndroid()
96 return if event.ctrlKey or event.metaKey or event.altKey or event.shiftKey109 return if event.ctrlKey or event.metaKey or event.altKey or event.shiftKey
97 if event.which is 9 or # tab110 if event.which is 9 or # tab
98 (event.which is 32 and app.isMobile()) # space111 (event.which is 32 and app.isMobile()) # space
99 @search @input.value[0...@input.selectionStart]112 @doScopeSearch(event)
100 $.stopEvent(event) if @doc113 return
114
115 onTextInput: (event) =>
116 return unless $.isChromeForAndroid()
117 if not @doc and @input.value and event.data == ' '
118 @doScopeSearch(event)
101 return119 return
102 120
103 extractHashValue: ->121 extractHashValue: ->
assets/javascripts/views/sidebar/sidebar.coffee+1−1
@@ -28,7 +28,7 @@ class app.views.Sidebar extends app.View · 27 unmodified lines
28 28
29 app.on 'ready', @onReady29 app.on 'ready', @onReady
30 30
31 $.on document.documentElement, 'mouseleave', (event) => @display() if event.clientX < 1031 $.on document.documentElement, 'mouseleave', (event) => @display() unless event.clientX <= 0
32 $.on document.documentElement, 'mouseenter', => @resetDisplay(forceNoHover: false)32 $.on document.documentElement, 'mouseenter', => @resetDisplay(forceNoHover: false)
33 return33 return
34 34
assets/stylesheets/application-dark.css.scss+0−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.png1//= depend_on sprites/docs.png
2//= depend_on docs-1@2x.png2//= depend_on sprites/docs@2x.png
3//= depend_on docs-2.png3//= depend_on sprites/docs.json
4//= depend_on docs-2@2x.png
5 4
6/*!5/*!
7 * Copyright 2013-2017 Thibaut Courouble and other contributors6 * Copyright 2013-2019 Thibaut Courouble and other contributors
8 *7 *
9 * This source code is licensed under the terms of the Mozilla8 * This source code is licensed under the terms of the Mozilla
10 * Public License, v. 2.0, a copy of which may be obtained at:9 * Public License, v. 2.0, a copy of which may be obtained at:
11 * http://mozilla.org/MPL/2.0/10 * http://mozilla.org/MPL/2.0/
12 */11 */
13 12
14@import 'global/variables',13@import 'global/variables-light',
14 'global/variables-dark',
15 'global/variables',
16 'global/mixins',
15 'global/icons',17 'global/icons',
16 'global/classes',18 'global/classes',
17 'global/base';19 'global/base';
@@ -34,6 +36,7 @@ · 16 unmodified lines
34 'pages/angularjs',36 'pages/angularjs',
35 'pages/apache',37 'pages/apache',
36 'pages/async',38 'pages/async',
39 'pages/bash',
37 'pages/bootstrap',40 'pages/bootstrap',
38 'pages/c',41 'pages/c',
39 'pages/cakephp',42 'pages/cakephp',
@@ -44,6 +47,7 @@ · 4 unmodified lines
44 'pages/crystal',47 'pages/crystal',
45 'pages/d',48 'pages/d',
46 'pages/d3',49 'pages/d3',
50 'pages/dart',
47 'pages/dojo',51 'pages/dojo',
48 'pages/drupal',52 'pages/drupal',
49 'pages/elixir',53 'pages/elixir',
@@ -53,7 +57,9 @@ · 3 unmodified lines
53 'pages/git',57 'pages/git',
54 'pages/github',58 'pages/github',
55 'pages/go',59 'pages/go',
60 'pages/graphite',
56 'pages/haskell',61 'pages/haskell',
62 'pages/jekyll',
57 'pages/jquery',63 'pages/jquery',
58 'pages/julia',64 'pages/julia',
59 'pages/knockout',65 'pages/knockout',
@@ -77,6 +83,8 @@ · 17 unmodified lines
77 'pages/phpunit',83 'pages/phpunit',
78 'pages/postgres',84 'pages/postgres',
79 'pages/pug',85 'pages/pug',
86 'pages/python',
87 'pages/qt',
80 'pages/ramda',88 'pages/ramda',
81 'pages/rdoc',89 'pages/rdoc',
82 'pages/react_native',90 'pages/react_native',
@@ -94,6 +102,7 @@ · 11 unmodified lines
94 'pages/support_tables',102 'pages/support_tables',
95 'pages/tcl_tk',103 'pages/tcl_tk',
96 'pages/tensorflow',104 'pages/tensorflow',
105 'pages/terraform',
97 'pages/underscore',106 'pages/underscore',
98 'pages/vue',107 'pages/vue',
99 'pages/webpack',108 'pages/webpack',
assets/stylesheets/components/_app.scss+5−5
@@ -1,6 +1,6 @@
1html._booting { background: $contentBackground; }1html._booting { background: var(--contentBackground); }
2body._max-width { background: none; }2body._max-width { background: none; }
3html._booting body._max-width { background: $documentBackground; }3html._booting body._max-width { background: var(--documentBackground); }
4 4
5._app {5._app {
6 position: relative;6 position: relative;
@@ -15,8 +15,8 @@ html._booting body._max-width { background: $documentBackground; } · 8 unmodified lines
15 15
16 ._max-width & {16 ._max-width & {
17 margin: 0 auto;17 margin: 0 auto;
18 max-width: $maxWidth;18 max-width: var(--maxWidth);
19 background: $contentBackground;19 background: var(--contentBackground);
20 box-shadow: 1px 0 $headerBorder, -1px 0 $headerBorder;20 box-shadow: 1px 0 var(--headerBorder), -1px 0 var(--headerBorder);
21 }21 }
22}22}
assets/stylesheets/components/_content.scss+30−32
@@ -4,13 +4,13 @@ · 3 unmodified lines
4 4
5._container {5._container {
6 position: relative;6 position: relative;
7 z-index: $contentZ;7 z-index: var(--contentZ);
8 height: 100%;8 height: 100%;
9 margin-left: $sidebarWidth;9 margin-left: var(--sidebarWidth);
10 pointer-events: none;10 pointer-events: none;
11 @extend %border-box;11 @extend %border-box;
12 12
13 @media #{$mediumScreen} { margin-left: $sidebarMediumWidth; }13 @include mobile { margin-left: var(--sidebarMediumWidth); }
14 14
15 ._sidebar-hidden & { margin-left: 0; }15 ._sidebar-hidden & { margin-left: 0; }
16 body:not(._native-scrollbars) & { -webkit-margin-end: -1px; }16 body:not(._native-scrollbars) & { -webkit-margin-end: -1px; }
@@ -30,7 +30,7 @@ · 13 unmodified lines
30 ._sidebar-hidden &:before {30 ._sidebar-hidden &:before {
31 content: '';31 content: '';
32 display: block;32 display: block;
33 margin-top: $headerHeight;33 margin-top: var(--headerHeight);
34 }34 }
35 35
36 ._overlay-scrollbars & { padding-left: .625rem; }36 ._overlay-scrollbars & { padding-left: .625rem; }
@@ -54,7 +54,7 @@ · 17 unmodified lines
54 font-size: 4rem;54 font-size: 4rem;
55 font-weight: 300;55 font-weight: 300;
56 letter-spacing: -.125rem;56 letter-spacing: -.125rem;
57 color: $loadingText;57 color: var(--loadingText);
58 text-align: center;58 text-align: center;
59 cursor: default;59 cursor: default;
60}60}
@@ -68,7 +68,7 @@ · 7 unmodified lines
68//68//
69 69
70._splash-title {70._splash-title {
71 color: $splashText;71 color: var(--splashText);
72 @extend %loading, %user-select-none;72 @extend %loading, %user-select-none;
73}73}
74 74
@@ -88,7 +88,7 @@ · 13 unmodified lines
88 min-height: calc(100vh - 2.375rem);88 min-height: calc(100vh - 2.375rem);
89 89
90 ._sidebar-hidden & {90 ._sidebar-hidden & {
91 min-height: calc(100vh - 2.375rem - #{$headerHeight});91 min-height: calc(100vh - 2.375rem - var(--headerHeight));
92 }92 }
93}93}
94 94
@@ -140,12 +140,12 @@ · 45 unmodified lines
140 140
141._error-text {141._error-text {
142 margin: 0 0 1rem;142 margin: 0 0 1rem;
143 color: $textColorLight;143 color: var(--textColorLight);
144}144}
145 145
146._error-links {146._error-links {
147 font-size: 1rem;147 font-size: 1rem;
148 font-weight: $boldFontWeight;148 font-weight: var(--boldFontWeight);
149}149}
150 150
151._error-link { padding: 0 .5rem; }151._error-link { padding: 0 .5rem; }
@@ -167,7 +167,7 @@ · 15 unmodified lines
167 height: 1px;167 height: 1px;
168 margin-top: .25rem;168 margin-top: .25rem;
169 margin-left: 1rem;169 margin-left: 1rem;
170 background: $boxBorderLight;170 background: var(--boxBorderLight);
171 }171 }
172}172}
173 173
@@ -197,7 +197,7 @@ · 23 unmodified lines
197._toc-title {197._toc-title {
198 margin: 0 0 .5rem;198 margin: 0 0 .5rem;
199 font-size: inherit;199 font-size: inherit;
200 font-weight: $boldFontWeight;200 font-weight: var(--boldFontWeight);
201}201}
202 202
203._toc-list {203._toc-list {
@@ -252,7 +252,7 @@ · 48 unmodified lines
252._docs-size {252._docs-size {
253 text-align: right;253 text-align: right;
254 254
255 > small { color: $textColorLight; }255 > small { color: var(--textColorLight); }
256}256}
257 257
258._docs-tools {258._docs-tools {
@@ -285,7 +285,7 @@ · 26 unmodified lines
285 padding: 0 .75rem;285 padding: 0 .75rem;
286 }286 }
287 ._btn-link:not(._show) { display: none; }287 ._btn-link:not(._show) { display: none; }
288 ._btn-link._show ~ ._btn-link._show { border-left: 1px solid $boxBorder; }288 ._btn-link._show ~ ._btn-link._show { border-left: 1px solid var(--boxBorder); }
289}289}
290 290
291//291//
@@ -297,7 +297,7 @@ · 5 unmodified lines
297 position: relative;297 position: relative;
298 padding-left: 10em;298 padding-left: 10em;
299 font-size: .8125rem;299 font-size: .8125rem;
300 color: $textColorLight;300 color: var(--textColorLight);
301 301
302 + ._news-row { margin-top: 1em; }302 + ._news-row { margin-top: 1em; }
303 }303 }
@@ -305,7 +305,7 @@ · 1 unmodified line
305 ._news-title {305 ._news-title {
306 display: block;306 display: block;
307 font-size: .875rem;307 font-size: .875rem;
308 color: $textColor;308 color: var(--textColor);
309 }309 }
310 310
311 ._news-date {311 ._news-date {
@@ -372,16 +372,18 @@ · 60 unmodified lines
372// Utilities372// Utilities
373//373//
374 374
375._bold { font-weight: $boldFontWeight; }375._bold { font-weight: var(--boldFontWeight); }
376._note { @extend %note; }376._note { @extend %note; }
377._note-green { @extend %note-green; }377._note-green { @extend %note-green; }
378._label { @extend %label; }378._label { @extend %label; }
379._code { @extend %code; }379._code { @extend %code; }
380._highlight { background: $highlightBackground !important; }380._highlight { background: var(--highlightBackground) !important; }
381 381
382._table { width: 100%; }382._table { width: 100%; }
383._mobile ._table { overflow-x: auto; }383._mobile ._table { overflow-x: auto; }
384 384
385._pre-heading { @extend %pre-heading; }
386
385._pre-clip {387._pre-clip {
386 display: none;388 display: none;
387 position: absolute;389 position: absolute;
@@ -394,12 +396,9 @@ · 6 unmodified lines
394 pre:hover > & { display: block; }396 pre:hover > & { display: block; }
395 &:hover { opacity: 1; }397 &:hover { opacity: 1; }
396 398
397 > svg { @extend %svg-icon; }399 > svg {
398 400 @extend %svg-icon;
399 @if $style == 'dark' {401 fill: var(--absolute);
400 > svg { fill: white; }
401 } @else {
402 > svg { fill: black; }
403 }402 }
404 403
405 &._pre-clip-success > svg,404 &._pre-clip-success > svg,
@@ -417,15 +416,13 @@ · 11 unmodified lines
417 line-height: normal;416 line-height: normal;
418 white-space: nowrap;417 white-space: nowrap;
419 padding: .375rem .675rem;418 padding: .375rem .675rem;
420 background-image: linear-gradient(lighten($boxBackground, 4%), darken($boxBackground, 2%));419 background-color: var(--boxBackground);
421 border: 1px solid $boxBorder;420 border: 1px solid var(--boxBorder);
422 border-radius: 3px;421 border-radius: 3px;
423 box-shadow: 0 1px rgba($boxBorder, .08);
424 cursor: pointer;422 cursor: pointer;
425 423
426 &:active {424 &:active {
427 background-color: $boxBackground;425 box-shadow: inset 0 1px 1px rgba(black, .05), inset 0 1px 4px var(--boxBorder);
428 box-shadow: inset 0 1px 1px rgba(black, .05), inset 0 1px 4px $boxBorder;
429 }426 }
430}427}
431 428
@@ -445,22 +442,23 @@ · 13 unmodified lines
445 442
446._btn-link {443._btn-link {
447 line-height: inherit;444 line-height: inherit;
448 color: $linkColor;445 color: var(--linkColor);
449 text-decoration: $linkTextDecoration;446 text-decoration: var(--linkTextDecoration);
450 447
451 &:hover {448 &:hover {
452 color: $linkColorHover;449 color: var(--linkColorHover);
453 text-decoration: underline;450 text-decoration: underline;
454 }451 }
455}452}
456 453
457._reset-btn,454._reset-btn,
458._reset-btn:hover {455._reset-btn:hover {
459 color: $textColorRed;456 color: var(--textColorRed);
460}457}
461 458
462._github-btn {459._github-btn {
463 display: inline-block;460 display: inline-block;
464 vertical-align: text-top;461 vertical-align: text-top;
465 margin-left: .25rem;462 margin-left: .25rem;
463 background: inherit;
466}464}
assets/stylesheets/components/_fail.scss+2−4
@@ -1,4 +1,4 @@
1._fail {1._fail { // Don't use CSS variables, in case the browser doesn't support them.
2 display: block;2 display: block;
3 position: relative;3 position: relative;
4 top: 1.5rem;4 top: 1.5rem;
@@ -23,7 +23,7 @@ · 18 unmodified lines
23._fail-title {23._fail-title {
24 margin: 0 0 1rem;24 margin: 0 0 1rem;
25 font-size: 1rem;25 font-size: 1rem;
26 font-weight: $boldFontWeight;26 font-weight: bold;
27}27}
28 28
29._fail-text, ._fail-list {29._fail-text, ._fail-list {
@@ -32,5 +32,3 @@ · 2 unmodified lines
32}32}
33 33
34._fail-text:last-child { margin: 0; }34._fail-text:last-child { margin: 0; }
35
36._fail-link { float: right; }
assets/stylesheets/components/_header.scss+26−35
@@ -4,20 +4,20 @@ · 3 unmodified lines
4 4
5._header {5._header {
6 position: absolute;6 position: absolute;
7 z-index: $headerZ;7 z-index: var(--headerZ);
8 top: 0;8 top: 0;
9 left: 0;9 left: 0;
10 display: -ms-flexbox;10 display: -ms-flexbox;
11 display: flex;11 display: flex;
12 width: $sidebarWidth;12 width: var(--sidebarWidth);
13 height: $headerHeight;13 height: var(--headerHeight);
14 background: $headerBackground;14 background: var(--headerBackground);
15 border-bottom: 1px solid $headerBorder;15 border-bottom: 1px solid var(--headerBorder);
16 border-right: 1px solid $headerBorder;16 border-right: 1px solid var(--headerBorder);
17 @extend %border-box;17 @extend %border-box;
18 @extend %user-select-none;18 @extend %user-select-none;
19 19
20 @media #{$mediumScreen} { width: $sidebarMediumWidth; }20 @include mobile { width: var(--sidebarMediumWidth); }
21}21}
22 22
23._header-left {23._header-left {
@@ -35,7 +35,7 @@ · 11 unmodified lines
35 flex: 0 0 auto;35 flex: 0 0 auto;
36 width: 2.25rem;36 width: 2.25rem;
37 height: 100%;37 height: 100%;
38 color: $textColorLight;38 color: var(--textColorLight);
39 text-align: center;39 text-align: center;
40 40
41 &[hidden] { display: none; }41 &[hidden] { display: none; }
@@ -67,8 +67,8 @@ · 25 unmodified lines
67 word-wrap: normal;67 word-wrap: normal;
68 overflow-wrap: normal;68 overflow-wrap: normal;
69 font-size: .875rem;69 font-size: .875rem;
70 background: $contentBackground;70 background: var(--contentBackground);
71 border: 1px solid $headerBorder;71 border: 1px solid var(--headerBorder);
72 border-radius: 3px;72 border-radius: 3px;
73 box-shadow: -1px 1px 1px rgba(black, .05);73 box-shadow: -1px 1px 1px rgba(black, .05);
74 transition: all 0ms cubic-bezier(0.23, 1, 0.32, 1) 1ms;74 transition: all 0ms cubic-bezier(0.23, 1, 0.32, 1) 1ms;
@@ -98,10 +98,10 @@ · 23 unmodified lines
98 margin: 0;98 margin: 0;
99 line-height: 1.5rem;99 line-height: 1.5rem;
100 font-size: 1rem;100 font-size: 1rem;
101 font-weight: $boldFontWeight;101 font-weight: var(--boldFontWeight);
102 letter-spacing: -.5px;102 letter-spacing: -.5px;
103 background: $sidebarBackground;103 background: var(--sidebarBackground);
104 border-bottom: 1px solid $sidebarBorder;104 border-bottom: 1px solid var(--sidebarBorder);
105 border-radius: 2px 2px 0 0;105 border-radius: 2px 2px 0 0;
106}106}
107 107
@@ -109,7 +109,7 @@ · 1 unmodified line
109._menu-title-link:hover {109._menu-title-link:hover {
110 display: block;110 display: block;
111 padding: .5rem 1rem;111 padding: .5rem 1rem;
112 color: $focusText;112 color: var(--focusText);
113 text-decoration: none;113 text-decoration: none;
114}114}
115 115
@@ -121,9 +121,9 @@ · 5 unmodified lines
121 text-decoration: none;121 text-decoration: none;
122 122
123 &:hover {123 &:hover {
124 color: $focusText;124 color: var(--focusText);
125 text-decoration: none;125 text-decoration: none;
126 background: $sidebarBackground;126 background: var(--sidebarBackground);
127 }127 }
128 128
129 &:last-child { border-radius: 0 0 2px 2px; }129 &:last-child { border-radius: 0 0 2px 2px; }
@@ -151,11 +151,7 @@ · 21 unmodified lines
151 opacity: .42;151 opacity: .42;
152 @extend %svg-icon;152 @extend %svg-icon;
153 153
154 @if $style == 'dark' {154 fill: var(--absolute);
155 fill: white;
156 } @else {
157 fill: black;
158 }
159 }155 }
160}156}
161 157
@@ -166,21 +162,19 @@ · 4 unmodified lines
166 height: 100%;162 height: 100%;
167 padding: 0 .75rem 1px 1.75rem;163 padding: 0 .75rem 1px 1.75rem;
168 font-size: .875rem;164 font-size: .875rem;
169 background: $contentBackground;165 background: var(--contentBackground);
170 border: 1px solid;166 border: 1px solid;
171 border-color: darken($headerBorder, 2%);167 border-color: var(--searchBorder);
172 border-radius: 3px;168 border-radius: 3px;
173 169
174 &:focus {170 &:focus {
175 outline: 0;171 outline: 0;
176 @if $inputFocusBorder {172 border-color: var(--inputFocusBorder);
177 border-color: $inputFocusBorder;173 box-shadow: 0 0 1px var(--inputFocusBorder);
178 box-shadow: 0 0 1px $inputFocusBorder;
179 }
180 }174 }
181 175
182 &[disabled] {176 &[disabled] {
183 background: $sidebarBackground;177 background: var(--sidebarBackground);
184 cursor: not-allowed;178 cursor: not-allowed;
185 }179 }
186}180}
@@ -202,13 +196,9 @@ · 15 unmodified lines
202 top: .5rem;196 top: .5rem;
203 left: .375rem;197 left: .375rem;
204 @extend %svg-icon;198 @extend %svg-icon;
199 fill: var(--absolute);
205 }200 }
206 201
207 @if $style == 'dark' {
208 > svg { fill: white; }
209 } @else {
210 > svg { fill: black; }
211 }
212 ._search-active > & { display: block; }202 ._search-active > & { display: block; }
213}203}
214 204
@@ -222,8 +212,9 @@ · 7 unmodified lines
222 line-height: 1.25rem;212 line-height: 1.25rem;
223 max-width: 50%;213 max-width: 50%;
224 font-size: .8125rem;214 font-size: .8125rem;
225 color: $textColorLight;215 color: var(--textColorLight);
226 background: darken($headerBackground, 5%);216 background: var(--searchTagBackground);
227 border-radius: 2px;217 border-radius: 2px;
218 cursor: pointer;
228 @extend %truncate-text;219 @extend %truncate-text;
229}220}
assets/stylesheets/components/_mobile.scss+2−2
@@ -4,7 +4,7 @@ · 3 unmodified lines
4 4
5._mobile {5._mobile {
6 font-size: 100%;6 font-size: 100%;
7 background: $contentBackground;7 background: var(--contentBackground);
8 8
9 ._hide-on-mobile { display: none; }9 ._hide-on-mobile { display: none; }
10 10
@@ -18,7 +18,7 @@ · 7 unmodified lines
18 18
19 ._container {19 ._container {
20 margin: 0;20 margin: 0;
21 padding-top: $headerHeight;21 padding-top: var(--headerHeight);
22 }22 }
23 23
24 ._content {24 ._content {
assets/stylesheets/components/_notice.scss+5−5
@@ -1,15 +1,15 @@
1._notice {1._notice {
2 position: absolute;2 position: absolute;
3 z-index: $noticeZ;3 z-index: var(--noticeZ);
4 bottom: 0;4 bottom: 0;
5 left: $sidebarWidth;5 left: var(--sidebarWidth);
6 right: 0;6 right: 0;
7 height: 2.5rem;7 height: 2.5rem;
8 padding: 0 1.25rem;8 padding: 0 1.25rem;
9 background: $noticeBackground;9 background: var(--noticeBackground);
10 box-shadow: inset 0 1px $noticeBorder;10 box-shadow: inset 0 1px var(--noticeBorder);
11 11
12 @media #{$mediumScreen} { left: $sidebarMediumWidth; }12 @include mobile { left: var(--sidebarMediumWidth); }
13 13
14 ._sidebar-hidden & { left: 0; }14 ._sidebar-hidden & { left: 0; }
15 15
assets/stylesheets/components/_notif.scss+16−12
@@ -7,9 +7,9 @@ · 6 unmodified lines
7 max-width: 90%;7 max-width: 90%;
8 padding: .625rem 1rem;8 padding: .625rem 1rem;
9 font-size: .75rem;9 font-size: .75rem;
10 color: $notifColor;10 color: var(--notifColor);
11 background: $notifBackground;11 background: var(--notifBackground);
12 border: $notifBorder;12 border: var(--notifBorder);
13 border-radius: .25rem;13 border-radius: .25rem;
14 transition: opacity .2s;14 transition: opacity .2s;
15 opacity: 0;15 opacity: 0;
@@ -30,7 +30,7 @@ · 14 unmodified lines
30 30
31._notif-info {31._notif-info {
32 float: right;32 float: right;
33 color: $notifColorLight;33 color: var(--notifColorLight);
34}34}
35 35
36._notif-link,36._notif-link,
@@ -69,13 +69,13 @@ · 32 unmodified lines
69 &::-webkit-scrollbar { width: 10px !important; }69 &::-webkit-scrollbar { width: 10px !important; }
70 70
71 &::-webkit-scrollbar-track {71 &::-webkit-scrollbar-track {
72 background: $notifBackground !important;72 background: var(--notifBackground) !important;
73 border: 0 !important;73 border: 0 !important;
74 border-radius: 5px !important;74 border-radius: 5px !important;
75 }75 }
76 76
77 &::-webkit-scrollbar-thumb {77 &::-webkit-scrollbar-thumb {
78 border: 3px solid $notifBackground !important;78 border: 3px solid var(--notifBackground) !important;
79 79
80 &:hover, &:active { border-width: 2px !important; }80 &:hover, &:active { border-width: 2px !important; }
81 }81 }
@@ -90,7 +90,7 @@ · 8 unmodified lines
90 > ._news-row {90 > ._news-row {
91 line-height: 1.125rem;91 line-height: 1.125rem;
92 font-size: .6875rem;92 font-size: .6875rem;
93 color: $notifColorLight;93 color: var(--notifColorLight);
94 margin-bottom: .25rem;94 margin-bottom: .25rem;
95 95
96 + ._news-row { margin-top: .625rem; }96 + ._news-row { margin-top: .625rem; }
@@ -107,7 +107,7 @@ · 10 unmodified lines
107 ._news-date {107 ._news-date {
108 float: right;108 float: right;
109 margin-left: 1rem;109 margin-left: 1rem;
110 font-weight: $boldFontWeight;110 font-weight: var(--boldFontWeight);
111 }111 }
112 112
113 code {113 code {
@@ -128,9 +128,13 @@ · 14 unmodified lines
128}128}
129 129
130._notif-tip {130._notif-tip {
131 color: $textColor;131 color: var(--textColor);
132 background: $tipBackground;132 background: var(--tipBackground);
133 border: $tipBorder;133 border: var(--tipBorder);
134 134
135 ._notif-info { color: $textColorLight; }135 ._notif-info { color: var(--textColorLight); }
136}
137
138._notif-right {
139 float: right;
136}140}
assets/stylesheets/components/_page.scss+4−4
@@ -23,7 +23,7 @@ · 22 unmodified lines
23 max-width: 100%;23 max-width: 100%;
24 margin-bottom: 1em;24 margin-bottom: 1em;
25 padding: 1px;25 padding: 1px;
26 border: 1px dotted $boxBorder;26 border: 1px dotted var(--boxBorder);
27 border-radius: 3px;27 border-radius: 3px;
28 @extend %border-box;28 @extend %border-box;
29 }29 }
@@ -50,7 +50,7 @@ · 20 unmodified lines
50 display: inline-block;50 display: inline-block;
51 vertical-align: top;51 vertical-align: top;
52 padding: 0 .5rem;52 padding: 0 .5rem;
53 background: $contentBackground;53 background: var(--contentBackground);
54 @extend %internal-link;54 @extend %internal-link;
55 55
56 & + & { margin-left: .75rem; }56 & + & { margin-left: .75rem; }
@@ -66,7 +66,7 @@ · 9 unmodified lines
66 clear: both;66 clear: both;
67 margin: 2rem 0 1.5rem;67 margin: 2rem 0 1.5rem;
68 font-size: .75rem;68 font-size: .75rem;
69 color: $textColorLight;69 color: var(--textColorLight);
70 text-align: center;70 text-align: center;
71 -webkit-font-smoothing: subpixel-antialiased;71 -webkit-font-smoothing: subpixel-antialiased;
72 72
@@ -78,7 +78,7 @@ · 5 unmodified lines
78 display: inline-block;78 display: inline-block;
79 margin: 0;79 margin: 0;
80 padding: .25rem .75rem;80 padding: .25rem .75rem;
81 background: $labelBackground;81 background: var(--labelBackground);
82 border-radius: 3px;82 border-radius: 3px;
83}83}
84 84
assets/stylesheets/components/_path.scss+6−6
@@ -1,17 +1,17 @@
1._path {1._path {
2 position: absolute;2 position: absolute;
3 z-index: $noticeZ - 1;3 z-index: var(--headerZ);
4 bottom: 0;4 bottom: 0;
5 left: $sidebarWidth;5 left: var(--sidebarWidth);
6 right: 0;6 right: 0;
7 height: 2rem;7 height: 2rem;
8 line-height: 2rem;8 line-height: 2rem;
9 padding: 0 .375rem;9 padding: 0 .375rem;
10 font-size: .875rem;10 font-size: .875rem;
11 background: $pathBackground;11 background: var(--pathBackground);
12 box-shadow: inset 0 1px $pathBorder;12 box-shadow: inset 0 1px var(--pathBorder);
13 13
14 @media #{$mediumScreen} { left: $sidebarMediumWidth; }14 @include mobile { left: var(--sidebarMediumWidth); }
15 15
16 ._sidebar-hidden & { left: 0; }16 ._sidebar-hidden & { left: 0; }
17 17
@@ -24,7 +24,7 @@ · 6 unmodified lines
24 display: inline-block;24 display: inline-block;
25 vertical-align: top;25 vertical-align: top;
26 padding: 0 .375rem;26 padding: 0 .375rem;
27 color: $textColor;27 color: var(--textColor);
28 text-decoration: none;28 text-decoration: none;
29 29
30 &:first-child:before {30 &:first-child:before {
assets/stylesheets/components/_prism.scss+25−32
@@ -1,9 +1,26 @@
1html {
2 --prismValue: #905;
3 --prismText: #5e8e01;
4 --prismOperator: #a67f59;
5 --prismKeyword: #0070a3;
6 --prismFunction: #dd4a68;
7 --prismVariable: #e90;
8}
9html._theme-dark {
10 --prismValue: #eb8160;
11 --prismText: #ddcf88;
12 --prismOperator: #b1c676;
13 --prismKeyword: #91b3ed;
14 --prismFunction: #c79e6b;
15 --prismVariable: #e9c062;
16}
17
1.token.comment,18.token.comment,
2.token.prolog,19.token.prolog,
3.token.doctype,20.token.doctype,
4.token.cdata,21.token.cdata,
5.token.punctuation {22.token.punctuation {
6 color: $textColorLight;23 color: var(--textColorLight);
7}24}
8 25
9.namespace {26.namespace {
@@ -17,11 +34,7 @@ · 7 unmodified lines
17.token.constant,34.token.constant,
18.token.symbol,35.token.symbol,
19.token.deleted {36.token.deleted {
20 @if $style == 'dark' {37 color: var(--prismValue);
21 color: #eb8160;
22 } @else {
23 color: #905;
24 }
25}38}
26 39
27.token.selector,40.token.selector,
@@ -30,11 +43,7 @@ · 2 unmodified lines
30.token.char,43.token.char,
31.token.builtin,44.token.builtin,
32.token.inserted {45.token.inserted {
33 @if $style == 'dark' {46 color: var(--prismText);
34 color: #ddcf88;
35 } @else {
36 color: #5e8e01;
37 }
38}47}
39 48
40.token.operator,49.token.operator,
@@ -42,44 +51,28 @@ · 1 unmodified line
42.token.url,51.token.url,
43.language-css .token.string,52.language-css .token.string,
44.style .token.string {53.style .token.string {
45 @if $style == 'dark' {54 color: var(--prismOperator);
46 color: #b1c676;
47 } @else {
48 color: #a67f59;
49 }
50}55}
51 56
52.token.atrule,57.token.atrule,
53.token.attr-value,58.token.attr-value,
54.token.keyword {59.token.keyword {
55 @if $style == 'dark' {60 color: var(--prismKeyword);
56 color: #91b3ed;
57 } @else {
58 color: #0070a3;
59 }
60}61}
61 62
62.token.function {63.token.function {
63 @if $style == 'dark' {64 color: var(--prismFunction);
64 color: #c79e6b;
65 } @else {
66 color: #dd4a68;
67 }
68}65}
69 66
70.token.regex,67.token.regex,
71.token.important,68.token.important,
72.token.variable {69.token.variable {
73 @if $style == 'dark' {70 color: var(--prismVariable);
74 color: #e9c062;
75 } @else {
76 color: #e90;
77 }
78}71}
79 72
80.token.important,73.token.important,
81.token.bold {74.token.bold {
82 font-weight: $boldFontWeight;75 font-weight: var(--boldFontWeight);
83}76}
84 77
85.token.italic {78.token.italic {
assets/stylesheets/components/_settings.scss+12−12
@@ -7,15 +7,15 @@ · 6 unmodified lines
7 position: absolute;7 position: absolute;
8 top: 0;8 top: 0;
9 bottom: 0;9 bottom: 0;
10 z-index: $headerZ;10 z-index: var(--headerZ);
11 11
12 &._in { display: block; }12 &._in { display: block; }
13 13
14 > ._header { justify-content: space-between; }14 > ._header { justify-content: space-between; }
15 15
16 &._dirty > ._header {16 &._dirty > ._header {
17 background: $noteGreenBackground;17 background: var(--noteGreenBackground);
18 border-color: $noteGreenBorder;18 border-color: var(--noteGreenBorder);
19 }19 }
20}20}
21 21
@@ -37,7 +37,7 @@ · 15 unmodified lines
37 padding-right: .5rem;37 padding-right: .5rem;
38 line-height: inherit;38 line-height: inherit;
39 font-size: inherit;39 font-size: inherit;
40 font-weight: $boldFontWeight;40 font-weight: var(--boldFontWeight);
41 text-align: right;41 text-align: right;
42 @extend %border-box;42 @extend %border-box;
43}43}
@@ -52,7 +52,7 @@ · 8 unmodified lines
52 52
53 > small {53 > small {
54 display: block;54 display: block;
55 color: $textColorLight;55 color: var(--textColorLight);
56 margin-left: 1.75rem;56 margin-left: 1.75rem;
57 }57 }
58 58
@@ -62,7 +62,7 @@ · 3 unmodified lines
62 }62 }
63}63}
64 64
65@media (max-width: $maxWidth) {65@media (max-width: 80rem) {
66 ._setting-max-width { display: none; }66 ._setting-max-width { display: none; }
67}67}
68 68
@@ -80,7 +80,7 @@ · 11 unmodified lines
80 line-height: 1.5rem;80 line-height: 1.5rem;
81 padding: 0 .75rem;81 padding: 0 .75rem;
82 font-size: .875rem;82 font-size: .875rem;
83 font-weight: $boldFontWeight;83 font-weight: var(--boldFontWeight);
84 color: inherit;84 color: inherit;
85 text-align: left;85 text-align: left;
86 cursor: pointer;86 cursor: pointer;
@@ -119,12 +119,12 @@ · 32 unmodified lines
119 position: relative;119 position: relative;
120 vertical-align: top;120 vertical-align: top;
121 padding: 0 .75rem;121 padding: 0 .75rem;
122 line-height: $headerHeight;122 line-height: var(--headerHeight);
123 color: $textColorLight;123 color: var(--textColorLight);
124 124
125 &.active {125 &.active {
126 color: $textColor;126 color: var(--textColor);
127 font-weight: $boldFontWeight;127 font-weight: var(--boldFontWeight);
128 box-shadow: inset 0 -2px $linkColor;128 box-shadow: inset 0 -2px var(--linkColor);
129 }129 }
130}130}
assets/stylesheets/components/_sidebar.scss+37−39
@@ -4,14 +4,14 @@ · 3 unmodified lines
4 4
5._sidebar {5._sidebar {
6 position: absolute;6 position: absolute;
7 z-index: $sidebarZ;7 z-index: var(--sidebarZ);
8 top: 0;8 top: 0;
9 bottom: 0;9 bottom: 0;
10 left: 0;10 left: 0;
11 overflow-x: hidden;11 overflow-x: hidden;
12 overflow-y: scroll;12 overflow-y: scroll;
13 padding-top: $headerHeight;13 padding-top: var(--headerHeight);
14 background: $sidebarBackground;14 background: var(--sidebarBackground);
15 background-clip: content-box;15 background-clip: content-box;
16 -webkit-overflow-scrolling: touch;16 -webkit-overflow-scrolling: touch;
17 -ms-overflow-style: none; // IE 10 doesn't support pointer-events17 -ms-overflow-style: none; // IE 10 doesn't support pointer-events
@@ -22,13 +22,13 @@ · 4 unmodified lines
22 22
23 ._overlay-scrollbars & {23 ._overlay-scrollbars & {
24 padding-top: 0;24 padding-top: 0;
25 top: $headerHeight;25 top: var(--headerHeight);
26 }26 }
27 27
28 body:not(._native-scrollbars) & {28 body:not(._native-scrollbars) & {
29 &::-webkit-scrollbar { width: 10px; }29 &::-webkit-scrollbar { width: 10px; }
30 &::-webkit-scrollbar-track {30 &::-webkit-scrollbar-track {
31 background: $contentBackground;31 background: var(--contentBackground);
32 border: 0;32 border: 0;
33 }33 }
34 &::-webkit-scrollbar-thumb {34 &::-webkit-scrollbar-thumb {
@@ -52,10 +52,10 @@ · 17 unmodified lines
52 52
53._resizer {53._resizer {
54 position: absolute;54 position: absolute;
55 z-index: $sidebarZ + 1;55 z-index: var(--headerZ);
56 top: $headerHeight;56 top: var(--headerHeight);
57 bottom: $headerHeight;57 bottom: var(--headerHeight);
58 left: $sidebarWidth;58 left: var(--sidebarWidth);
59 margin-left: -2px;59 margin-left: -2px;
60 width: 3px;60 width: 3px;
61 cursor: col-resize;61 cursor: col-resize;
@@ -72,11 +72,11 @@ · 10 unmodified lines
72 margin: 0;72 margin: 0;
73 padding: 0;73 padding: 0;
74 list-style: none;74 list-style: none;
75 width: $sidebarWidth;75 width: var(--sidebarWidth);
76 box-shadow: inset -1px 0 $sidebarBorder;76 box-shadow: inset -1px 0 var(--sidebarBorder);
77 @extend %border-box;77 @extend %border-box;
78 78
79 @media #{$mediumScreen} { width: $sidebarMediumWidth; }79 @include mobile { width: var(--sidebarMediumWidth); }
80 80
81 ._sidebar > & { min-height: 100%; }81 ._sidebar > & { min-height: 100%; }
82 82
@@ -89,7 +89,7 @@ · 6 unmodified lines
89 padding: 0 .75rem 0 2.125rem;89 padding: 0 .75rem 0 2.125rem;
90 line-height: 2rem;90 line-height: 2rem;
91 font-size: .75rem;91 font-size: .75rem;
92 color: $textColorLight;92 color: var(--textColorLight);
93 text-transform: uppercase;93 text-transform: uppercase;
94 cursor: default;94 cursor: default;
95}95}
@@ -110,8 +110,8 @@ · 14 unmodified lines
110 line-height: 1.5rem;110 line-height: 1.5rem;
111 font-size: .875rem;111 font-size: .875rem;
112 cursor: default;112 cursor: default;
113 background: $sidebarBackground;113 background: var(--sidebarBackground);
114 box-shadow: inset -1px 0 $sidebarBorder;114 box-shadow: inset -1px 0 var(--sidebarBorder);
115 @extend %truncate-text;115 @extend %truncate-text;
116 116
117 &, &:hover {117 &, &:hover {
@@ -123,16 +123,16 @@ · 5 unmodified lines
123 &.focus:hover,123 &.focus:hover,
124 &.active,124 &.active,
125 &.active:hover {125 &.active:hover {
126 color: $focusText;126 color: var(--focusText);
127 background: $focusBackground;127 background: var(--focusBackground);
128 box-shadow: inset -1px 0 $focusBorder;128 box-shadow: inset -1px 0 var(--focusBorder);
129 }129 }
130 130
131 &.active,131 &.active,
132 &.active:hover {132 &.active:hover {
133 color: $selectionText;133 color: var(--selectionText);
134 background: $selectionBackground;134 background: var(--selectionBackground);
135 box-shadow: inset -1px 0 $selectionBorder;135 box-shadow: inset -1px 0 var(--selectionBorder);
136 }136 }
137 137
138 &:before {138 &:before {
@@ -160,7 +160,7 @@ · 21 unmodified lines
160}160}
161 161
162._list-count {162._list-count {
163 color: $textColorLighter;163 color: var(--textColorLighter);
164 pointer-events: none;164 pointer-events: none;
165 165
166 ._list-disabled:hover > & { display: none; }166 ._list-disabled:hover > & { display: none; }
@@ -168,7 +168,7 @@ · 1 unmodified line
168 168
169._list-enable {169._list-enable {
170 display: none;170 display: none;
171 color: $linkColor;171 color: var(--linkColor);
172 cursor: pointer;172 cursor: pointer;
173 173
174 &:hover { text-decoration: underline; }174 &:hover { text-decoration: underline; }
@@ -188,7 +188,7 @@ · 13 unmodified lines
188._list-disabled {188._list-disabled {
189 @extend %_list-dir;189 @extend %_list-dir;
190 190
191 &, &:hover { color: $textColorLight; }191 &, &:hover { color: var(--textColorLight); }
192 &:before { opacity: .7; }192 &:before { opacity: .7; }
193}193}
194 194
@@ -200,11 +200,9 @@ · 5 unmodified lines
200 width: 1rem;200 width: 1rem;
201 height: 1rem;201 height: 1rem;
202 cursor: pointer;202 cursor: pointer;
203 fill: black;203 fill: var(--absolute);
204 opacity: .4;204 opacity: .4;
205 205
206 @if $style == 'dark' { fill: white; }
207
208 &:hover { opacity: .65; }206 &:hover { opacity: .65; }
209 207
210 ._list-rdir > & {208 ._list-rdir > & {
@@ -239,11 +237,11 @@ · 28 unmodified lines
239//237//
240 238
241._list-pagelink {239._list-pagelink {
242 color: $linkColor;240 color: var(--linkColor);
243 cursor: pointer;241 cursor: pointer;
244 242
245 &:hover {243 &:hover {
246 color: $linkColorHover;244 color: var(--linkColorHover);
247 text-decoration: underline;245 text-decoration: underline;
248 }246 }
249}247}
@@ -275,9 +273,9 @@ · 25 unmodified lines
275 left: .75rem;273 left: .75rem;
276 width: .75rem;274 width: .75rem;
277 height: 1px;275 height: 1px;
278 background: rgba($selectionText, .9);276 background: var(--transparentSelectionText);
279 box-shadow: 0 -3px rgba($selectionText, .9), // top line277 box-shadow: 0 -3px var(--transparentSelectionText), // top line
280 0 3px rgba($selectionText, .9); // bottom line278 0 3px var(--transparentSelectionText); // bottom line
281 }279 }
282}280}
283 281
@@ -289,7 +287,7 @@ · 5 unmodified lines
289 padding: .5rem .75rem;287 padding: .5rem .75rem;
290 line-height: 1.25rem;288 line-height: 1.25rem;
291 font-size: .8125rem;289 font-size: .8125rem;
292 color: $textColorLight;290 color: var(--textColorLight);
293 291
294 & + & { padding-top: 0; }292 & + & { padding-top: 0; }
295}293}
@@ -303,9 +301,9 @@ · 7 unmodified lines
303._list-hover.clone {301._list-hover.clone {
304 position: fixed;302 position: fixed;
305 overflow: visible;303 overflow: visible;
306 z-index: $hoverZ;304 z-index: var(--hoverZ);
307 left: 0;305 left: 0;
308 min-width: $sidebarWidth;306 min-width: var(--sidebarWidth);
309 padding: .25rem .75rem;307 padding: .25rem .75rem;
310 pointer-events: none;308 pointer-events: none;
311 -webkit-font-smoothing: subpixel-antialiased;309 -webkit-font-smoothing: subpixel-antialiased;
@@ -313,7 +311,7 @@ · 1 unmodified line
313 transform: translate3d(0, 0, 0);311 transform: translate3d(0, 0, 0);
314 @extend %border-box;312 @extend %border-box;
315 313
316 @media #{$mediumScreen} { min-width: $sidebarMediumWidth; }314 @include mobile { min-width: var(--sidebarMediumWidth); }
317 315
318 > ._list-text { display: inline; }316 > ._list-text { display: inline; }
319 317
@@ -346,10 +344,10 @@ · 26 unmodified lines
346 padding: .5rem .75rem .25rem .75rem;344 padding: .5rem .75rem .25rem .75rem;
347 line-height: 1.5rem;345 line-height: 1.5rem;
348 font-size: .75rem;346 font-size: .75rem;
349 font-weight: $bolderFontWeight;347 font-weight: var(--bolderFontWeight);
350 color: $textColorLight;348 color: var(--textColorLight);
351 text-transform: uppercase;349 text-transform: uppercase;
352 background: linear-gradient(to bottom, $sidebarBackground, $sidebarBackground 75%, rgba($sidebarBackground, 0));350 background: linear-gradient(to bottom, var(--sidebarBackground), var(--sidebarBackground) 75%, var(--transparentSidebarBackground));
353 cursor: default;351 cursor: default;
354}352}
355 353
assets/stylesheets/global/_base.scss+40−31
@@ -1,9 +1,13 @@
1html {1html {
2 height: 100%;2 height: 100%;
3 font-size: 100%;3 font-size: 100%;
4 background: $documentBackground;4 background: #fff; // fallback to show the error message to browsers that don't support CSS variables.
5 background: var(--documentBackground);
5 6
6 @media #{$mediumScreen} { font-size: 93.75%; }7 @include mobile { font-size: 93.75%; }
8
9 @include print { background: none; }
10 @import 'global/print';
7}11}
8 12
9body {13body {
@@ -14,10 +18,11 @@ body { · 4 unmodified lines
14 font-weight: normal;18 font-weight: normal;
15 font-family: $baseFont;19 font-family: $baseFont;
16 line-height: 1.7;20 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);
18 word-wrap: break-word;23 word-wrap: break-word;
19 overflow-wrap: break-word;24 overflow-wrap: break-word;
20 background: $contentBackground;25 background: var(--contentBackground);
21 touch-action: manipulation;26 touch-action: manipulation;
22 -webkit-tap-highlight-color: rgba(black, 0);27 -webkit-tap-highlight-color: rgba(black, 0);
23 -webkit-touch-callout: none;28 -webkit-touch-callout: none;
@@ -26,11 +31,11 @@ body { · 2 unmodified lines
26}31}
27 32
28a {33a {
29 color: $linkColor;34 color: var(--linkColor);
30 text-decoration: $linkTextDecoration;35 text-decoration: var(--linkTextDecoration);
31 36
32 &:hover {37 &:hover {
33 color: $linkColorHover;38 color: var(--linkColorHover);
34 text-decoration: underline;39 text-decoration: underline;
35 }40 }
36}41}
@@ -44,7 +49,7 @@ img { · 7 unmodified lines
44h1, h2, h3, h4, h5, h6 {49h1, h2, h3, h4, h5, h6 {
45 margin: 1.5em 0 1em;50 margin: 1.5em 0 1em;
46 line-height: 1.3;51 line-height: 1.3;
47 font-weight: $bolderFontWeight;52 font-weight: var(--bolderFontWeight);
48}53}
49 54
50h1 { font-size: 1.5em; }55h1 { font-size: 1.5em; }
@@ -56,7 +61,7 @@ h5, h6 { font-size: 1em; } · 5 unmodified lines
56p { margin: 0 0 1em; }61p { margin: 0 0 1em; }
57p:last-child { margin-bottom: 0; }62p:last-child { margin-bottom: 0; }
58 63
59b, strong { font-weight: $boldFontWeight; }64b, strong { font-weight: var(--boldFontWeight); }
60 65
61small { font-size: .9em; }66small { font-size: .9em; }
62 67
@@ -76,7 +81,7 @@ li > ul, li > ol, dd > ul, dd > ol { margin: .5em 0; } · 13 unmodified lines
76li > p { margin-bottom: .25em; }81li > p { margin-bottom: .25em; }
77 82
78dl { margin: 1.5em 0; }83dl { margin: 1.5em 0; }
79dt { font-weight: $boldFontWeight; }84dt { font-weight: var(--boldFontWeight); }
80dd {85dd {
81 margin: .375em;86 margin: .375em;
82 padding-left: 1em;87 padding-left: 1em;
@@ -88,15 +93,15 @@ dfn, var { font-style: normal; } · 5 unmodified lines
88 93
89abbr, acronym, dfn {94abbr, acronym, dfn {
90 cursor: help;95 cursor: help;
91 border-bottom: 1px dotted $textColor;96 border-bottom: 1px dotted var(--textColor);
92}97}
93 98
94pre, code, samp, %pre, %code {99pre, code, samp, %pre, %code {
95 font-family: $monoFont;100 font-family: var(--monoFont);
96 font-weight: normal;101 font-weight: normal;
97 font-style: normal;102 font-style: normal;
98 font-size: .9em;103 font-size: .9em;
99 color: $textColor;104 color: var(--textColor);
100 white-space: pre-wrap;105 white-space: pre-wrap;
101 direction: ltr;106 direction: ltr;
102 -moz-tab-size: 2;107 -moz-tab-size: 2;
@@ -118,14 +123,14 @@ a > code { color: inherit; } · 15 unmodified lines
118table {123table {
119 margin: 1.5em 0;124 margin: 1.5em 0;
120 background: none;125 background: none;
121 border: 1px solid $boxBorder;126 border: 1px solid var(--boxBorder);
122 border-collapse: separate;127 border-collapse: separate;
123 border-spacing: 0;128 border-spacing: 0;
124 border-radius: 3px;129 border-radius: 3px;
125}130}
126 131
127caption {132caption {
128 font-weight: $boldFontWeight;133 font-weight: var(--boldFontWeight);
129 padding: 0 .7em .3em;134 padding: 0 .7em .3em;
130}135}
131 136
@@ -139,15 +144,15 @@ th, td { · 7 unmodified lines
139}144}
140 145
141th {146th {
142 font-weight: $boldFontWeight;147 font-weight: var(--boldFontWeight);
143 border: 0;148 border: 0;
144 border-bottom: 1px solid $boxBorder;149 border-bottom: 1px solid var(--boxBorder);
145 border-radius: 0;150 border-radius: 0;
146 @extend %heading-box;151 @extend %heading-box;
147 152
148 &:empty { background: none; }153 &:empty { background: none; }
149 154
150 + th, + td { border-left: 1px solid $boxBorder; }155 + th, + td { border-left: 1px solid var(--boxBorder); }
151 156
152 tr:first-child > &:first-child { border-top-left-radius: 3px; }157 tr:first-child > &:first-child { border-top-left-radius: 3px; }
153 tr:first-child > &:last-child { border-top-right-radius: 3px; }158 tr:first-child > &:last-child { border-top-right-radius: 3px; }
@@ -158,10 +163,10 @@ th { · 4 unmodified lines
158}163}
159 164
160td {165td {
161 background: $contentBackground;166 background: var(--contentBackground);
162 border-bottom: 1px solid $boxBorderLight;167 border-bottom: 1px solid var(--boxBorderLight);
163 168
164 + td { border-left: 1px solid $boxBorderLight; }169 + td { border-left: 1px solid var(--boxBorderLight); }
165 tr:last-child > & { border-bottom: 0; }170 tr:last-child > & { border-bottom: 0; }
166 171
167 > pre:only-child, > p:only-child, > ul:only-child, > ol:only-child {172 > pre:only-child, > p:only-child, > ul:only-child, > ol:only-child {
@@ -187,7 +192,7 @@ input, button { · 19 unmodified lines
187 margin: 0;192 margin: 0;
188 font-family: inherit;193 font-family: inherit;
189 font-size: 100%;194 font-size: 100%;
190 color: $textColor;195 color: var(--textColor);
191 line-height: normal;196 line-height: normal;
192 @extend %border-box;197 @extend %border-box;
193}198}
@@ -215,6 +220,10 @@ button:focus { · 21 unmodified lines
215 outline: -webkit-focus-ring-color auto 5px;220 outline: -webkit-focus-ring-color auto 5px;
216}221}
217 222
223img, iframe {
224 background: var(--externalsBackground);
225}
226
218input[type="search"]::-webkit-search-cancel-button,227input[type="search"]::-webkit-search-cancel-button,
219input[type="search"]::-webkit-search-decoration {228input[type="search"]::-webkit-search-decoration {
220 -webkit-appearance: none;229 -webkit-appearance: none;
@@ -227,9 +236,9 @@ input[type="search"]::-webkit-search-decoration { · 6 unmodified lines
227 border: 0 !important;236 border: 0 !important;
228}237}
229 238
230::-webkit-input-placeholder { color: $textColorLighter; }239::-webkit-input-placeholder { color: var(--textColorLighter); }
231::-moz-placeholder { color: $textColorLighter; opacity: 1; }240::-moz-placeholder { color: var(--textColorLighter); opacity: 1; }
232:-ms-input-placeholder { color: $textColorLighter; }241:-ms-input-placeholder { color: var(--textColorLighter); }
233 242
234body:not(._native-scrollbars) {243body:not(._native-scrollbars) {
235 *::-webkit-scrollbar { -webkit-appearance: none; }244 *::-webkit-scrollbar { -webkit-appearance: none; }
@@ -240,12 +249,12 @@ body:not(._native-scrollbars) { · 4 unmodified lines
240 *::-webkit-scrollbar-corner { display: none; }249 *::-webkit-scrollbar-corner { display: none; }
241 250
242 *::-webkit-scrollbar-track {251 *::-webkit-scrollbar-track {
243 background: $contentBackground;252 background: var(--contentBackground);
244 border: 1px solid $contentBackground;253 border: 1px solid var(--contentBackground);
245 254
246 &:hover {255 &:hover {
247 background: $sidebarBackground;256 background: var(--sidebarBackground);
248 border-color: $sidebarBorder;257 border-color: var(--sidebarBorder);
249 }258 }
250 259
251 &:vertical { border-width: 0 0 0 1px; }260 &:vertical { border-width: 0 0 0 1px; }
@@ -263,14 +272,14 @@ body:not(._native-scrollbars) { · 11 unmodified lines
263 272
264 *::-webkit-scrollbar-thumb {273 *::-webkit-scrollbar-thumb {
265 min-height: 2rem;274 min-height: 2rem;
266 background: $scrollbarColor;275 background: var(--scrollbarColor);
267 background-clip: padding-box;276 background-clip: padding-box;
268 border: 5px solid rgba(black, 0);277 border: 5px solid rgba(black, 0);
269 border-radius: 10px;278 border-radius: 10px;
270 279
271 &:hover,280 &:hover,
272 &:active {281 &:active {
273 background-color: $scrollbarColorHover;282 background-color: var(--scrollbarColorHover);
274 border-width: 4px;283 border-width: 4px;
275 }284 }
276 }285 }
assets/stylesheets/global/_classes.scss+24−25
@@ -36,15 +36,15 @@ · 35 unmodified lines
36//36//
37 37
38%box {38%box {
39 background: $boxBackground;39 background: var(--boxBackground);
40 border: 1px solid $boxBorder;40 border: 1px solid var(--boxBorder);
41 border-radius: 3px;41 border-radius: 3px;
42}42}
43 43
44%heading-box {44%heading-box {
45 color: $boxHeaderColor;45 color: var(--boxHeaderColor);
46 background: $boxHeaderBackground;46 background: var(--boxHeaderBackground);
47 border: 1px solid $boxBorder;47 border: 1px solid var(--boxBorder);
48 border-radius: 3px;48 border-radius: 3px;
49}49}
50 50
@@ -82,15 +82,15 @@ · 31 unmodified lines
82%note {82%note {
83 margin: 1.5rem 0;83 margin: 1.5rem 0;
84 padding: .5rem .875rem;84 padding: .5rem .875rem;
85 background: $noteBackground;85 background: var(--noteBackground);
86 border: 1px solid $noteBorder;86 border: 1px solid var(--noteBorder);
87 border-radius: 3px;87 border-radius: 3px;
88}88}
89 89
90%label {90%label {
91 margin: 0 1px;91 margin: 0 1px;
92 padding: 1px 4px 2px;92 padding: 1px 4px 2px;
93 background: $labelBackground;93 background: var(--labelBackground);
94 border-radius: 3px;94 border-radius: 3px;
95}95}
96 96
@@ -102,35 +102,35 @@ · 5 unmodified lines
102 padding-right: .5em;102 padding-right: .5em;
103 overflow: hidden;103 overflow: hidden;
104 font-size: inherit;104 font-size: inherit;
105 color: $boxHeaderColor;105 color: var(--boxHeaderColor);
106 border: 1px solid $boxBorder;106 border: 1px solid var(--boxBorder);
107 border-radius: 2px;107 border-radius: 2px;
108 @extend %label;108 @extend %label;
109}109}
110 110
111%label-yellow {111%label-yellow {
112 background: $noteBackground;112 background: var(--noteBackground);
113 border-color: $noteBorder;113 border-color: var(--noteBorder);
114}114}
115 115
116%note-green, %label-green {116%note-green, %label-green {
117 background: $noteGreenBackground;117 background: var(--noteGreenBackground);
118 border-color: $noteGreenBorder;118 border-color: var(--noteGreenBorder);
119}119}
120 120
121%note-blue, %label-blue {121%note-blue, %label-blue {
122 background: $noteBlueBackground;122 background: var(--noteBlueBackground);
123 border-color: $noteBlueBorder;123 border-color: var(--noteBlueBorder);
124}124}
125 125
126%note-orange, %label-orange {126%note-orange, %label-orange {
127 background: $noteOrangeBackground;127 background: var(--noteOrangeBackground);
128 border-color: $noteOrangeBorder;128 border-color: var(--noteOrangeBorder);
129}129}
130 130
131%note-red, %label-red {131%note-red, %label-red {
132 background: $noteRedBackground;132 background: var(--noteRedBackground);
133 border-color: $noteRedBorder;133 border-color: var(--noteRedBorder);
134}134}
135 135
136//136//
@@ -149,12 +149,11 @@ · 12 unmodified lines
149 pointer-events: none;149 pointer-events: none;
150 150
151 // <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M15,15H2V6h2.595c0,0,0.689-0.896,2.17-2H1C0.447,4,0,4.449,0,5v11c0,0.553,0.447,1,1,1h15c0.553,0,1-0.447,1-1v-3.746 l-2,1.645V15z M13.361,8.05v3.551L20,6.4l-6.639-4.999v3.131C5.3,4.532,5.3,12.5,5.3,12.5C7.582,8.752,8.986,8.05,13.361,8.05z"/></svg>151 // <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M15,15H2V6h2.595c0,0,0.689-0.896,2.17-2H1C0.447,4,0,4.449,0,5v11c0,0.553,0.447,1,1,1h15c0.553,0,1-0.447,1-1v-3.746 l-2,1.645V15z M13.361,8.05v3.551L20,6.4l-6.639-4.999v3.131C5.3,4.532,5.3,12.5,5.3,12.5C7.582,8.752,8.986,8.05,13.361,8.05z"/></svg>
152 @if $style == 'dark' {152 background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzMzNzdjMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
153 background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2NiZDBkMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
154 } @else {
155 background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzMzNzdjMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
156 }
157 }153 }
158}154}
155html._theme-dark %external-link:after {
156 background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2NiZDBkMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);
157}
159 158
160%internal-link:after { content: none !important; }159%internal-link:after { content: none !important; }
assets/stylesheets/global/_icons.scss+0−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
25html._theme-dark {
26 %darkIconFix {
27 filter: invert(100%) grayscale(100%);
28 -webkit-filter: invert(100%) grayscale(100%);
29 }
30}
31
32<%=
33 items = []
34
35 manifest['items'].each do |item|
36 rules = []
37 rules << "background-position: -#{item['col']}rem -#{item['row']}rem;"
38 rules << "@extend %darkIconFix !optional;" if item['dark_icon_fix']
39 items << "._icon-#{item['type']}:before { #{rules.join(' ')} }"
40 end
41
42 items.join('')
43 %>
assets/stylesheets/global/_mixins.scss+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;1html._theme-dark {
2$monoFont: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;2 --absolute: white;
3$boldFontWeight: 500;
4$bolderFontWeight: 600;
5 3
6$style: 'dark';4 --documentBackground: #222;
5 --contentBackground: #33373a;
7 6
8$maxWidth: 80rem;7 --textColor: #cbd0d0;
9$headerHeight: 3rem;8 --textColorLight: #9da5ad;
10$sidebarWidth: 20rem;9 --textColorLighter: #77787a;
11$sidebarMediumWidth: 16rem;
12 10
13$documentBackground: #222;11 --externalsBackground: #fff;
14$contentBackground: #33373a;
15 12
16$textColor: #cbd0d0;13 --inputFocusBorder: transparent;
17$textColorLight: #9da5ad;
18$textColorLighter: #77787a;
19$textColorRed: #f44336;
20 14
21$inputFocusBorder: false;15 --focusBackground: #3f4042;
16 --focusBorder: #000;
17 --focusText: #f7f2f2;
22 18
23$focusBackground: #3f4042;19 --loadingText: #5d6164;
24$focusBorder: #000;
25$focusText: #f7f2f2;
26 20
27$loadingText: #5d6164;21 --selectionBackground: #007acc;
28$splashText: $loadingText;22 --selectionBorder: #000;
29 23
30$selectionBackground: #007acc;24 --highlightBackground: #64675f;
31$selectionBorder: #000;
32$selectionText: #fff;
33 25
34$highlightBackground: #64675f;26 --linkColor: var(--textColor);
27 --linkColorHover: white;
28 --linkTextDecoration: underline;
35 29
36$linkColor: $textColor;30 --headerBackground: #1c1c1c;
37$linkColorHover: white;31 --headerBorder: #000;
38$linkTextDecoration: underline;32 --searchTagBackground: #{darken(#1c1c1c, 5%)};
33 --searchBorder: #{darken(#000, 2%)};
39 34
40$headerBackground: #1c1c1c;35 --sidebarBackground: #24282a;
41$headerBorder: #000;36 --transparentSidebarBackground: #{rgba(#24282a, 0)};
37 --sidebarBorder: #000;
42 38
43$sidebarBackground: #24282a;39 --scrollbarColor: #6c6c6f;
44$sidebarBorder: #000;40 --scrollbarColorHover: #949697;
45 41
46$scrollbarColor: #6c6c6f;42 --pathBackground: var(--headerBackground);
47$scrollbarColorHover: #949697;43 --pathBorder: var(--headerBorder);
48 44
49$pathBackground: $headerBackground;45 --noticeBackground: var(--sidebarBackground);
50$pathBorder: $headerBorder;46 --noticeBorder: var(--sidebarBorder);
51 47
52$noticeBackground: $sidebarBackground;48 --boxBackground: var(--sidebarBackground);
53$noticeBorder: $sidebarBorder;49 --boxBorder: var(--headerBorder);
50 --boxBorderLight: var(--headerBorder);
51 --boxHeaderColor: #dbe4e4;
52 --boxHeaderBackground: var(--sidebarBackground);
54 53
55$boxBackground: $sidebarBackground;54 --noteBackground: #45474b;
56$boxBorder: $headerBorder;55 --noteBorder: #000;
57$boxBorderLight: $headerBorder;
58$boxHeaderColor: #dbe4e4;
59$boxHeaderBackground: $sidebarBackground;
60 56
61$noteBackground: #45474b;57 --noteGreenBackground: #284a2a;
62$noteBorder: #000;58 --noteGreenBorder: #000;
63 59
64$noteGreenBackground: #284a2a;60 --noteBlueBackground: #2a4151;
65$noteGreenBorder: #000;61 --noteBlueBorder: #000;
66 62
67$noteBlueBackground: #2a4151;63 --noteOrangeBackground: #563322;
68$noteBlueBorder: #000;64 --noteOrangeBorder: #000;
69 65
70$noteOrangeBackground: #563322;66 --noteRedBackground: #603033;
71$noteOrangeBorder: #000;67 --noteRedBorder: #000;
72 68
73$noteRedBackground: #603033;69 --labelBackground: var(--boxBackground);
74$noteRedBorder: #000;
75 70
76$labelBackground: $boxBackground;71 --notifBackground: #{rgba(#555, .95)};
72 --notifBorder: 1px solid #000;
77 73
78$notifBackground: rgba(#555, .95);74 --tipBackground: var(--notifBackground);
79$notifBorder: 1px solid #000;75 --tipBorder: var(--notifBorder);
80$notifColor: #fff;76}
81$notifColorLight: #ccc;
82
83$tipBackground: $notifBackground;
84$tipBorder: $notifBorder;
85
86$mediumScreen: '(max-width: 800px)';
87
88$contentZ: 1;
89$sidebarZ: 2;
90$headerZ: 3;
91$noticeZ: 4;
92$hoverZ: 5;
assets/stylesheets/global/_variables-light.scss+76−0
@@ -0,0 +1,76 @@
1html._theme-default {
2 --absolute: black;
3
4 --documentBackground: #f3f3f3;
5 --contentBackground: #fff;
6
7 --textColor: #333;
8 --textColorLight: #666;
9 --textColorLighter: #888;
10
11 --externalsBackground: #fff;
12
13 --inputFocusBorder: #35b5f4;
14
15 --focusBackground: #e5e5e5;
16 --focusBorder: #d4d4d4;
17 --focusText: #000;
18
19 --loadingText: #ccc;
20
21 --selectionBackground: #398df0;
22 --selectionBorder: #196fc2;
23
24 --highlightBackground: #fffdcd;
25
26 --linkColor: #3377c0;
27 --linkColorHover: #2f6cb6;
28 --linkTextDecoration: none;
29
30 --headerBackground: #eee;
31 --headerBorder: #d7d7d7;
32 --searchTagBackground: #{darken(#eee, 5%)};
33 --searchBorder: #{darken(#d7d7d7, 2%)};
34
35 --sidebarBackground: #f9f9f9;
36 --transparentSidebarBackground: #{rgba(#f9f9f9, 0)};
37 --sidebarBorder: #e1e1e1;
38
39 --scrollbarColor: #ccc;
40 --scrollbarColorHover: #999;
41
42 --pathBackground: var(--sidebarBackground);
43 --pathBorder: var(--sidebarBorder);
44
45 --noticeBackground: #faf9e2;
46 --noticeBorder: #e2e2c1;
47
48 --boxBackground: #fafafa;
49 --boxBorder: #d8d8d8;
50 --boxBorderLight: #e5e5e5;
51 --boxHeaderColor: var(--textColor);
52 --boxHeaderBackground: #f5f5f5;
53
54 --noteBackground: #f8f8dd;
55 --noteBorder: #d3d952;
56
57 --noteGreenBackground: #e7f8e1;
58 --noteGreenBorder: #89da70;
59
60 --noteBlueBackground: #d4f3fd;
61 --noteBlueBorder: #94bbeb;
62
63 --noteOrangeBackground: #fbe6d1;
64 --noteOrangeBorder: #ec8b01;
65
66 --noteRedBackground: #fed5d3;
67 --noteRedBorder: #dc7874;
68
69 --labelBackground: #f4f4f4;
70
71 --notifBackground: #{rgba(#333, .85)};
72 --notifBorder: none;
73
74 --tipBackground: #{rgba(#fffdcd, .95)};
75 --tipBorder: 1px solid #e7dca9;
76}
assets/stylesheets/global/_variables.scss+25−82
@@ -1,92 +1,35 @@
1// Variables needed to style the error message for browsers that don't support CSS variables.
1$baseFont: -apple-system, BlinkMacSystemFont, 'San Francisco', 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;2$baseFont: -apple-system, BlinkMacSystemFont, 'San Francisco', 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;
2$monoFont: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
3$boldFontWeight: 500;
4$bolderFontWeight: 600;
5
6$style: 'light';
7
8$maxWidth: 80rem;
9$headerHeight: 3rem;
10$sidebarWidth: 20rem;
11$sidebarMediumWidth: 16rem;
12
13$documentBackground: #f3f3f3;
14$contentBackground: #fff;
15
16$textColor: #333;3$textColor: #333;
17$textColorLight: #666;
18$textColorLighter: #888;
19$textColorRed: #f44336;
20
21$inputFocusBorder: #35b5f4;
22
23$focusBackground: #e5e5e5;
24$focusBorder: #d4d4d4;
25$focusText: #000;
26
27$loadingText: #ccc;
28$splashText: #ccc;
29
30$selectionBackground: #398df0;
31$selectionBorder: #196fc2;
32$selectionText: #fff;
33
34$highlightBackground: #fffdcd;
35
36$linkColor: #3377c0;
37$linkColorHover: #2f6cb6;
38$linkTextDecoration: none;
39
40$headerBackground: #eee;
41$headerBorder: #d7d7d7;
42
43$sidebarBackground: #f9f9f9;
44$sidebarBorder: #e1e1e1;
45
46$scrollbarColor: #ccc;
47$scrollbarColorHover: #999;
48
49$pathBackground: $sidebarBackground;
50$pathBorder: $sidebarBorder;
51
52$noticeBackground: #faf9e2;
53$noticeBorder: #e2e2c1;
54
55$boxBackground: #fafafa;
56$boxBorder: #d8d8d8;
57$boxBorderLight: #e5e5e5;
58$boxHeaderColor: $textColor;
59$boxHeaderBackground: #f5f5f5;
60
61$noteBackground: #f8f8dd;
62$noteBorder: #d3d952;
63
64$noteGreenBackground: #e7f8e1;
65$noteGreenBorder: #89da70;
66 4
67$noteBlueBackground: #d4f3fd;5html {
68$noteBlueBorder: #94bbeb;6 --baseFont: #{$baseFont};
7 --monoFont: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
8 --boldFontWeight: 500;
9 --bolderFontWeight: 600;
69 10
70$noteOrangeBackground: #fbe6d1;11 --textColorRed: #f44336;
71$noteOrangeBorder: #ec8b01;
72 12
73$noteRedBackground: #fed5d3;13 --splashText: var(--loadingText);
74$noteRedBorder: #dc7874;
75 14
76$labelBackground: #f4f4f4;15 --selectionText: #fff;
16 --transparentSelectionText: rgba(255, 255, 255, 0.9);
77 17
78$notifBackground: rgba(#333, .85);18 --notifColor: #fff;
79$notifBorder: none;19 --notifColorLight: #ccc;
80$notifColor: #fff;
81$notifColorLight: #ccc;
82 20
83$tipBackground: rgba(#fffdcd, .95);21 --maxWidth: 80rem;
84$tipBorder: 1px solid #e7dca9;22 --headerHeight: 3rem;
23 --sidebarWidth: 20rem;
24 --sidebarMediumWidth: 16rem;
85 25
86$mediumScreen: '(max-width: 800px)';26 --focusBackground: #e5e5e5;
27 --focusBorder: #d4d4d4;
28 --focusText: #000;
87 29
88$contentZ: 1;30 --contentZ: 1;
89$sidebarZ: 2;31 --sidebarZ: 2;
90$headerZ: 3;32 --headerZ: 3;
91$noticeZ: 4;33 --noticeZ: 4;
92$hoverZ: 5;34 --hoverZ: 5;
35}
assets/stylesheets/pages/_angular.scss+2−0
@@ -19,4 +19,6 @@ · 18 unmodified lines
19 font-style: italic;19 font-style: italic;
20 text-align: right;20 text-align: right;
21 }21 }
22
23 td h3 { margin: 0 !important; }
22}24}
assets/stylesheets/pages/_angularjs.scss+1−1
@@ -8,7 +8,7 @@ · 7 unmodified lines
8 .nav-index-section {8 .nav-index-section {
9 margin: 1.5em 0 1em -2em;9 margin: 1.5em 0 1em -2em;
10 list-style: none;10 list-style: none;
11 font-weight: $boldFontWeight;11 font-weight: var(--boldFontWeight);
12 text-transform: capitalize;12 text-transform: capitalize;
13 }13 }
14 14
assets/stylesheets/pages/_async.scss+2−2
@@ -3,13 +3,13 @@ · 2 unmodified lines
3 3
4 h3 > .type-signature {4 h3 > .type-signature {
5 float: right;5 float: right;
6 color: $textColorLight;6 color: var(--textColorLight);
7 }7 }
8 8
9 h3 > .signature-attributes {9 h3 > .signature-attributes {
10 font-size: .75rem;10 font-size: .75rem;
11 font-weight: normal;11 font-weight: normal;
12 font-style: italic;12 font-style: italic;
13 color: $textColorLighter;13 color: var(--textColorLighter);
14 }14 }
15}15}
assets/stylesheets/pages/_bash.scss+12−0
@@ -0,0 +1,12 @@
1._bash {
2 dl > dt > code,
3 dl > dt > kbd {
4 @extend %block-label, %label-blue;
5 }
6
7 th[align=left] {
8 border-left: 1px solid var(--boxBorder);
9 }
10
11 code { @extend %label; }
12}
assets/stylesheets/pages/_bootstrap.scss+1−1
@@ -4,7 +4,7 @@ · 3 unmodified lines
4 h4 > code, h5 > code, strong > code { @extend %label; }4 h4 > code, h5 > code, strong > code { @extend %label; }
5 5
6 h2 > small {6 h2 > small {
7 color: $textColorLight;7 color: var(--textColorLight);
8 float: right;8 float: right;
9 }9 }
10 10
assets/stylesheets/pages/_c.scss+4−2
@@ -23,7 +23,7 @@ · 22 unmodified lines
23 23
24 .t-spar { // language/switch, language/for, etc.24 .t-spar { // language/switch, language/for, etc.
25 font-style: italic;25 font-style: italic;
26 color: $textColorLight;26 color: var(--textColorLight);
27 }27 }
28 .t-sdsc-nopad dl, .t-sdsc-nopad dd { margin: 0; }28 .t-sdsc-nopad dl, .t-sdsc-nopad dd { margin: 0; }
29 29
@@ -46,6 +46,8 @@ · 16 unmodified lines
46 46
47 + div { margin-left: .5em; }47 + div { margin-left: .5em; }
48 }48 }
49
50 > table { margin: 0; }
49 }51 }
50 52
51 .t-dcl-rev-aux > td:empty { padding: 0; }53 .t-dcl-rev-aux > td:empty { padding: 0; }
@@ -55,7 +57,7 @@ · 3 unmodified lines
55 margin: 1rem 0;57 margin: 1rem 0;
56 padding: .375rem;58 padding: .375rem;
57 font-size: .75rem;59 font-size: .75rem;
58 border: 1px solid $boxBorder;60 border: 1px solid var(--boxBorder);
59 border-radius: 2px;61 border-radius: 2px;
60 }62 }
61 63
assets/stylesheets/pages/_clojure.scss+1−1
@@ -5,6 +5,6 @@ · 4 unmodified lines
5 .type {5 .type {
6 float: right;6 float: right;
7 font-size: .9em;7 font-size: .9em;
8 color: $textColorLight;8 color: var(--textColorLight);
9 }9 }
10}10}
assets/stylesheets/pages/_d.scss+3−3
@@ -2,10 +2,10 @@ · 1 unmodified line
2 h2 { @extend %block-heading; }2 h2 { @extend %block-heading; }
3 h3, .d_decl { @extend %block-label, %label-blue; }3 h3, .d_decl { @extend %block-label, %label-blue; }
4 .d_decl { @extend %code; }4 .d_decl { @extend %code; }
5 .d_decl > small { color: $textColorLight; }5 .d_decl > small { color: var(--textColorLight); }
6 .d_decl > strong { font-weight: $bolderFontWeight; }6 .d_decl > strong { font-weight: var(--bolderFontWeight); }
7 7
8 p > code, li > code, td > code, dd > code { @extend %label; }8 p > code, li > code, td > code, dd > code { @extend %label; }
9 9
10 span.red { color: $textColorRed; }10 span.red { color: var(--textColorRed); }
11}11}
assets/stylesheets/pages/_dart.scss+12−0
@@ -0,0 +1,12 @@
1._dart {
2 @extend %simple;
3
4 dl:not(.dl-horizontal) dt, .multi-line-signature {
5 @extend %block-label;
6
7 .features {
8 float: right;
9 color: var(--textColorLight);
10 }
11 }
12}
assets/stylesheets/pages/_dojo.scss+1−1
@@ -1,5 +1,5 @@
1._dojo {1._dojo {
2 @extend %simple;2 @extend %simple;
3 3
4 .jsdoc-inheritance { color: $textColorLight; }4 .jsdoc-inheritance { color: var(--textColorLight); }
5}5}
assets/stylesheets/pages/_ember.scss+2−2
@@ -5,7 +5,7 @@ · 4 unmodified lines
5 5
6 h3 > .access {6 h3 > .access {
7 float: right;7 float: right;
8 color: $textColorLight;8 color: var(--textColorLight);
9 font-weight: normal;9 font-weight: normal;
10 }10 }
11 11
@@ -15,6 +15,6 @@ · 3 unmodified lines
15 }15 }
16 16
17 p.github-link {17 p.github-link {
18 color: $textColorLight;18 color: var(--textColorLight);
19 }19 }
20}20}
assets/stylesheets/pages/_erlang.scss+1−1
@@ -5,5 +5,5 @@ · 4 unmodified lines
5 code.code { @extend %label; }5 code.code { @extend %label; }
6 .note { @extend %note; }6 .note { @extend %note; }
7 .warning { @extend %note, %note-red; }7 .warning { @extend %note, %note-red; }
8 .note .label, .warning .label { font-weight: $boldFontWeight; }8 .note .label, .warning .label { font-weight: var(--boldFontWeight); }
9}9}
assets/stylesheets/pages/_graphite.scss+7−0
@@ -0,0 +1,7 @@
1._graphite {
2 @extend %simple;
3
4 dl > dt {
5 @extend %block-label, %label-blue;
6 }
7}
assets/stylesheets/pages/_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
32 32
33 .toc-list {33 .toc-list {
34 margin-top: 0;34 margin-top: 0;
35 font-weight: $boldFontWeight;35 font-weight: var(--boldFontWeight);
36 36
37 > li + li { margin-top: 1em; }37 > li + li { margin-top: 1em; }
38 > li > ul { font-weight: normal; }38 > li > ul { font-weight: normal; }
@@ -48,7 +48,7 @@ · 9 unmodified lines
48 .returns,48 .returns,
49 .option-type {49 .option-type {
50 float: right;50 float: right;
51 font-weight: $boldFontWeight;51 font-weight: var(--boldFontWeight);
52 margin-left: 1em;52 margin-left: 1em;
53 }53 }
54 54
assets/stylesheets/pages/_love.scss+2−2
@@ -12,6 +12,6 @@ · 11 unmodified lines
12 .smwtable { width: 100%; }12 .smwtable { width: 100%; }
13 .smwtable td:nth-last-child(2), .smwtable td:last-child { width: 2.5em; }13 .smwtable td:nth-last-child(2), .smwtable td:last-child { width: 2.5em; }
14 14
15 .cell-green { background: $noteGreenBackground; }15 .cell-green { background: var(--noteGreenBackground); }
16 .cell-red { background: $noteRedBackground; }16 .cell-red { background: var(--noteRedBackground); }
17}17}
assets/stylesheets/pages/_mdn.scss+28−3
@@ -7,7 +7,7 @@ · 6 unmodified lines
7 > span {7 > span {
8 display: block;8 display: block;
9 font-size: 1rem;9 font-size: 1rem;
10 font-weight: $boldFontWeight;10 font-weight: var(--boldFontWeight);
11 }11 }
12 12
13 ul, ol {13 ul, ol {
@@ -30,6 +30,7 @@ · 16 unmodified lines
30 .notice,30 .notice,
31 .warning,31 .warning,
32 .overheadIndicator,32 .overheadIndicator,
33 .blockIndicator,
33 .syntaxbox, // CSS, JavaScript34 .syntaxbox, // CSS, JavaScript
34 .twopartsyntaxbox, // CSS35 .twopartsyntaxbox, // CSS
35 .inheritsbox, // JavaScript36 .inheritsbox, // JavaScript
@@ -42,7 +43,7 @@ · 6 unmodified lines
42 > .note {43 > .note {
43 em {44 em {
44 font-style: normal;45 font-style: normal;
45 font-weight: $boldFontWeight;46 font-weight: var(--boldFontWeight);
46 }47 }
47 48
48 > ul { margin: 1em 0; }49 > ul { margin: 1em 0; }
@@ -93,7 +94,7 @@ · 44 unmodified lines
93 dt > strong > code, // HTML element attribute94 dt > strong > code, // HTML element attribute
94 dl > dt > code { // CSS property value, Javascript function argument95 dl > dt > code { // CSS property value, Javascript function argument
95 font-family: inherit;96 font-family: inherit;
96 font-weight: $boldFontWeight;97 font-weight: var(--boldFontWeight);
97 font-size: inherit;98 font-size: inherit;
98 }99 }
99 100
@@ -104,4 +105,28 @@ · 4 unmodified lines
104 .cleared { clear: both; } // CSS/box-shadow105 .cleared { clear: both; } // CSS/box-shadow
105 106
106 code > strong { font-weight: normal; }107 code > strong { font-weight: normal; }
108
109 // Compatibility tablees
110
111 .bc-github-link {
112 float: right;
113 font-size: .75rem;
114 }
115
116 .bc-supports-yes, .bc-supports-yes + dd, .bc-supports-yes + dd + dd { background: var(--noteGreenBackground); }
117 .bc-supports-partial, .bc-supports-partial + dd, .bc-supports-partial + dd + dd { background: var(--noteOrangeBackground); }
118 .bc-supports-no, .bc-supports-no + dd, .bc-supports-no + dd + dd { background: var(--noteRedBackground); }
119
120 .bc-table {
121 min-width: 100%;
122
123 dl {
124 margin: .25rem 0 0;
125 padding: .25rem 0 0;
126 font-size: .75rem;
127 border-top: 1px solid var(--boxBorder);
128 }
129
130 dd { margin: 0; }
131 }
107}132}
assets/stylesheets/pages/_meteor.scss+2−2
@@ -9,10 +9,10 @@ · 8 unmodified lines
9 dt > code { @extend %label; }9 dt > code { @extend %label; }
10 10
11 .api-heading { overflow: hidden; }11 .api-heading { overflow: hidden; }
12 .api-heading > code { font-weight: $boldFontWeight; }12 .api-heading > code { font-weight: var(--boldFontWeight); }
13 .locus, .src-code { float: right; }13 .locus, .src-code { float: right; }
14 .locus, .type, .src-code { margin-left: .5em; }14 .locus, .type, .src-code { margin-left: .5em; }
15 h2 .subtext-api { margin-top: .25rem; }15 h2 .subtext-api { margin-top: .25rem; }
16 .locus, .subtext-api, .subtext-api > code { font-size: .75rem; }16 .locus, .subtext-api, .subtext-api > code { font-size: .75rem; }
17 .locus, .type { color: $textColorLight; }17 .locus, .type { color: var(--textColorLight); }
18}18}
assets/stylesheets/pages/_node.scss+3−0
@@ -20,5 +20,8 @@ · 19 unmodified lines
20 margin: 0 0 1em 1em;20 margin: 0 0 1em 1em;
21 @extend %label;21 @extend %label;
22 }22 }
23
24 .srclink { float: right; }
25 details > table { margin: 0; }
23}26}
24 27
assets/stylesheets/pages/_npm.scss+1−1
@@ -8,7 +8,7 @@ · 7 unmodified lines
8 8
9 .faint.heading {9 .faint.heading {
10 font-size: .9em;10 font-size: .9em;
11 color: $textColorLight;11 color: var(--textColorLight);
12 }12 }
13 13
14 .youtube-video iframe { width: 420px; height: 315px; }14 .youtube-video iframe { width: 420px; height: 315px; }
assets/stylesheets/pages/_phalcon.scss+1−1
@@ -3,6 +3,6 @@ · 2 unmodified lines
3 3
4 h3 > small {4 h3 > small {
5 float: right;5 float: right;
6 color: $textColorLight;6 color: var(--textColorLight);
7 }7 }
8}8}
assets/stylesheets/pages/_php.scss+2−2
@@ -13,7 +13,7 @@ · 12 unmodified lines
13 13
14 .verinfo {14 .verinfo {
15 float: right;15 float: right;
16 font-weight: $boldFontWeight;16 font-weight: var(--boldFontWeight);
17 }17 }
18 18
19 .classsynopsis,19 .classsynopsis,
@@ -21,7 +21,7 @@ · 1 unmodified line
21 .description > .methodsynopsis,21 .description > .methodsynopsis,
22 .description > .fieldsynopsis { @extend %note, %note-blue; }22 .description > .fieldsynopsis { @extend %note, %note-blue; }
23 23
24 .classsynopsisinfo_comment { color: $textColorLight; }24 .classsynopsisinfo_comment { color: var(--textColorLight); }
25 25
26 .classsynopsisinfo_comment,26 .classsynopsisinfo_comment,
27 .classsynopsis > .constructorsynopsis,27 .classsynopsis > .constructorsynopsis,
assets/stylesheets/pages/_postgres.scss+12−7
@@ -1,19 +1,24 @@
1._postgres {1._postgres {
2 padding-left: 1rem;2 padding-left: 1rem;
3 3
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; }
5 h2 { @extend %block-heading; }5 h2 { @extend %block-heading; }
6 6
7 .VARIABLELIST dt { @extend %block-label, %label-blue; }7 .variablelist dt { @extend %block-label, %label-blue; }
8 8
9 blockquote.NOTE, blockquote.IMPORTANT, blockquote.TIP, blockquote.CAUTION { @extend %note; }9 blockquote.note, blockquote.important, blockquote.tip, blockquote.caution { @extend %note; }
10 blockquote.TIP { @extend %note-green; }10 blockquote.tip { @extend %note-green; }
11 blockquote.CAUTION { @extend %note-orange; }11 blockquote.caution { @extend %note-orange; }
12
13 blockquote > h3 {
14 font-size: .875rem;
15 margin: 0 0 .25rem;
16 }
12 17
13 p > code { @extend %label; }18 p > code { @extend %label; }
14 p.c2 { font-weight: $boldFontWeight; }19 p.c2 { font-weight: var(--boldFontWeight); }
15 20
16 .NAVFOOTER > table { width: 100%; }21 .navfooter > table { width: 100%; }
17 td[align=center] { text-align: center; }22 td[align=center] { text-align: center; }
18 td[align=right] { text-align: right; }23 td[align=right] { text-align: right; }
19}24}
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
6 .method-description > h2, h3, h4, h5, h6 { font-size: 1em; }6 .method-description > h2, h3, h4, h5, h6 { font-size: 1em; }
7 7
8 .method-heading {8 .method-heading {
9 font-weight: $boldFontWeight;9 font-weight: var(--boldFontWeight);
10 @extend %block-label, %label-blue;10 @extend %block-label, %label-blue;
11 11
12 + .method-heading { margin-top: -.5em; }12 + .method-heading { margin-top: -.5em; }
@@ -22,26 +22,19 @@ · 9 unmodified lines
22 a.method-click-advice {22 a.method-click-advice {
23 float: right;23 float: right;
24 font-size: .75rem;24 font-size: .75rem;
25 color: $linkColor;25 color: var(--linkColor);
26 cursor: pointer;26 cursor: pointer;
27 @extend %user-select-none;27 @extend %user-select-none;
28 28
29 &:hover { text-decoration: underline; }29 &:hover { text-decoration: underline; }
30 }
31 30
32 .method-description { position: relative; }31 @include print {
32 display: none;
33 }
34 }
33 35
34 .method-source-code {36 .method-source-code {
35 display: none;37 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; }
45 }38 }
46 39
47 // Rails guides40 // Rails guides
assets/stylesheets/pages/_react_native.scss+1−1
@@ -2,7 +2,7 @@ · 1 unmodified line
2 @extend %simple;2 @extend %simple;
3 3
4 .deprecated { @extend %note, %note-orange; }4 .deprecated { @extend %note, %note-orange; }
5 .deprecatedTitle { font-weight: $boldFontWeight; }5 .deprecatedTitle { font-weight: var(--boldFontWeight); }
6 6
7 span.platform { float: right; }7 span.platform { float: right; }
8 span.propType, span.platform { font-weight: normal; }8 span.propType, span.platform { font-weight: normal; }
assets/stylesheets/pages/_redis.scss+2−2
@@ -14,7 +14,7 @@ · 13 unmodified lines
14 14
15 .args {15 .args {
16 font-size: .75rem;16 font-size: .75rem;
17 color: $textColorLight;17 color: var(--textColorLight);
18 }18 }
19 19
20 // Others20 // Others
@@ -35,7 +35,7 @@ · 14 unmodified lines
35 > .prompt {35 > .prompt {
36 float: left;36 float: left;
37 margin-right: .5em;37 margin-right: .5em;
38 color: $textColorLight;38 color: var(--textColorLight);
39 }39 }
40 40
41 > code {41 > code {
assets/stylesheets/pages/_rfc.scss+1−1
@@ -10,6 +10,6 @@ · 9 unmodified lines
10 10
11 > h1, > h2, > h3, > h4, > h5 {11 > h1, > h2, > h3, > h4, > h5 {
12 margin: 0;12 margin: 0;
13 font-family: $baseFont;13 font-family: var(--baseFont);
14 }14 }
15}15}
assets/stylesheets/pages/_rust.scss+10−1
@@ -3,9 +3,18 @@ · 2 unmodified lines
3 3
4 h4 { @extend %block-label; }4 h4 { @extend %block-label; }
5 .docblock { margin-left: 1em; }5 .docblock { margin-left: 1em; }
6 div.information, div.important-traits {
7 @extend %note;
8
9 > pre { margin: .5rem 0; }
10 }
6 11
7 div.stability { margin-bottom: 1em; }12 div.stability { margin-bottom: 1em; }
8 em.stab, span.stab { @extend %label; }13 em.stab, span.stab { @extend %label; }
9 em.stab.unstable, span.stab.unstable { @extend %label-orange; }14 em.stab.unstable, span.stab.unstable { @extend %label-orange; }
10 .since, .out-of-band { float: right; }15 .out-of-band { float: right; }
16 .since, .srclink {
17 float: right;
18 margin-left: .5rem;
19 }
11}20}
assets/stylesheets/pages/_simple.scss+1−31
@@ -17,34 +17,4 @@ · 16 unmodified lines
17 blockquote > h4, blockquote > h5 { margin-top: .25rem; }17 blockquote > h4, blockquote > h5 { margin-top: .25rem; }
18}18}
19 19
20._apache_pig,20._simple { @extend %simple; }
21._bower,
22._chai,
23._codeceptjs,
24._docker,
25._electron,
26._fish,
27._flow,
28._gnu,
29._grunt,
30._haxe,
31._immutable,
32._influxdata,
33._jasmine,
34._jest,
35._less,
36._lodash,
37._marionette,
38._markdown,
39._mocha,
40._mongoose,
41._nim,
42._redux,
43._requirejs,
44._simple,
45._typescript,
46._vagrant,
47._vulkan,
48._yarn {
49 @extend %simple;
50}
assets/stylesheets/pages/_sphinx.scss+3−2
@@ -10,10 +10,11 @@ · 9 unmodified lines
10 10
11 .important { @extend %note-orange; }11 .important { @extend %note-orange; }
12 .warning, .deprecated-removed, .deprecated { @extend %note-red; }12 .warning, .deprecated-removed, .deprecated { @extend %note-red; }
13 .hint { @extend %note-green; }
13 14
14 .versionmodified, span.title, .topic-title {15 .versionmodified, span.title, .topic-title {
15 display: block;16 display: block;
16 font-weight: $boldFontWeight;17 font-weight: var(--boldFontWeight);
17 }18 }
18 19
19 p > code, li > code, dd > code, .docutils > dt > code { @extend %label; }20 p > code, li > code, dd > code, .docutils > dt > code { @extend %label; }
@@ -25,7 +26,7 @@ · 5 unmodified lines
25 .admonition-title {26 .admonition-title {
26 float: left;27 float: left;
27 margin: 0 .5em 0 0;28 margin: 0 .5em 0 0;
28 font-weight: $boldFontWeight;29 font-weight: var(--boldFontWeight);
29 30
30 &:after { content: ':'; }31 &:after { content: ':'; }
31 }32 }
assets/stylesheets/pages/_sphinx_simple.scss+1−1
@@ -6,7 +6,7 @@ · 5 unmodified lines
6 .admonition.tip { @extend %note-green; }6 .admonition.tip { @extend %note-green; }
7 .admonition-title {7 .admonition-title {
8 margin: 0 0 .25rem;8 margin: 0 0 .25rem;
9 font-weight: $boldFontWeight;9 font-weight: var(--boldFontWeight);
10 }10 }
11 11
12 code { @extend %label; }12 code { @extend %label; }
assets/stylesheets/pages/_support_tables.scss+1−1
@@ -24,7 +24,7 @@ · 23 unmodified lines
24 }24 }
25 25
26 tr.current {26 tr.current {
27 font-weight: $boldFontWeight;27 font-weight: var(--boldFontWeight);
28 font-size: 1rem;28 font-size: 1rem;
29 }29 }
30 30
assets/stylesheets/pages/_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 @@
1._yard {1._yard {
2 @extend %simple;2 @extend %simple;
3 3
4 .tag_title { font-weight: $boldFontWeight; }4 .tag_title { font-weight: var(--boldFontWeight); }
5}5}
assets/stylesheets/pages/_yii.scss+1−1
@@ -3,6 +3,6 @@ · 2 unmodified lines
3 3
4 .detail-header-tag, .detailHeaderTag {4 .detail-header-tag, .detailHeaderTag {
5 float: right;5 float: right;
6 color: $textColorLight;6 color: var(--textColorLight);
7 }7 }
8}8}
docs/adding-docs.md+24−0
@@ -0,0 +1,24 @@
1Adding a documentation may look like a daunting task but once you get the hang of it, it's actually quite simple. Don't hesitate to ask for help [in Gitter](https://gitter.im/FreeCodeCamp/DevDocs) if you ever get stuck.
2
3**Note:** please read the [contributing guidelines](../.github/CONTRIBUTING.md) before submitting a new documentation.
4
51. Create a subclass of `Docs::UrlScraper` or `Docs::FileScraper` in the `lib/docs/scrapers/` directory. Its name should be the [PascalCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename (e.g. `my_doc` → `MyDoc`)
62. Add the appropriate class attributes and filter options (see the [Scraper Reference](./scraper-reference.md) page).
73. Check that the scraper is listed in `thor docs:list`.
84. Create filters specific to the scraper in the `lib/docs/filters/[my_doc]/` directory and add them to the class's [filter stacks](./scraper-reference.md#filter-stacks). You may create any number of filters but will need at least the following two:
9 * A [`CleanHtml`](./filter-reference.md#cleanhtmlfilter) filter whose task is to clean the HTML markup (e.g. adding `id` attributes to headings) and remove everything superfluous and/or nonessential.
10 * An [`Entries`](./filter-reference.md#entriesfilter) filter whose task is to determine the pages' metadata (the list of entries, each with a name, type and path).
11 The [Filter Reference](./filter-reference.md) page has all the details about filters.
125. Using the `thor docs:page [my_doc] [path]` command, check that the scraper works properly. Files will appear in the `public/docs/[my_doc]/` directory (but not inside the app as the command doesn't touch the index). `path` in this case refers to either the remote path (if using `UrlScraper`) or the local path (if using `FileScraper`).
136. Generate the full documentation using the `thor docs:generate [my_doc] --force` command. Additionally, you can use the `--verbose` option to see which files are being created/updated/deleted (useful to see what changed since the last run), and the `--debug` option to see which URLs are being requested and added to the queue (useful to pin down which page adds unwanted URLs to the queue).
147. Start the server, open the app, enable the documentation, and see how everything plays out.
158. Tweak the scraper/filters and repeat 5) and 6) until the pages and metadata are ok.
169. To customize the pages' styling, create an SCSS file in the `assets/stylesheets/pages/` directory and import it in both `application.css.scss` AND `application-dark.css.scss`. Both the file and CSS class should be named `_[type]` where [type] is equal to the scraper's `type` attribute (documentations with the same type share the same custom CSS and JS). Setting the type to `simple` will apply the general styling rules in `assets/stylesheets/pages/_simple.scss`, which can be used for documentations where little to no CSS changes are needed.
1710. To add syntax highlighting or execute custom JavaScript on the pages, create a file in the `assets/javascripts/views/pages/` directory (take a look at the other files to see how it works).
1811. Add the documentation's icon in the `public/icons/docs/[my_doc]/` directory, in both 16x16 and 32x32-pixels formats. The icon spritesheet is automatically generated when you (re)start your local DevDocs instance.
1912. Add the documentation's copyright details to the list in `assets/javascripts/templates/pages/about_tmpl.coffee`. This is the data shown in the table on the [about](https://devdocs.io/about) page, and is ordered alphabetically. Simply copying an existing item, placing it in the right slot and updating the values to match the new scraper will do the job.
2013. Ensure `thor updates:check [my_doc]` shows the correct latest version.
21
22If the documentation includes more than a few hundreds pages and is available for download, try to scrape it locally (e.g. using `FileScraper`). It'll make the development process much faster and avoids putting too much load on the source site. (It's not a problem if your scraper is coupled to your local setup, just explain how it works in your pull request.)
23
24Finally, try to document your scraper and filters' behavior as much as possible using comments (e.g. why some URLs are ignored, HTML markup removed, metadata that way, etc.). It'll make updating the documentation much easier.
docs/filter-reference.md+224−0
@@ -0,0 +1,224 @@
1**Table of contents:**
2
3* [Overview](#overview)
4* [Instance methods](#instance-methods)
5* [Core filters](#core-filters)
6* [Custom filters](#custom-filters)
7 - [CleanHtmlFilter](#cleanhtmlfilter)
8 - [EntriesFilter](#entriesfilter)
9
10## Overview
11
12Filters use the [HTML::Pipeline](https://github.com/jch/html-pipeline) library. They take an HTML string or [Nokogiri](http://nokogiri.org/) node as input, optionally perform modifications and/or extract information from it, and then outputs the result. Together they form a pipeline where each filter hands its output to the next filter's input. Every documentation page passes through this pipeline before being copied on the local filesystem.
13
14Filters are subclasses of the [`Docs::Filter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/filter.rb) class and require a `call` method. A basic implementation looks like this:
15
16```ruby
17module Docs
18 class CustomFilter < Filter
19 def call
20 doc
21 end
22 end
23end
24```
25
26Filters which manipulate the Nokogiri node object (`doc` and related methods) are _HTML filters_ and must not manipulate the HTML string (`html`). Vice-versa, filters which manipulate the string representation of the document are _text filters_ and must not manipulate the Nokogiri node object. The two types are divided into two stacks within the scrapers. These stacks are then combined into a pipeline that calls the HTML filters before the text filters (more details [here](./scraper-reference.md#filter-stacks)). This is to avoid parsing the document multiple times.
27
28The `call` method must return either `doc` or `html`, depending on the type of filter.
29
30## Instance methods
31
32* `doc` [Nokogiri::XML::Node]
33 The Nokogiri representation of the container element.
34 See [Nokogiri's API docs](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node) for the list of available methods.
35
36* `html` [String]
37 The string representation of the container element.
38
39* `context` [Hash] **(frozen)**
40 The scraper's `options` along with a few additional keys: `:base_url`, `:root_url`, `:root_page` and `:url`.
41
42* `result` [Hash]
43 Used to store the page's metadata and pass back information to the scraper.
44 Possible keys:
45
46 - `:path` — the page's normalized path
47 - `:store_path` — the path where the page will be stored (equal to `:path` with `.html` at the end)
48 - `:internal_urls` — the list of distinct internal URLs found within the page
49 - `:entries` — the [`Entry`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/models/entry.rb) objects to add to the index
50
51* `css`, `at_css`, `xpath`, `at_xpath`
52 Shortcuts for `doc.css`, `doc.xpath`, etc.
53
54* `base_url`, `current_url`, `root_url` [Docs::URL]
55 Shortcuts for `context[:base_url]`, `context[:url]`, and `context[:root_url]` respectively.
56
57* `root_path` [String]
58 Shortcut for `context[:root_path]`.
59
60* `subpath` [String]
61 The sub-path from the base URL of the current URL.
62 _Example: if `base_url` equals `example.com/docs` and `current_url` equals `example.com/docs/file?raw`, the returned value is `/file`._
63
64* `slug` [String]
65 The `subpath` removed of any leading slash or `.html` extension.
66 _Example: if `subpath` equals `/dir/file.html`, the returned value is `dir/file`._
67
68* `root_page?` [Boolean]
69 Returns `true` if the current page is the root page.
70
71* `initial_page?` [Boolean]
72 Returns `true` if the current page is the root page or its subpath is one of the scraper's `initial_paths`.
73
74## Core filters
75
76* [`ContainerFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/container.rb) — changes the root node of the document (remove everything outside)
77* [`CleanHtmlFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_html.rb) — removes HTML comments, `<script>`, `<style>`, etc.
78* [`NormalizeUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_urls.rb) — replaces all URLs with their fully qualified counterpart
79* [`InternalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/internal_urls.rb) — detects internal URLs (the ones to scrape) and replaces them with their unqualified, relative counterpart
80* [`NormalizePathsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_paths.rb) — makes the internal paths consistent (e.g. always end with `.html`)
81* [`CleanLocalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_local_urls.rb) — removes links, iframes and images pointing to localhost (`FileScraper` only)
82* [`InnerHtmlFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/inner_html.rb) — converts the document to a string
83* [`CleanTextFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_text.rb) — removes empty nodes
84* [`AttributionFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/attribution.rb) — appends the license info and link to the original document
85* [`TitleFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/title.rb) — prepends the document with a title (disabled by default)
86* [`EntriesFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/entries.rb) — abstract filter for extracting the page's metadata
87
88## Custom filters
89
90Scrapers can have any number of custom filters but require at least the two described below.
91
92**Note:** filters are located in the [`lib/docs/filters`](https://github.com/Thibaut/devdocs/tree/master/lib/docs/filters/) directory. The class's name must be the [CamelCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename.
93
94### `CleanHtmlFilter`
95
96The `CleanHtml` filter is tasked with cleaning the HTML markup where necessary and removing anything superfluous or nonessential. Only the core documentation should remain at the end.
97
98Nokogiri's many jQuery-like methods make it easy to search and modify elements — see the [API docs](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node).
99
100Here's an example implementation that covers the most common use-cases:
101
102```ruby
103module Docs
104 class MyScraper
105 class CleanHtmlFilter < Filter
106 def call
107 css('hr').remove
108 css('#changelog').remove if root_page?
109
110 # Set id attributes on <h3> instead of an empty <a>
111 css('h3').each do |node|
112 node['id'] = node.at_css('a')['id']
113 end
114
115 # Make proper table headers
116 css('td.header').each do |node|
117 node.name = 'th'
118 end
119
120 # Remove code highlighting
121 css('pre').each do |node|
122 node.content = node.content
123 end
124
125 doc
126 end
127 end
128 end
129end
130```
131
132**Notes:**
133
134* Empty elements will be automatically removed by the core `CleanTextFilter` later in the pipeline's execution.
135* Although the goal is to end up with a clean version of the page, try to keep the number of modifications to a minimum, so as to make the code easier to maintain. Custom CSS is the preferred way of normalizing the pages (except for hiding stuff which should always be done by removing the markup).
136* Try to document your filter's behavior as much as possible, particularly modifications that apply only to a subset of pages. It'll make updating the documentation easier.
137
138### `EntriesFilter`
139
140The `Entries` filter is responsible for extracting the page's metadata, represented by a set of _entries_, each with a name, type and path.
141
142The following two models are used under the hood to represent the metadata:
143
144* [`Entry(name, type, path)`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/models/entry.rb)
145* [`Type(name, slug, count)`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/models/type.rb)
146
147Each scraper must implement its own `EntriesFilter` by subclassing the [`Docs::EntriesFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/entries.rb) class. The base class already implements the `call` method and includes four methods which the subclasses can override:
148
149* `get_name` [String]
150 The name of the default entry (aka. the page's name).
151 It is usually guessed from the `slug` (documented above) or by searching the HTML markup.
152 **Default:** modified version of `slug` (underscores are replaced with spaces and forward slashes with dots)
153
154* `get_type` [String]
155 The type of the default entry (aka. the page's type).
156 Entries without a type can be searched for but won't be listed in the app's sidebar (unless no other entries have a type).
157 **Default:** `nil`
158
159* `include_default_entry?` [Boolean]
160 Whether to include the default entry.
161 Used when a page consists of multiple entries (returned by `additional_entries`) but doesn't have a name/type of its own, or to remove a page from the index (if it has no additional entries), in which case it won't be copied on the local filesystem and any link to it in the other pages will be broken (as explained on the [Scraper Reference](./scraper-reference.md) page, this is used to keep the `:skip` / `:skip_patterns` options to a maintainable size, or if the page includes links that can't reached from anywhere else).
162 **Default:** `true`
163
164* `additional_entries` [Array]
165 The list of additional entries.
166 Each entry is represented by an Array of three attributes: its name, fragment identifier, and type. The fragment identifier refers to the `id` attribute of the HTML element (usually a heading) that the entry relates to. It is combined with the page's path to become the entry's path. If absent or `nil`, the page's path is used. If the type is absent or `nil`, the default `type` is used.
167 Example: `[ ['One'], ['Two', 'id'], ['Three', nil, 'type'] ]` adds three additional entries, the first one named "One" with the default path and type, the second one named "Two" with the URL fragment "#id" and the default type, and the third one named "Three" with the default path and the type "type".
168 The list is usually constructed by running through the markup. Exceptions can also be hard-coded for specific pages.
169 **Default:** `[]`
170
171The following accessors are also available, but must not be overridden:
172
173* `name` [String]
174 Memoized version of `get_name` (`nil` for the root page).
175
176* `type` [String]
177 Memoized version of `get_type` (`nil` for the root page).
178
179**Notes:**
180
181* Leading and trailing whitespace is automatically removed from names and types.
182* Names must be unique across the documentation and as short as possible (ideally less than 30 characters). Whenever possible, methods should be differentiated from properties by appending `()`, and instance methods should be differentiated from class methods using the `Class#method` or `object.method` conventions.
183* You can call `name` from `get_type` or `type` from `get_name` but doing both will cause a stack overflow (i.e. you can infer the name from the type or the type from the name, but you can't do both at the same time). Don't call `get_name` or `get_type` directly as their value isn't memoized.
184* The root page has no name and no type (both are `nil`). `get_name` and `get_type` won't get called with the page (but `additional_entries` will).
185* `Docs::EntriesFilter` is an _HTML filter_. It must be added to the scraper's `html_filters` stack.
186* Try to document the code as much as possible, particularly the special cases. It'll make updating the documentation easier.
187
188**Example:**
189
190```ruby
191module Docs
192 class MyScraper
193 class EntriesFilter < Docs::EntriesFilter
194 def get_name
195 node = at_css('h1')
196 result = node.content.strip
197 result << ' event' if type == 'Events'
198 result << '()' if node['class'].try(:include?, 'function')
199 result
200 end
201
202 def get_type
203 object, method = *slug.split('/')
204 method ? object : 'Miscellaneous'
205 end
206
207 def additional_entries
208 return [] if root_page?
209
210 css('h2').map do |node|
211 [node.content, node['id']]
212 end
213 end
214
215 def include_default_entry?
216 !at_css('.obsolete')
217 end
218 end
219 end
220end
221```
222
223
224return [[Home]]
docs/maintainers.md+102−0
@@ -0,0 +1,102 @@
1# Maintainer's Guide
2
3This document is intended for [DevDocs maintainers](#list-of-maintainers).
4
5## Merging pull requests
6
7- PRs should be approved by at least one maintainer before being merged.
8
9- PRs that add or update documentations should always be merged locally, and the app deployed, before the merge is pushed to GitHub.
10
11 This workflow is required because there is a dependency between the local and production environments. The `thor docs:download` command downloads documentations from production files uploaded by the `thor docs:upload` command. If a PR adding a new documentation is merged and pushed to GitHub before the files have been uploaded to production, the `thor docs:download` will fail for the new documentation and the docker container will not build properly until the new documentation is deployed to production.
12
13## Updating docs
14
15The process for updating docs is as follow:
16
17- Make version/release changes in the scraper file.
18- If needed, update the copyright notice of the documentation in the scraper file (`options[:attribution]`) and the about page (`about_tmpl.coffee`). The copyright notice must be the same as the one on the original documentation.
19- Run `thor docs:generate`.
20- Make sure the documentation still works well. The `thor docs:generate` command outputs a summary of the changes, which helps identifying issues (e.g. deleted files) and new pages to check out in the app. Verify locally that everything works, especially the files that were created (if any), and that the categorization of entries is still good. Often, updates will require code changes to tweak some new markup in the source website or categorize new entries.
21- Commit the changes (protip: use the `thor docs:commit` command documented below).
22- Optional: do more updates.
23- Run `thor docs:upload` (documented below).
24- Push to GitHub to [deploy the app](#deploying-devdocs) and verify that everything works in production.
25- Run `thor docs:clean` (documented below).
26
27Note: changes to `public/docs/docs.json` should never be committed. This file reflects which documentations have been downloaded or generated locally, which is always none on a fresh `git clone`.
28
29## Setup requirements
30
31In order to deploy DevDocs, you must:
32
33- be given access to Heroku, [configure the Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) on your computer, and familiarize yourself with Heroku's UI and CLI, as well as that of New Relic (accessible through [the Heroku dashboard](https://dashboard.heroku.com/apps/devdocs)).
34
35- be given access to DevDocs's [Sentry instance](https://sentry.io/devdocs/devdocs-js/) (for JS error tracking) and familiarize yourself with its UI.
36
37- be provided with DevDocs's S3 credentials, and install (`brew install awscli` on macOS) and [configure](https://docs.aws.amazon.com/cli/latest/reference/configure/) the AWS CLI on your computer. The configuration must add a named profile called "devdocs":
38 ```
39 aws configure --profile devdocs
40 ```
41
42- be provided with DevDocs's MaxCDN push zone credentials, and add them to your `.bash_profile` as such:
43 ```
44 export DEVDOCS_DL_USERNAME="username"
45 export DEVDOCS_DL_PASSWORD="password"
46 ```
47
48## Thor commands
49
50In addition to the [publicly-documented commands](https://github.com/freeCodeCamp/devdocs#available-commands), the following commands are aimed at DevDocs maintainers:
51
52- `thor docs:package`
53
54 Generates packages for one or more documentations. Those packages are intended to be uploaded to DevDocs's MaxCDN push zone by maintainers via the `thor docs:upload` command, and downloaded by users via the `thor docs:download` command.
55
56 Versions can be specified as such: `thor docs:package rails@5.2 node@10\ LTS`.
57
58 Packages can also be automatically generated during the scraping process by passing the `--package` option to `thor docs:generate`.
59
60- `thor docs:upload`
61
62 This command does two operations:
63
64 1. sync the files for the specified documentations with S3 (used by the Heroku app);
65 2. upload the documentations' packages to DevDocs's MaxCDN push zone (used by the `thor docs:download` command).
66
67 For the command to work, you must have the AWS CLI and MaxCDN credentials configured as indicated above.
68
69 **Important:** the app should always be deployed immediately after this command has finished running. Do not run this command unless you are able and ready to deploy DevDocs.
70
71 To upload all documentations that are packaged on your computer, run `thor docs:upload --packaged`.
72 To test your configuration and the effect of this command without uploading anything, pass the `--dryrun` option.
73
74- `thor docs:commit`
75
76 Shortcut command to create a Git commit for a given documentation once it has been updated. Scraper and `assets/` file changes will be committed. The commit message will include the most recent version that the documentation was updated to. If some files were missed by the commit, use `git commit --amend` to add them to the commit. The command may be run before `thor docs:upload` is run, but the commit should not be pushed to GitHub before the files have been uploaded and the app deployed.
77
78- `thor docs:clean`
79
80 Shortcut command to delete all package files (once uploaded via `thor docs:upload`, they are not needed anymore).
81
82## Deploying DevDocs
83
84Once docs have been uploaded via `thor docs:upload` (if applicable), you can push to the DevDocs master branch (or merge the PR containing the updates). If the Travis build succeeds, the Heroku application will be deployed automatically.
85
86- If you're deploying documentation updates, verify that the documentations work properly once the deploy is done. Keep in mind that you'll need to wait a few seconds for the service worker to finish caching the new assets. You should see a "DevDocs has been updated" notification appear when the caching is done, after which you need to refresh the page to see the changes.
87- If you're deploying frontend changes, monitor [Sentry](https://sentry.io/devdocs/devdocs-js/) for new JS errors once the deploy is done.
88- If you're deploying server changes, monitor New Relic (accessible through [the Heroku dashboard](https://dashboard.heroku.com/apps/devdocs)) for Ruby exceptions and throughput or response time changes once the deploy is done.
89
90If any issue arises, run `heroku rollback` to rollback to the previous version of the app (this can also be done via Heroku's UI). Note that this will not revert changes made to documentation files that were uploaded via `thor docs:upload`. Try and fix the issue as quickly as possible, then re-deploy the app. Reach out to other maintainers if you need help.
91
92If this is your first deploy, make sure another maintainer is around to assist.
93
94## List of maintainers
95
96- [Jed Fox](https://github.com/j-f1)
97- [Jasper van Merle](https://github.com/jmerle)
98- [Ahmad Abdolsaheb](https://github.com/ahmadabdolsaheb)
99- [Mrugesh Mohapatra](https://github.com/raisedadead)
100- [Thibaut Courouble](https://github.com/thibaut)
101
102Interested in helping maintain DevDocs? Come talk to us on [Gitter](https://gitter.im/FreeCodeCamp/DevDocs) :)
docs/scraper-reference.md+227−0
@@ -0,0 +1,227 @@
1**Table of contents:**
2
3* [Overview](#overview)
4* [Configuration](#configuration)
5 - [Attributes](#attributes)
6 - [Filter stacks](#filter-stacks)
7 - [Filter options](#filter-options)
8
9## Overview
10
11Starting from a root URL, scrapers recursively follow links that match a set of rules, passing each valid response through a chain of filters before writing the file on the local filesystem. They also create an index of the pages' metadata (determined by one filter), which is dumped into a JSON file at the end.
12
13Scrapers rely on the following libraries:
14
15* [Typhoeus](https://github.com/typhoeus/typhoeus) for making HTTP requests
16* [HTML::Pipeline](https://github.com/jch/html-pipeline) for applying filters
17* [Nokogiri](http://nokogiri.org/) for parsing HTML
18
19There are currently two kinds of scrapers: [`UrlScraper`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/scrapers/url_scraper.rb) which downloads files via HTTP and [`FileScraper`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/scrapers/file_scraper.rb) which reads them from the local filesystem. They function almost identically (both use URLs), except that `FileScraper` substitutes the base URL with a local path before reading a file. `FileScraper` uses the placeholder `localhost` base URL by default and includes a filter to remove any URL pointing to it at the end.
20
21To be processed, a response must meet the following requirements:
22
23* 200 status code
24* HTML content type
25* effective URL (after redirection) contained in the base URL (explained below)
26
27(`FileScraper` only checks if the file exists and is not empty.)
28
29Each URL is requested only once (case-insensitive).
30
31## Configuration
32
33Configuration is done via class attributes and divided into three main categories:
34
35* [Attributes](#attributes) — essential information such as name, version, URL, etc.
36* [Filter stacks](#filter-stacks) — the list of filters that will be applied to each page.
37* [Filter options](#filter-options) — the options passed to said filters.
38
39**Note:** scrapers are located in the [`lib/docs/scrapers`](https://github.com/Thibaut/devdocs/tree/master/lib/docs/scrapers/) directory. The class's name must be the [CamelCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename.
40
41### Attributes
42
43* `name` [String]
44 Must be unique.
45 Defaults to the class's name.
46
47* `slug` [String]
48 Must be unique, lowercase, and not include dashes (underscores are ok).
49 Defaults to `name` lowercased.
50
51* `type` [String] **(required, inherited)**
52 Defines the CSS class name (`_[type]`) and custom JavaScript class (`app.views.[Type]Page`) that will be added/loaded on each page. Documentations sharing a similar structure (e.g. generated with the same tool or originating from the same website) should use the same `type` to avoid duplicating the CSS and JS.
53 Must include lowercase letters only.
54
55* `release` [String] **(required)**
56 The version of the software at the time the scraper was last run. This is only informational and doesn't affect the scraper's behavior.
57
58* `base_url` [String] **(required in `UrlScraper`)**
59 The documents' location. Only URLs _inside_ the `base_url` will be scraped. "inside" more or less means "starting with" except that `/docs` is outside `/doc` (but `/doc/` is inside).
60 Defaults to `localhost` in `FileScraper`. _(Note: any iframe, image, or skipped link pointing to localhost will be removed by the `CleanLocalUrls` filter; the value should be overridden if the documents are available online.)_
61 Unless `root_path` is set, the root/initial URL is equal to `base_url`.
62
63* `root_path` [String] **(inherited)**
64 The path from the `base_url` of the root URL.
65
66* `initial_paths` [Array] **(inherited)**
67 A list of paths (from the `base_url`) to add to the initial queue. Useful for scraping isolated documents.
68 Defaults to `[]`. _(Note: the `root_path` is added to the array at runtime.)_
69
70* `dir` [String] **(required, `FileScraper` only)**
71 The absolute path where the files are located on the local filesystem.
72 _Note: `FileScraper` works exactly like `UrlScraper` (manipulating the same kind of URLs) except that it substitutes `base_url` with `dir` in order to read files instead of making HTTP requests._
73
74* `params` [Hash] **(inherited, `UrlScraper` only)**
75 Query string parameters to append to every URL. (e.g. `{ format: 'raw' }` → `?format=raw`)
76 Defaults to `{}`.
77
78* `abstract` [Boolean]
79 Make the scraper abstract / not runnable. Used for sharing behavior with other scraper classes (e.g. all MDN scrapers inherit from the abstract [`Mdn`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/scrapers/mdn/mdn.rb) class).
80 Defaults to `false`.
81
82### Filter stacks
83
84Each scraper has two [filter](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/filter.rb) [stacks](https://github.com/Thibaut/devdocs/blob/master/lib/docs/core/filter_stack.rb): `html_filters` and `text_filters`. They are combined into a pipeline (using the [HTML::Pipeline](https://github.com/jch/html-pipeline) library) which causes each filter to hand its output to the next filter's input.
85
86HTML filters are executed first and manipulate a parsed version of the document (a [Nokogiri](http://nokogiri.org/Nokogiri/XML/Node.html) node object), whereas text filters manipulate the document as a string. This separation avoids parsing the document multiple times.
87
88Filter stacks are like sorted sets. They can modified using the following methods:
89
90```ruby
91push(*names) # append one or more filters at the end
92insert_before(index, *names) # insert one filter before another (index can be a name)
93insert_after(index, *names) # insert one filter after another (index can be a name)
94replace(index, name) # replace one filter with another (index can be a name)
95```
96
97"names" are `require` paths relative to `Docs` (e.g. `jquery/clean_html` → `Docs::Jquery::CleanHtml`).
98
99Default `html_filters`:
100
101* [`ContainerFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/container.rb) — changes the root node of the document (remove everything outside)
102* [`CleanHtmlFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_html.rb) — removes HTML comments, `<script>`, `<style>`, etc.
103* [`NormalizeUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_urls.rb) — replaces all URLs with their fully qualified counterpart
104* [`InternalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/internal_urls.rb) — detects internal URLs (the ones to scrape) and replaces them with their unqualified, relative counterpart
105* [`NormalizePathsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_paths.rb) — makes the internal paths consistent (e.g. always end with `.html`)
106* [`CleanLocalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_local_urls.rb) — removes links, iframes and images pointing to localhost (`FileScraper` only)
107
108Default `text_filters`:
109
110* [`InnerHtmlFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/inner_html.rb) — converts the document to a string
111* [`CleanTextFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/clean_text.rb) — removes empty nodes
112* [`AttributionFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/attribution.rb) — appends the license info and link to the original document
113
114Additionally:
115
116* [`TitleFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/title.rb) is a core HTML filter, disabled by default, which prepends the document with a title (`<h1>`).
117* [`EntriesFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/entries.rb) is an abstract HTML filter that each scraper must implement and responsible for extracting the page's metadata.
118
119### Filter options
120
121The filter options are stored in the `options` Hash. The Hash is inheritable (a recursive copy) and empty by default.
122
123More information about how filters work is available on the [Filter Reference](./filter-reference.md) page.
124
125* [`ContainerFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/container.rb)
126
127 - `:container` [String or Proc]
128 A CSS selector of the container element. Everything outside of it will be removed and become unavailable to the other filters. If more than one element match the selector, the first one inside the DOM is used. If no elements match the selector, an error is raised.
129 If the value is a Proc, it is called for each page with the filter instance as argument, and should return a selector or `nil`.
130 The default container is the `<body>` element.
131 _Note: links outside of the container element will not be followed by the scraper. To remove links that should be followed, use a [`CleanHtml`](./filter-reference.md#cleanhtmlfilter) filter later in the stack._
132
133* [`NormalizeUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/normalize_urls.rb)
134 The following options are used to modify URLs in the pages. They are useful to remove duplicates (when the same page is accessible from multiple URLs) and fix websites that have a bunch of redirections in place (when URLs that should be scraped, aren't, because they are behind a redirection which is outside of the `base_url` — see the MDN scrapers for examples of this).
135
136 - `:replace_urls` [Hash]
137 Replaces all instances of a URL with another.
138 Format: `{ 'original_url' => 'new_url' }`
139 - `:replace_paths` [Hash]
140 Replaces all instances of a sub-path (path from the `base_url`) with another.
141 Format: `{ 'original_path' => 'new_path' }`
142 - `:fix_urls` [Proc]
143 Called with each URL. If the returned value is `nil`, the URL isn't modified. Otherwise the returned value is used as replacement.
144
145 _Note: before these rules are applied, all URLs are converted to their fully qualified counterpart (http://...)._
146
147* [`InternalUrlsFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/internal_urls.rb)
148
149 Internal URLs are the ones _inside_ the scraper's `base_url` ("inside" more or less means "starting with", except that `/docs` is outside `/doc`). They will be scraped unless excluded by one of the following rules. All internal URLs are converted to relative URLs inside the pages.
150
151 - `:skip_links` [Boolean or Proc]
152 If `false`, does not convert or follow any internal URL (creating a single-page documentation).
153 If the value is a Proc, it is called for each page with the filter instance as argument.
154 - `:follow_links` [Proc]
155 Called for page with the filter instance as argument. If the returned value is `false`, does not add internal URLs to the queue.
156 - `:trailing_slash` [Boolean]
157 If `true`, adds a trailing slash to all internal URLs. If `false`, removes it.
158 This is another option used to remove duplicate pages.
159 - `:skip` [Array]
160 Ignores internal URLs whose sub-paths (path from the `base_url`) are in the Array (case-insensitive).
161 - `:skip_patterns` [Array]
162 Ignores internal URLs whose sub-paths match any Regexp in the Array.
163 - `:only` [Array]
164 Ignores internal URLs whose sub-paths aren't in the Array (case-insensitive) and don't match any Regexp in `:only_patterns`.
165 - `:only_patterns` [Array]
166 Ignores internal URLs whose sub-paths don't match any Regexp in the Array and aren't in `:only`.
167
168 If the scraper has a `root_path`, the empty and `/` paths are automatically skipped.
169 If `:only` or `:only_patterns` is set, the root path is automatically added to `:only`.
170
171 _Note: pages can be excluded from the index based on their content using the [`Entries`](./filter-reference.md#entriesfilter) filter. However, their URLs will still be converted to relative in the other pages and trying to open them will return a 404 error. Although not ideal, this is often better than having to maintain a long list of `:skip` URLs._
172
173* [`AttributionFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/attribution.rb)
174
175 - `:attribution` [String] **(required)**
176 An HTML string with the copyright and license information. See the other scrapers for examples.
177
178* [`TitleFilter`](https://github.com/Thibaut/devdocs/blob/master/lib/docs/filters/core/title.rb)
179
180 - `:title` [String or Boolean or Proc]
181 Unless the value is `false`, adds a title to every page.
182 If the value is `nil`, the title is the name of the page as determined by the [`Entries`](./filter-reference.md#entriesfilter) filter. Otherwise the title is the String or the value returned by the Proc (called for each page, with the filter instance as argument). If the Proc returns `nil` or `false`, no title is added.
183 - `:root_title` [String or Boolean]
184 Overrides the `:title` option for the root page only.
185
186 _Note: this filter is disabled by default._
187
188## Keeping scrapers up-to-date
189
190In order to keep scrapers up-to-date the `get_latest_version(opts)` method should be overridden. If `self.release` is defined, this should return the latest version of the documentation. If `self.release` is not defined, it should return the Epoch time when the documentation was last modified. If the documentation will never change, simply return `1.0.0`. The result of this method is periodically reported in a "Documentation versions report" issue which helps maintainers keep track of outdated documentations.
191
192To make life easier, there are a few utility methods that you can use in `get_latest_version`:
193* `fetch(url, opts)`
194
195 Makes a GET request to the url and returns the response body.
196
197 Example: [lib/docs/scrapers/bash.rb](../lib/docs/scrapers/bash.rb)
198* `fetch_doc(url, opts)`
199
200 Makes a GET request to the url and returns the HTML body converted to a Nokogiri document.
201
202 Example: [lib/docs/scrapers/git.rb](../lib/docs/scrapers/git.rb)
203* `fetch_json(url, opts)`
204
205 Makes a GET request to the url and returns the JSON body converted to a dictionary.
206
207 Example: [lib/docs/scrapers/mdn/mdn.rb](../lib/docs/scrapers/mdn/mdn.rb)
208* `get_npm_version(package, opts)`
209
210 Returns the latest version of the given npm package.
211
212 Example: [lib/docs/scrapers/bower.rb](../lib/docs/scrapers/bower.rb)
213* `get_latest_github_release(owner, repo, opts)`
214
215 Returns the tag name of the latest GitHub release of the given repository. If the tag name is preceded by a "v", the "v" will be removed.
216
217 Example: [lib/docs/scrapers/jsdoc.rb](../lib/docs/scrapers/jsdoc.rb)
218* `get_github_tags(owner, repo, opts)`
219
220 Returns the list of tags on the given repository ([format](https://developer.github.com/v3/repos/#list-tags)).
221
222 Example: [lib/docs/scrapers/liquid.rb](../lib/docs/scrapers/liquid.rb)
223* `get_github_file_contents(owner, repo, path, opts)`
224
225 Returns the contents of the requested file in the default branch of the given repository.
226
227 Example: [lib/docs/scrapers/minitest.rb](../lib/docs/scrapers/minitest.rb)
lib/app.rb+51−47
@@ -12,6 +12,8 @@ class App < Sinatra::Application · 11 unmodified lines
12 Rack::Mime::MIME_TYPES['.webapp'] = 'application/x-web-app-manifest+json'12 Rack::Mime::MIME_TYPES['.webapp'] = 'application/x-web-app-manifest+json'
13 13
14 configure do14 configure do
15 use Rack::SslEnforcer, only_environments: ['production', 'test'], hsts: true, force_secure_cookies: false
16
15 set :sentry_dsn, ENV['SENTRY_DSN']17 set :sentry_dsn, ENV['SENTRY_DSN']
16 set :protection, except: [:frame_options, :xss_header]18 set :protection, except: [:frame_options, :xss_header]
17 19
@@ -35,6 +37,9 @@ class App < Sinatra::Application · 17 unmodified lines
35 37
36 set :csp, false38 set :csp, false
37 39
40 require 'docs'
41 Docs.generate_manifest
42
38 Dir[docs_path, root.join(assets_prefix, '*/')].each do |path|43 Dir[docs_path, root.join(assets_prefix, '*/')].each do |path|
39 sprockets.append_path(path)44 sprockets.append_path(path)
40 end45 end
@@ -48,6 +53,11 @@ class App < Sinatra::Application · 7 unmodified lines
48 end53 end
49 54
50 configure :test, :development do55 configure :test, :development do
56 require 'thor'
57 load 'tasks/sprites.thor'
58
59 SpritesCLI.new.invoke(:generate)
60
51 require 'active_support/per_thread_registry'61 require 'active_support/per_thread_registry'
52 require 'active_support/cache'62 require 'active_support/cache'
53 sprockets.cache = ActiveSupport::Cache.lookup_store :file_store, root.join('tmp', 'cache', 'assets', environment.to_s)63 sprockets.cache = ActiveSupport::Cache.lookup_store :file_store, root.join('tmp', 'cache', 'assets', environment.to_s)
@@ -67,7 +77,7 @@ class App < Sinatra::Application · 13 unmodified lines
67 set :static, false77 set :static, false
68 set :cdn_origin, 'https://cdn.devdocs.io'78 set :cdn_origin, 'https://cdn.devdocs.io'
69 set :docs_origin, '//docs.devdocs.io'79 set :docs_origin, '//docs.devdocs.io'
70 set :csp, "default-src 'self' *; script-src 'self' 'nonce-devdocs' http://cdn.devdocs.io https://cdn.devdocs.io https://www.google-analytics.com https://secure.gaug.es http://*.jquery.com https://*.jquery.com; font-src 'none'; style-src 'self' 'unsafe-inline' *; img-src 'self' * data:;"80 set :csp, "default-src 'self' *; script-src 'self' 'nonce-devdocs' https://cdn.devdocs.io https://www.google-analytics.com https://secure.gaug.es https://*.jquery.com; font-src 'none'; style-src 'self' 'unsafe-inline' *; img-src 'self' * data:;"
71 81
72 use Rack::ConditionalGet82 use Rack::ConditionalGet
73 use Rack::ETag83 use Rack::ETag
@@ -133,7 +143,7 @@ class App < Sinatra::Application · 59 unmodified lines
133 end143 end
134 144
135 def canonical_origin145 def canonical_origin
136 "http://#{request.host_with_port}"146 "https://#{request.host_with_port}"
137 end147 end
138 148
139 def browser149 def browser
@@ -190,51 +200,45 @@ class App < Sinatra::Application · 50 unmodified lines
190 request.query_string.empty? ? nil : "?#{request.query_string}"200 request.query_string.empty? ? nil : "?#{request.query_string}"
191 end201 end
192 202
193 def manifest_asset_urls203 def service_worker_asset_urls
194 @@manifest_asset_urls ||= [204 @@service_worker_asset_urls ||= [
195 javascript_path('application', asset_host: false),205 javascript_path('application', asset_host: false),
196 stylesheet_path('application'),206 stylesheet_path('application'),
197 stylesheet_path('application-dark'),207 image_path('sprites/docs.png'),
198 image_path('docs-1.png'),208 image_path('sprites/docs@2x.png'),
199 image_path('docs-1@2x.png'),209 asset_path('docs.js'),
200 image_path('docs-2.png'),210 App.production? ? nil : javascript_path('debug'),
201 image_path('docs-2@2x.png'),211 ].compact
202 asset_path('docs.js')
203 ]
204 end
205
206 def main_stylesheet_path
207 stylesheet_paths[dark_theme? ? :dark : :default]
208 end
209
210 def alternate_stylesheet_path
211 stylesheet_paths[dark_theme? ? :default : :dark]
212 end212 end
213 213
214 def stylesheet_paths214 # Returns a cache name for the service worker to use which changes if any of the assets changes
215 @@stylesheet_paths ||= {215 # When a manifest exist, this name is only created once based on the asset manifest because it never changes without a server restart
216 default: stylesheet_path('application'),216 # If a manifest does not exist, it is created every time this method is called because the assets can change while the server is running
217 dark: stylesheet_path('application-dark')217 def service_worker_cache_name
218 }218 if File.exist?(App.assets_manifest_path)
219 end219 if defined?(@@service_worker_cache_name)
220 220 return @@service_worker_cache_name
221 def app_size221 end
222 @app_size ||= memoized_cookies['size'].nil? ? '20rem' : "#{memoized_cookies['size']}px"
223 end
224 222
225 def app_layout223 digest = Sprockets::Manifest
226 memoized_cookies['layout']224 .new(nil, App.assets_manifest_path)
227 end225 .files
226 .values
227 .map {|file| file["digest"]}
228 .join
228 229
229 def app_theme230 return @@service_worker_cache_name ||= Digest::MD5.hexdigest(digest)
230 @app_theme ||= memoized_cookies['dark'].nil? ? 'default' : 'dark'231 else
231 end232 paths = App.sprockets
233 .each_file
234 .to_a
235 .reject {|file| file.start_with?(App.docs_path)}
232 236
233 def dark_theme?237 return App.sprockets.pack_hexdigest(App.sprockets.files_digest(paths))
234 app_theme == 'dark'238 end
235 end239 end
236 240
237 def redirect_via_js(path) # courtesy of HTML5 App Cache241 def redirect_via_js(path)
238 response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/'242 response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/'
239 redirect '/', 302243 redirect '/', 302
240 end244 end
@@ -253,19 +257,19 @@ class App < Sinatra::Application · 12 unmodified lines
253 before do257 before do
254 if request.host == OUT_HOST && !request.path.start_with?('/s/')258 if request.host == OUT_HOST && !request.path.start_with?('/s/')
255 query_string = "?#{request.query_string}" unless request.query_string.empty?259 query_string = "?#{request.query_string}" unless request.query_string.empty?
256 redirect "http://devdocs.io#{request.path}#{query_string}", 302260 redirect "https://devdocs.io#{request.path}#{query_string}", 302
257 end261 end
258 end262 end
259 263
260 get '/manifest.appcache' do264 get '/service-worker.js' do
261 content_type 'text/cache-manifest'265 content_type 'application/javascript'
262 expires 0, :'no-cache'266 expires 0, :'no-cache'
263 erb :manifest267 erb :'service-worker.js'
264 end268 end
265 269
266 get '/' do270 get '/' do
267 return redirect "/#q=#{params[:q]}" if params[:q]271 return redirect "/#q=#{params[:q]}" if params[:q]
268 return redirect '/' unless request.query_string.empty? # courtesy of HTML5 App Cache272 return redirect '/' unless request.query_string.empty?
269 response.headers['Content-Security-Policy'] = settings.csp if settings.csp273 response.headers['Content-Security-Policy'] = settings.csp if settings.csp
270 erb :index274 erb :index
271 end275 end
@@ -435,13 +439,13 @@ class App < Sinatra::Application · 163 unmodified lines
435 439
436 maker.channel.links.new_link do |link|440 maker.channel.links.new_link do |link|
437 link.rel = 'self'441 link.rel = 'self'
438 link.href = 'http://devdocs.io/feed.atom'442 link.href = 'https://devdocs.io/feed.atom'
439 link.type = 'application/atom+xml'443 link.type = 'application/atom+xml'
440 end444 end
441 445
442 maker.channel.links.new_link do |link|446 maker.channel.links.new_link do |link|
443 link.rel = 'alternate'447 link.rel = 'alternate'
444 link.href = 'http://devdocs.io/'448 link.href = 'https://devdocs.io/'
445 link.type = 'text/html'449 link.type = 'text/html'
446 end450 end
447 451
@@ -450,14 +454,14 @@ class App < Sinatra::Application · 2 unmodified lines
450 item.id = "tag:devdocs.io,2014:News/#{settings.news.length - i}"454 item.id = "tag:devdocs.io,2014:News/#{settings.news.length - i}"
451 item.title = news[1].split("\n").first.gsub(/<\/?[^>]*>/, '')455 item.title = news[1].split("\n").first.gsub(/<\/?[^>]*>/, '')
452 item.description do |desc|456 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/')
454 desc.type = 'html'458 desc.type = 'html'
455 end459 end
456 item.updated = "#{news.first}T14:00:00Z"460 item.updated = "#{news.first}T14:00:00Z"
457 item.published = "#{news.first}T14:00:00Z"461 item.published = "#{news.first}T14:00:00Z"
458 item.links.new_link do |link|462 item.links.new_link do |link|
459 link.rel = 'alternate'463 link.rel = 'alternate'
460 link.href = 'http://devdocs.io/'464 link.href = 'https://devdocs.io/'
461 link.type = 'text/html'465 link.type = 'text/html'
462 end466 end
463 end467 end
lib/docs.rb+18−1
@@ -1,5 +1,5 @@
1require 'bundler/setup'1require 'bundler/setup'
2Bundler.require :docs2Bundler.require :default, :docs
3 3
4require 'active_support'4require 'active_support'
5require 'active_support/core_ext'5require 'active_support/core_ext'
@@ -29,6 +29,7 @@ module Docs · 23 unmodified lines
29 self.rescue_errors = false29 self.rescue_errors = false
30 30
31 class DocNotFound < NameError; end31 class DocNotFound < NameError; end
32 class SetupError < StandardError; end
32 33
33 def self.all34 def self.all
34 Dir["#{root_path}/docs/scrapers/**/*.rb"].35 Dir["#{root_path}/docs/scrapers/**/*.rb"].
@@ -74,6 +75,22 @@ module Docs · 39 unmodified lines
74 end75 end
75 end76 end
76 77
78 def self.find_by_slug(slug, version = nil)
79 doc = all.find { |klass| klass.slug == slug }
80
81 unless doc
82 raise DocNotFound.new(%(could not find doc with "#{slug}"), slug)
83 end
84
85 if version.present?
86 version = doc.versions.find { |klass| klass.version == version || klass.version_slug == version }
87 raise DocNotFound.new(%(could not find version "#{version}" for doc "#{doc.name}"), doc.name) unless version
88 doc = version
89 end
90
91 doc
92 end
93
77 def self.generate_page(name, version, page_id)94 def self.generate_page(name, version, page_id)
78 find(name, version).store_page(store, page_id)95 find(name, version).store_page(store, page_id)
79 end96 end
lib/docs/core/doc.rb+118−0
@@ -2,6 +2,7 @@ module Docs · 1 unmodified line
2 class Doc2 class Doc
3 INDEX_FILENAME = 'index.json'3 INDEX_FILENAME = 'index.json'
4 DB_FILENAME = 'db.json'4 DB_FILENAME = 'db.json'
5 META_FILENAME = 'meta.json'
5 6
6 class << self7 class << self
7 include Instrumentable8 include Instrumentable
@@ -73,6 +74,10 @@ module Docs · 65 unmodified lines
73 File.join path, DB_FILENAME74 File.join path, DB_FILENAME
74 end75 end
75 76
77 def meta_path
78 File.join path, META_FILENAME
79 end
80
76 def as_json81 def as_json
77 json = { name: name, slug: slug, type: type }82 json = { name: name, slug: slug, type: type }
78 json[:links] = links if links.present?83 json[:links] = links if links.present?
@@ -90,6 +95,9 @@ module Docs · 11 unmodified lines
90 false95 false
91 end96 end
92 end97 end
98 rescue Docs::SetupError => error
99 puts "ERROR: #{error.message}"
100 false
93 end101 end
94 102
95 def store_pages(store)103 def store_pages(store)
@@ -107,11 +115,15 @@ module Docs · 11 unmodified lines
107 if index.present?115 if index.present?
108 store_index(store, INDEX_FILENAME, index)116 store_index(store, INDEX_FILENAME, index)
109 store_index(store, DB_FILENAME, pages)117 store_index(store, DB_FILENAME, pages)
118 store_meta(store)
110 true119 true
111 else120 else
112 false121 false
113 end122 end
114 end123 end
124 rescue Docs::SetupError => error
125 puts "ERROR: #{error.message}"
126 false
115 end127 end
116 128
117 private129 private
@@ -131,6 +143,13 @@ module Docs · 13 unmodified lines
131 instrument "#{filename.remove('.json')}.doc", before: old_json, after: new_json143 instrument "#{filename.remove('.json')}.doc", before: old_json, after: new_json
132 store.write(filename, new_json)144 store.write(filename, new_json)
133 end145 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
134 end153 end
135 154
136 def initialize155 def initialize
@@ -144,5 +163,104 @@ module Docs · 7 unmodified lines
144 def build_pages(&block)163 def build_pages(&block)
145 raise NotImplementedError164 raise NotImplementedError
146 end165 end
166
167 def get_scraper_version(opts)
168 if self.class.method_defined?(:options) and !options[:release].nil?
169 options[:release]
170 else
171 # If options[:release] does not exist, we return the Epoch timestamp of when the doc was last modified in DevDocs production
172 json = fetch_json('https://devdocs.io/docs.json', opts)
173 items = json.select {|item| item['name'] == self.class.name}
174 items = items.map {|item| item['mtime']}
175 items.max
176 end
177 end
178
179 # Should return the latest version of this documentation
180 # If options[:release] is defined, it should be in the same format
181 # If options[:release] is not defined, it should return the Epoch timestamp of when the documentation was last updated
182 # If the docs will never change, simply return '1.0.0'
183 def get_latest_version(opts)
184 raise NotImplementedError
185 end
186
187 # Returns whether or not this scraper is outdated.
188 #
189 # The default implementation assumes the documentation uses a semver(-like) approach when it comes to versions.
190 # Patch updates are ignored because there are usually little to no documentation changes in bug-fix-only releases.
191 #
192 # Scrapers of documentations that do not use this versioning approach should override this method.
193 #
194 # Examples of the default implementation:
195 # 1 -> 2 = outdated
196 # 1.1 -> 1.2 = outdated
197 # 1.1.1 -> 1.1.2 = not outdated
198 def is_outdated(scraper_version, latest_version)
199 scraper_parts = scraper_version.to_s.split(/\./).map(&:to_i)
200 latest_parts = latest_version.to_s.split(/\./).map(&:to_i)
201
202 # Only check the first two parts, the third part is for patch updates
203 [0, 1].each do |i|
204 break if i >= scraper_parts.length or i >= latest_parts.length
205 return true if latest_parts[i] > scraper_parts[i]
206 return false if latest_parts[i] < scraper_parts[i]
207 end
208
209 false
210 end
211
212 private
213
214 #
215 # Utility methods for get_latest_version
216 #
217
218 def fetch(url, opts)
219 headers = {}
220
221 if opts.key?(:github_token) and url.start_with?('https://api.github.com/')
222 headers['Authorization'] = "token #{opts[:github_token]}"
223 end
224
225 opts[:logger].debug("Fetching #{url}")
226 response = Request.run(url, { connecttimeout: 15, headers: headers })
227
228 if response.success?
229 response.body
230 else
231 reason = response.timed_out? ? "Timed out while connecting to #{url}" : "Couldn't fetch #{url} (response code #{response.code})"
232 opts[:logger].error(reason)
233 raise reason
234 end
235 end
236
237 def fetch_doc(url, opts)
238 body = fetch(url, opts)
239 Nokogiri::HTML.parse(body, nil, 'UTF-8')
240 end
241
242 def fetch_json(url, opts)
243 JSON.parse fetch(url, opts)
244 end
245
246 def get_npm_version(package, opts)
247 json = fetch_json("https://registry.npmjs.com/#{package}", opts)
248 json['dist-tags']['latest']
249 end
250
251 def get_latest_github_release(owner, repo, opts)
252 release = fetch_json("https://api.github.com/repos/#{owner}/#{repo}/releases/latest", opts)
253 tag_name = release['tag_name']
254 tag_name.start_with?('v') ? tag_name[1..-1] : tag_name
255 end
256
257 def get_github_tags(owner, repo, opts)
258 fetch_json("https://api.github.com/repos/#{owner}/#{repo}/tags", opts)
259 end
260
261 def get_github_file_contents(owner, repo, path, opts)
262 json = fetch_json("https://api.github.com/repos/#{owner}/#{repo}/contents/#{path}", opts)
263 Base64.decode64(json['content'])
264 end
147 end265 end
148end266end
lib/docs/core/filter.rb+3−12
@@ -91,19 +91,10 @@ module Docs · 90 unmodified lines
91 super91 super
92 end92 end
93 93
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
105 def clean_path(path)94 def clean_path(path)
106 path.gsub %r{[!;:]+}, '-'95 path = path.gsub %r{[!;:]}, '-'
96 path = path.gsub %r{\+}, '_plus_'
97 path
107 end98 end
108 end99 end
109end100end
lib/docs/core/manifest.rb+4−22
@@ -14,32 +14,14 @@ module Docs · 13 unmodified lines
14 end14 end
15 15
16 def as_json16 def as_json
17 indexed_docs.map do |doc|17 @docs.each_with_object [] do |doc, result|
18 json = doc.as_json18 next unless @store.exist?(doc.meta_path)
19 json[:mtime] = doc_mtime(doc)19 result << JSON.parse(@store.read(doc.meta_path))
20 json[:db_size] = doc_db_size(doc)
21 json
22 end20 end
23 end21 end
24 22
25 def to_json23 def to_json
26 JSON.generate(as_json)24 JSON.pretty_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)
43 end25 end
44 end26 end
45end27end
lib/docs/core/requester.rb+8−4
@@ -1,5 +1,7 @@
1module Docs1module Docs
2 class Requester < Typhoeus::Hydra2 class Requester < Typhoeus::Hydra
3 include Instrumentable
4
3 attr_reader :request_options5 attr_reader :request_options
4 6
5 def self.run(urls, options = {}, &block)7 def self.run(urls, options = {}, &block)
@@ -20,7 +22,7 @@ module Docs · 14 unmodified lines
20 def initialize(options = {})22 def initialize(options = {})
21 @request_options = options.extract!(:request_options)[:request_options].try(:dup) || {}23 @request_options = options.extract!(:request_options)[:request_options].try(:dup) || {}
22 options[:max_concurrency] ||= 2024 options[:max_concurrency] ||= 20
23 options[:pipelining] = false25 options[:pipelining] = 0
24 super26 super
25 end27 end
26 28
@@ -52,9 +54,11 @@ module Docs · 25 unmodified lines
52 end54 end
53 55
54 def handle_response(response)56 def handle_response(response)
55 on_response.each do |callback|57 instrument 'handle_response.requester', url: response.url do
56 result = callback.call(response)58 on_response.each do |callback|
57 result.each { |url| request(url) } if result.is_a? Array59 result = callback.call(response)
60 result.each { |url| request(url) } if result.is_a?(Array)
61 end
58 end62 end
59 end63 end
60 end64 end
lib/docs/core/scraper.rb+1−1
@@ -41,7 +41,7 @@ module Docs · 40 unmodified lines
41 self.html_filters = FilterStack.new41 self.html_filters = FilterStack.new
42 self.text_filters = FilterStack.new42 self.text_filters = FilterStack.new
43 43
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'
45 text_filters.push 'images' # ensure the images filter runs after all html filters45 text_filters.push 'images' # ensure the images filter runs after all html filters
46 text_filters.push 'inner_html', 'clean_text', 'attribution'46 text_filters.push 'inner_html', 'clean_text', 'attribution'
47 47
lib/docs/core/scrapers/file_scraper.rb+21−7
@@ -1,14 +1,13 @@
1module Docs1module Docs
2 class FileScraper < Scraper2 class FileScraper < Scraper
3 SOURCE_DIRECTORY = File.expand_path '../../../../../docs', __FILE__
4
3 Response = Struct.new :body, :url5 Response = Struct.new :body, :url
4 6
5 class << self7 class << self
6 attr_accessor :dir
7
8 def inherited(subclass)8 def inherited(subclass)
9 super9 super
10 subclass.base_url = base_url10 subclass.base_url = base_url
11 subclass.dir = dir
12 end11 end
13 end12 end
14 13
@@ -16,13 +15,25 @@ module Docs · 1 unmodified line
16 15
17 html_filters.push 'clean_local_urls'16 html_filters.push 'clean_local_urls'
18 17
18 def source_directory
19 @source_directory ||= File.join(SOURCE_DIRECTORY, self.class.path)
20 end
21
19 private22 private
20 23
24 def assert_source_directory_exists
25 unless Dir.exists?(source_directory)
26 raise SetupError, "The #{self.class.name} scraper requires the original documentation files to be stored in the \"#{source_directory}\" directory."
27 end
28 end
29
21 def request_one(url)30 def request_one(url)
22 Response.new read_file(file_path_for(url)), URL.parse(url)31 assert_source_directory_exists
32 Response.new read_file(File.join(source_directory, url_to_path(url))), URL.parse(url)
23 end33 end
24 34
25 def request_all(urls)35 def request_all(urls)
36 assert_source_directory_exists
26 queue = [urls].flatten37 queue = [urls].flatten
27 until queue.empty?38 until queue.empty?
28 result = yield request_one(queue.shift)39 result = yield request_one(queue.shift)
@@ -34,12 +45,15 @@ module Docs · 5 unmodified lines
34 response.body.present?45 response.body.present?
35 end46 end
36 47
37 def file_path_for(url)48 def url_to_path(url)
38 File.join self.class.dir, url.remove(base_url.to_s)49 url.remove(base_url.to_s)
39 end50 end
40 51
41 def read_file(path)52 def read_file(path)
42 File.read(path) rescue nil53 File.read(path)
54 rescue
55 instrument 'warn.doc', msg: "Failed to open file: #{path}"
56 nil
43 end57 end
44 end58 end
45end59end
lib/docs/core/scrapers/url_scraper.rb+46−1
@@ -13,6 +13,8 @@ module Docs · 12 unmodified lines
13 end13 end
14 end14 end
15 15
16 @@rate_limiter = nil
17
16 self.params = {}18 self.params = {}
17 self.headers = { 'User-Agent' => 'DevDocs' }19 self.headers = { 'User-Agent' => 'DevDocs' }
18 self.force_gzip = false20 self.force_gzip = false
@@ -24,6 +26,15 @@ module Docs · 5 unmodified lines
24 end26 end
25 27
26 def request_all(urls, &block)28 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
27 Requester.run urls, request_options: request_options, &block38 Requester.run urls, request_options: request_options, &block
28 end39 end
29 40
@@ -35,7 +46,11 @@ module Docs · 5 unmodified lines
35 46
36 def process_response?(response)47 def process_response?(response)
37 if response.error?48 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
39 elsif response.blank?54 elsif response.blank?
40 raise "Empty response body: #{response.url}"55 raise "Empty response body: #{response.url}"
41 end56 end
@@ -161,5 +176,35 @@ module Docs · 119 unmodified lines
161 super.merge! redirections: self.class.redirections176 super.merge! redirections: self.class.redirections
162 end177 end
163 end178 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
164 end209 end
165end210end
lib/docs/core/url.rb+1−1
@@ -72,7 +72,7 @@ module Docs · 71 unmodified lines
72 72
73 if base == dest73 if base == dest
74 ''74 ''
75 elsif dest.start_with? File.join(base, '')75 elsif dest.start_with?(::File.join(base, ''))
76 url.path[(path.length)..-1]76 url.path[(path.length)..-1]
77 end77 end
78 end78 end
lib/docs/filters/angular/clean_html.rb+9−1
@@ -7,7 +7,7 @@ module Docs · 6 unmodified lines
7 at_css('h1').content = 'Angular Documentation'7 at_css('h1').content = 'Angular Documentation'
8 end8 end
9 9
10 css('br', 'hr', '.material-icons', '.header-link').remove10 css('br', 'hr', '.material-icons', '.header-link', '.breadcrumb').remove
11 11
12 css('.content', 'article', '.api-header', 'section', '.instance-member').each do |node|12 css('.content', 'article', '.api-header', 'section', '.instance-member').each do |node|
13 node.before(node.children).remove13 node.before(node.children).remove
@@ -86,6 +86,14 @@ module Docs · 72 unmodified lines
86 at_css('h1').content = subpath.remove('api/')86 at_css('h1').content = subpath.remove('api/')
87 end87 end
88 88
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
89 doc97 doc
90 end98 end
91 end99 end
lib/docs/filters/angular/entries.rb+2−0
@@ -16,6 +16,8 @@ module Docs · 15 unmodified lines
16 node.next_element.content.remove('@angular/')16 node.next_element.content.remove('@angular/')
17 elsif at_css('.api-type-label.module')17 elsif at_css('.api-type-label.module')
18 name.split('/').first18 name.split('/').first
19 elsif slug.start_with?('api/')
20 slug.split('/').second
19 else21 else
20 'Miscellaneous'22 'Miscellaneous'
21 end23 end
lib/docs/filters/ansible/clean_html.rb+2−2
@@ -4,8 +4,8 @@ module Docs · 3 unmodified lines
4 def call4 def call
5 @doc = at_css('#page-content')5 @doc = at_css('#page-content')
6 6
7 css('.__cf_email__').each do |node|7 css('font').each do |node|
8 node.replace(decode_cloudflare_email(node['data-cfemail']))8 node.before(node.children).remove
9 end9 end
10 10
11 doc11 doc
lib/docs/filters/ansible/entries.rb+27−3
@@ -11,14 +11,38 @@ module Docs · 10 unmodified lines
11 end11 end
12 12
13 def get_type13 def get_type
14 if slug.include?('module')14 if version == '2.4'
15 if name =~ /\A[a-z]/ && node = css('.toctree-l2.current').last15 if slug.include?('module')
16 "Modules: #{node.content.remove(' Modules')}"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'
17 else37 else
18 'Modules'38 'Modules'
19 end39 end
20 elsif slug.include?('playbook')40 elsif slug.include?('playbook')
21 'Playbooks'41 'Playbooks'
42 elsif slug =~ /\Auser_guide\//
43 'Guides: User'
44 elsif slug =~ /\Ascenario_guides\//
45 'Guides: Scenarios'
22 elsif slug.include?('guide')46 elsif slug.include?('guide')
23 'Guides'47 'Guides'
24 else48 else
lib/docs/filters/babel/clean_html.rb+38−0
@@ -0,0 +1,38 @@
1module 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
38end
lib/docs/filters/babel/entries.rb+40−0
@@ -0,0 +1,40 @@
1module 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
40end
lib/docs/filters/bash/clean_html.rb+74−0
@@ -0,0 +1,74 @@
1module Docs
2 class Bash
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove the navigation header and footer and the lines underneath and above it
6 at_css('.header + hr').remove
7 line_above = at_xpath('//div[@class="header"]/preceding::hr[1]')
8 line_above.remove unless line_above.nil?
9 css('.header').remove
10
11 # Remove chapter and section numbers from title
12 title_node = at_css('h1, h2, h3, h4, h5, h6')
13 title_node.content = title_node.content.gsub(/(\d+\.?)+/, '').strip
14 title_node.name = 'h1'
15
16 # Remove the "D. " from names like "D. Concept Index" and "D. Function Index"
17 title_node.content = title_node.content[3..-1] if title_node.content.start_with?("D. ")
18
19 # Remove columns containing a single space from tables
20 # In the original reference they are used to add width between two columns
21 xpath('//td[text()=" " and not(descendant::*)]').remove
22
23 # Add id's to additional entry nodes
24 css('dl > dt > code').each do |node|
25 # Only take the direct text (i.e. "<div>Hello <span>World</span></div>" becomes "Hello")
26 node['id'] = node.xpath('text()').to_s.strip
27 end
28
29 # Fix hashes of index entries so they link to the correct hash on the linked page
30 css('table[class^=index-] td[valign=top] > a').each do |node|
31 path = node['href'].split('#')[0]
32 hash = node.content
33
34 # Fix the index entries linking to the Special Parameters page
35 # There are multiple index entries that should link to the same paragraph on that page
36 # Example: the documentation for "$!" is equal to the documentation for "!"
37 if path.downcase.include?('special-parameters')
38 if hash.size > 1 && hash[0] == '$'
39 hash = hash[1..-1]
40 end
41 end
42
43 node['href'] = path + '#' + hash
44 end
45
46 # Fix index table letter hashes (the "Jump to" hashes)
47 css('table[class^=index-] th > a').each do |node|
48 node['id'] = node['name']
49 end
50
51 # Remove the rows with a horizontal line in them from the index tables
52 css('td[colspan="4"]').remove
53
54 # Remove additional text from menu entry and index entry cells
55 css('td[valign=top]').each do |node|
56 link = node.at_css('a')
57 node.children = link unless link.nil?
58 end
59
60 css('tt', 'code', 'table').remove_attr('class')
61
62 css('tt').each do |node|
63 node.name = 'code'
64 end
65
66 css('pre').each do |node|
67 node.content = node.content
68 end
69
70 doc
71 end
72 end
73 end
74end
lib/docs/filters/bash/entries.rb+56−0
@@ -0,0 +1,56 @@
1module Docs
2 class Bash
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 name = at_css('hr + a + *').content.gsub(/(\d+\.?)+/, '')
6
7 # Remove the "D. " from names like "D. Concept Index" and "D. Function Index"
8 name = name[3..-1] if name.start_with?('D. ')
9
10 name
11 end
12
13 def get_type
14 return 'Manual: Appendices' if name.start_with?('Appendix')
15 return 'Manual: Indexes' if at_css('a[rel=up]').content.include?("Index")
16 "Manual"
17 end
18
19 def additional_entries
20 entry_type = {
21 "Function Index" => "Functions",
22 "Index of Shell Builtin Commands" => "Builtin Commands",
23 "Index of Shell Reserved Words" => "Reserved Words",
24 "Parameter and Variable Index" => "Parameters and Variables"
25 }[name]
26
27 # Only extract additional entries from certain index pages
28 return [] if entry_type.nil?
29
30 entries = []
31
32 css('table[class^=index-] td[valign=top] > a').each_slice(2) do |entry_node, section_node|
33 entry_name = entry_node.content
34
35 page = section_node['href'].split('#')[0]
36 hash = entry_name
37
38 # The Special Parameters page has multiple additional entries which should link to the same paragraph
39 # Example: the documentation for "$!" is equal to the documentation for "!"
40 if page == 'special-parameters'
41 if hash.size > 1 && hash[0] == '$'
42 hash = hash[1..-1]
43 end
44 end
45
46 # Construct path to the page which the index links to
47 entry_path = '/html_node/' + page + '#' + hash
48
49 entries << [entry_name, entry_path, entry_type]
50 end
51
52 entries
53 end
54 end
55 end
56end
lib/docs/filters/bluebird/clean_html.rb+36−0
@@ -0,0 +1,36 @@
1module 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
36end
lib/docs/filters/bluebird/entries.rb+48−0
@@ -0,0 +1,48 @@
1module 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
48end
lib/docs/filters/bootstrap/clean_html_v3.rb+1−5
@@ -27,7 +27,7 @@ module Docs · 26 unmodified lines
27 node.remove_attribute('data-example-id')27 node.remove_attribute('data-example-id')
28 prev = node.previous_element28 prev = node.previous_element
29 prev = prev.previous_element until prev['id']29 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>)
31 end31 end
32 end32 end
33 33
@@ -39,10 +39,6 @@ module Docs · 5 unmodified lines
39 node['class'] = 'col'39 node['class'] = 'col'
40 end40 end
41 41
42 css('.__cf_email__').each do |node|
43 node.replace(decode_cloudflare_email(node['data-cfemail']))
44 end
45
46 css('figure.highlight').each do |node|42 css('figure.highlight').each do |node|
47 code = node.at_css('code')43 code = node.at_css('code')
48 node['data-language'] = code['data-lang']44 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
17 end17 end
18 18
19 css('.bd-example', '.responsive-utilities-test').each do |node|19 css('.bd-example', '.responsive-utilities-test').each do |node|
20 next unless node.previous_element
21
20 if node.previous_element['class'].try(:include?, 'bd-example')22 if node.previous_element['class'].try(:include?, 'bd-example')
21 node.remove23 node.remove
22 else24 else
@@ -26,7 +28,7 @@ module Docs · 3 unmodified lines
26 node.remove_attribute('data-example-id')28 node.remove_attribute('data-example-id')
27 prev = node.previous_element29 prev = node.previous_element
28 prev = prev.previous_element until prev['id']30 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>)
30 end32 end
31 end33 end
32 34
@@ -38,10 +40,6 @@ module Docs · 5 unmodified lines
38 node['class'] = 'col'40 node['class'] = 'col'
39 end41 end
40 42
41 css('.__cf_email__').each do |node|
42 node.replace(decode_cloudflare_email(node['data-cfemail']))
43 end
44
45 css('.highlight').each do |node|43 css('.highlight').each do |node|
46 code = node.at_css('code')44 code = node.at_css('code')
47 node['data-language'] = code['data-lang']45 node['data-language'] = code['data-lang']
lib/docs/filters/c/clean_html.rb+11−2
@@ -4,12 +4,17 @@ module Docs · 3 unmodified lines
4 def call4 def call
5 css('h1').remove if root_page?5 css('h1').remove if root_page?
6 6
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
7 css('#siteSub', '#contentSub', '.printfooter', '.t-navbar', '.editsection', '#toc',12 css('#siteSub', '#contentSub', '.printfooter', '.t-navbar', '.editsection', '#toc',
8 '.t-dsc-sep', '.t-dcl-sep', '#catlinks', '.ambox-notice', '.mw-cite-backlink',13 '.t-dsc-sep', '.t-dcl-sep', '#catlinks', '.ambox-notice', '.mw-cite-backlink',
9 '.t-sdsc-sep:first-child:last-child', '.t-example-live-link',14 '.t-sdsc-sep:first-child:last-child', '.t-example-live-link',
10 '.t-dcl-rev-num > .t-dcl-rev-aux ~ tr:not(.t-dcl-rev-aux) > td:nth-child(2)').remove15 '.t-dcl-rev-num > .t-dcl-rev-aux ~ tr:not(.t-dcl-rev-aux) > td:nth-child(2)').remove
11 16
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',
13 'th > div', 'td > div', '.t-dsc-see', '.mainpagediv', 'code > b', 'tbody').each do |node|18 'th > div', 'td > div', '.t-dsc-see', '.mainpagediv', 'code > b', 'tbody').each do |node|
14 node.before(node.children).remove19 node.before(node.children).remove
15 end20 end
@@ -78,7 +83,7 @@ module Docs · 62 unmodified lines
78 end83 end
79 node.inner_html = node.inner_html.strip84 node.inner_html = node.inner_html.strip
80 node << '.' if node.content =~ /[a-zA-Z0-9\)]\z/85 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')
82 end87 end
83 88
84 css('pre').each do |node|89 css('pre').each do |node|
@@ -100,6 +105,10 @@ module Docs · 15 unmodified lines
100 node.content = node.content105 node.content = node.content
101 end106 end
102 107
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
103 doc112 doc
104 end113 end
105 end114 end
lib/docs/filters/cmake/entries.rb+1−0
@@ -20,6 +20,7 @@ module Docs · 19 unmodified lines
20 'prop_sf' => 'Properties: Source Files',20 'prop_sf' => 'Properties: Source Files',
21 'prop_test' => 'Properties: Tests',21 'prop_test' => 'Properties: Tests',
22 'prop_tgt' => 'Properties: Targets',22 'prop_tgt' => 'Properties: Targets',
23 'envvar' => 'Environment Variables',
23 'variable' => 'Variables' }24 'variable' => 'Variables' }
24 25
25 def get_name26 def get_name
lib/docs/filters/codeception/clean_html.rb+6−0
@@ -4,6 +4,12 @@ module Docs · 3 unmodified lines
4 def call4 def call
5 @doc = doc.at_css('#page')5 @doc = doc.at_css('#page')
6 6
7 css('.algolia__search-content').remove
8
9 css('.algolia__initial-content').each do |node|
10 node.before(node.children).remove
11 end
12
7 while doc.element_children.length == 113 while doc.element_children.length == 1
8 doc.first_element_child.before(doc.first_element_child.children).remove14 doc.first_element_child.before(doc.first_element_child.children).remove
9 end15 end
lib/docs/filters/codeception/entries.rb+1−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Codeception2 class Codeception
3 class EntriesFilter < Docs::EntriesFilter3 class EntriesFilter < Docs::EntriesFilter
4 def get_name4 def get_name
5 name = (at_css('h1') || at_css('h2')).content5 name = (at_css('.js-algolia__initial-content h1') || at_css('.js-algolia__initial-content h2')).content
6 6
7 if number = subpath[/\A\d+/]7 if number = subpath[/\A\d+/]
8 name.prepend "#{number.to_i}. "8 name.prepend "#{number.to_i}. "
lib/docs/filters/codeceptjs/entries.rb+2−2
@@ -20,8 +20,8 @@ module Docs · 19 unmodified lines
20 def additional_entries20 def additional_entries
21 return [] unless subpath.start_with?('helpers') && subpath != 'helpers/'21 return [] unless subpath.start_with?('helpers') && subpath != 'helpers/'
22 22
23 css('h2').each_with_object [] do |node, entries|23 css('h2, h3').each_with_object [] do |node, entries|
24 next if node['id'] == 'access-from-helpers'24 next if node['id'] == 'access-from-helpers' || node.content !~ /\s*[a-z_]/
25 entries << ["#{node.content} (#{name})", node['id']]25 entries << ["#{node.content} (#{name})", node['id']]
26 end26 end
27 end27 end
lib/docs/filters/cordova/clean_html.rb+1−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Cordova2 class Cordova
3 class CleanHtmlFilter < Filter3 class CleanHtmlFilter < Filter
4 def call4 def call
5 @doc = at_css('.page-content > div')5 @doc = at_css('#page-toc-source') || at_css('.page-content > div')
6 6
7 at_css('h1').content = 'Apache Cordova' if root_page?7 at_css('h1').content = 'Apache Cordova' if root_page?
8 8
lib/docs/filters/core/parse_cf_email.rb+21−0
@@ -0,0 +1,21 @@
1# frozen_string_literal: true
2
3module 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
21end
lib/docs/filters/cpp/entries.rb+2−0
@@ -31,6 +31,8 @@ module Docs · 30 unmodified lines
31 'Keywords'31 'Keywords'
32 elsif subpath.start_with?('experimental')32 elsif subpath.start_with?('experimental')
33 'Experimental libraries'33 'Experimental libraries'
34 elsif subpath.start_with?('language/')
35 'Language'
34 elsif type = at_css('.t-navbar > div:nth-child(4) > :first-child').try(:content)36 elsif type = at_css('.t-navbar > div:nth-child(4) > :first-child').try(:content)
35 type.strip!37 type.strip!
36 type.remove! ' library'38 type.remove! ' library'
lib/docs/filters/cpp/fix_urls.rb+0−1
@@ -4,7 +4,6 @@ module Docs · 3 unmodified lines
4 def call4 def call
5 html.gsub! File.join(Cpp.base_url, Cpp.root_path), Cpp.base_url[0..-2]5 html.gsub! File.join(Cpp.base_url, Cpp.root_path), Cpp.base_url[0..-2]
6 html.gsub! %r{#{Cpp.base_url}([^"']+?)\.html}, "#{Cpp.base_url}\\1"6 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'
8 html7 html
9 end8 end
10 end9 end
lib/docs/filters/crystal/clean_html.rb+1−1
@@ -16,7 +16,7 @@ module Docs · 15 unmodified lines
16 end16 end
17 17
18 def api18 def api
19 @doc = at_css('#main-content')19 @doc = at_css('.main-content')
20 20
21 at_css('h1 + p').remove if root_page?21 at_css('h1 + p').remove if root_page?
22 22
lib/docs/filters/crystal/entries.rb+1−1
@@ -32,7 +32,7 @@ module Docs · 31 unmodified lines
32 if hierarchy && hierarchy.content.include?('Exception')32 if hierarchy && hierarchy.content.include?('Exception')
33 'Exceptions'33 'Exceptions'
34 else34 else
35 type = at_css('#types-list > ul > .current > a').content35 type = at_css('.types-list > ul > .current > a').content
36 type = 'Float' if type.start_with?('Float')36 type = 'Float' if type.start_with?('Float')
37 type = 'Int' if type.start_with?('Int')37 type = 'Int' if type.start_with?('Int')
38 type = 'UInt' if type.start_with?('UInt')38 type = 'UInt' if type.start_with?('UInt')
lib/docs/filters/css/entries.rb+23−12
@@ -6,13 +6,16 @@ module Docs · 5 unmodified lines
6 'CSS_Background_and_Borders' => 'Backgrounds & Borders',6 'CSS_Background_and_Borders' => 'Backgrounds & Borders',
7 'CSS_Columns' => 'Multi-column Layout',7 'CSS_Columns' => 'Multi-column Layout',
8 'CSS_Flexible_Box_Layout' => 'Flexible Box Layout',8 'CSS_Flexible_Box_Layout' => 'Flexible Box Layout',
9 'CSS_Fonts' => 'Fonts',
9 'CSS_Grid_Layout' => 'Grid Layout',10 'CSS_Grid_Layout' => 'Grid Layout',
10 'CSS_Images' => 'Image Values',11 'CSS_Images' => 'Images',
11 'CSS_Lists_and_Counters' => 'Lists & Counters',12 'CSS_Lists_and_Counters' => 'Lists',
12 'CSS_Transforms' => 'Transforms',13 'CSS_Transforms' => 'Transforms',
13 'Media_Queries' => 'Media Queries',14 'Media_Queries' => 'Media Queries',
15 'filter-function' => 'Filter Effects',
14 'transform-function' => 'Transforms',16 'transform-function' => 'Transforms',
15 '@media' => 'Media Queries',17 '@media' => 'Media Queries',
18 'overscroll' => 'Overscroll',
16 'text-size-adjust' => 'Miscellaneous',19 'text-size-adjust' => 'Miscellaneous',
17 'resolved_value' => 'Miscellaneous',20 'resolved_value' => 'Miscellaneous',
18 'touch-action' => 'Miscellaneous',21 'touch-action' => 'Miscellaneous',
@@ -34,7 +37,7 @@ module Docs · 15 unmodified lines
34 "#{super}()"37 "#{super}()"
35 elsif slug =~ /\A[a-z]+_/i38 elsif slug =~ /\A[a-z]+_/i
36 slug.to_s.gsub('_', ' ').gsub('/', ': ')39 slug.to_s.gsub('_', ' ').gsub('/', ': ')
37 elsif slug.start_with?('transform-function')40 elsif slug.start_with?('transform-function') || slug.start_with?('filter-function')
38 slug.split('/').last + '()'41 slug.split('/').last + '()'
39 else42 else
40 super43 super
@@ -42,7 +45,7 @@ module Docs · 1 unmodified line
42 end45 end
43 46
44 def get_type47 def get_type
45 if slug.include?('-webkit') || slug.include?('-moz')48 if slug.include?('-webkit') || slug.include?('-moz') || slug.include?('-ms')
46 'Extensions'49 'Extensions'
47 elsif type = TYPE_BY_PATH[slug.split('/').first]50 elsif type = TYPE_BY_PATH[slug.split('/').first]
48 type51 type
@@ -51,6 +54,7 @@ module Docs · 2 unmodified lines
51 type.remove! ' Module'54 type.remove! ' Module'
52 type.remove! %r{ Level \d\z}55 type.remove! %r{ Level \d\z}
53 type.remove! %r{\(.*\)}56 type.remove! %r{\(.*\)}
57 type.remove! %r{ \d\z}
54 type.sub! 'and', '&'58 type.sub! 'and', '&'
55 type.strip!59 type.strip!
56 type = 'Grid Layout' if type.include?('Grid Layout')60 type = 'Grid Layout' if type.include?('Grid Layout')
@@ -65,18 +69,22 @@ module Docs · 8 unmodified lines
65 'Pseudo-Elements'69 'Pseudo-Elements'
66 elsif name.start_with?(':')70 elsif name.start_with?(':')
67 'Selectors'71 'Selectors'
72 elsif name.start_with?('display-')
73 'Display'
68 else74 else
69 'Miscellaneous'75 'Miscellaneous'
70 end76 end
71 end77 end
72 78
73 STATUSES = {79 STATUSES = {
74 'spec-Living' => 0,80 'spec-Living' => 0,
75 'spec-REC' => 1,81 'spec-REC' => 1,
76 'spec-CR' => 2,82 'spec-CR' => 2,
77 'spec-LC' => 3,83 'spec-PR' => 3,
78 'spec-WD' => 4,84 'spec-LC' => 4,
79 'spec-ED' => 585 'spec-WD' => 5,
86 'spec-ED' => 6,
87 'spec-Obsolete' => 7
80 }88 }
81 89
82 PRIORITY_STATUSES = %w(spec-REC spec-CR)90 PRIORITY_STATUSES = %w(spec-REC spec-CR)
@@ -84,11 +92,14 @@ module Docs · 1 unmodified line
84 92
85 def get_spec93 def get_spec
86 return unless table = at_css('#Specifications + table') || css('.standard-table').last94 return unless table = at_css('#Specifications + table') || css('.standard-table').last
95
87 specs = table.css('tbody tr').to_a96 specs = table.css('tbody tr').to_a
88 # [link, span]97 # [link, span]
89 specs.map! { |node| [node.at_css('> td:nth-child(1) > a'), node.at_css('> td:nth-child(2) > span')] }98 specs.map! { |node| [node.at_css('> td:nth-child(1) > a'), node.at_css('> td:nth-child(2) > span')] }
90 # ignore non-CSS specs99 # ignore non-CSS specs
91 specs.select! { |pair| pair.first && pair.first['href'] =~ /css|fxtf|fullscreen|svg/i && !pair.first['href'].include?('compat.spec') }100 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 }
92 # ["Spec", "spec-REC"]103 # ["Spec", "spec-REC"]
93 specs.map! { |pair| [pair.first.child.content, pair.second['class']] }104 specs.map! { |pair| [pair.first.child.content, pair.second['class']] }
94 # sort by status105 # sort by status
@@ -106,8 +117,8 @@ module Docs · 11 unmodified lines
106 'shape' => [117 'shape' => [
107 %w(rect() Syntax) ],118 %w(rect() Syntax) ],
108 'timing-function' => [119 'timing-function' => [
109 %w(cubic-bezier() The_cubic-bezier()_class_of_timing-functions),120 %w(cubic-bezier()),
110 %w(steps() The_steps()_class_of_timing-functions),121 %w(steps()),
111 %w(linear linear),122 %w(linear linear),
112 %w(ease ease),123 %w(ease ease),
113 %w(ease-in ease-in),124 %w(ease-in ease-in),
lib/docs/filters/d/entries.rb+2−0
@@ -19,6 +19,8 @@ module Docs · 18 unmodified lines
19 name.split('.')[0..2].join('.')19 name.split('.')[0..2].join('.')
20 elsif name.start_with?('ddmd')20 elsif name.start_with?('ddmd')
21 'ddmd'21 'ddmd'
22 elsif name.start_with?('rt')
23 'rt'
22 else24 else
23 name.split('.')[0..1].join('.')25 name.split('.')[0..1].join('.')
24 end26 end
lib/docs/filters/dart/clean_html.rb+66−0
@@ -0,0 +1,66 @@
1module Docs
2 class Dart
3 class CleanHtmlFilter < Filter
4 def call
5 # Move the title into the main content node in the v1 docs
6 title = at_css('h1.title')
7 if title
8 name = title.children.last.content.strip
9 kind = title.at_css('.kind').content
10 at_css('.main-content').prepend_child("<h1>#{name} #{kind}</h1>")
11 end
12
13 # Add a title to the homepage of the v2 docs
14 if subpath == 'index.html' && at_css('.main-content > h1').nil?
15 at_css('.main-content').prepend_child('<h1>Dart SDK</h1>')
16 end
17
18 # Add the library to the main content (it is not always visible in the menu entry)
19 breadcrumbs = at_css('.breadcrumbs').css('li:not(.self-crumb) > a')
20 if breadcrumbs.length > 1
21 library = breadcrumbs[1].content
22
23 # Generate the link to the homepage of the library
24 with_hypens = library.gsub(/:/, '-')
25 location = "#{'../' * subpath.count('/')}#{with_hypens}/#{with_hypens}-library"
26 link = "<a href=\"#{location}\" class=\"_links-link\">#{library}</span>"
27
28 # Add the link to the main title, just like how the "Homepage" and "Source code" links appear
29 at_css('.main-content').prepend_child("<p class=\"_links\">#{link}</p>")
30 end
31
32 # Extract the actual content
33 # We can't use options[:container] here because the entries filter uses the breadcrumbs node
34 @doc = at_css('.main-content')
35
36 # Move the features (i.e. "read-only, inherited") into the blue header
37 css('.features').each do |node|
38 header = node.xpath('parent::dd/preceding::dt').last
39 header.add_child node unless header.nil?
40 end
41
42 css('section').each do |node|
43 if node['id'] && node.first_element_child
44 node.first_element_child['id'] ||= node['id']
45 end
46
47 node.before(node.children).remove
48 end
49
50 css('span').each do |node|
51 node.before(node.children).remove
52 end
53
54 # Make code blocks detectable by Prism
55 css('pre').each do |node|
56 node['data-language'] = 'dart'
57 node.content = node.content
58 end
59
60 css('.properties', '.property', '.callables', '.callable').remove_attr('class')
61
62 doc
63 end
64 end
65 end
66end
lib/docs/filters/dart/entries.rb+42−0
@@ -0,0 +1,42 @@
1module Docs
2 class Dart
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 title = at_css('h1.title')
6 if title # v1
7 name = title.element_children.last.content.strip
8 kind = title.at_css('.kind').content
9 else # v2
10 title = at_css('.main-content > h1')
11 name = title.content[/(.*)( )/, 1].split(' top-level')[0]
12 kind = title.content[/(.*)( )(.+)/, 3]
13 end
14
15 breadcrumbs = at_css('.breadcrumbs').css('li:not(.self-crumb) > a')
16 first_part = ''
17
18 if breadcrumbs.length == 2 && !kind.include?('class')
19 first_part = breadcrumbs[1].content
20 elsif breadcrumbs.length == 3
21 first_part = breadcrumbs[2].content
22 end
23
24 separator = ''
25
26 unless first_part.empty?
27 if kind.include?('class')
28 separator = ':'
29 else
30 separator = '.'
31 end
32 end
33
34 "#{first_part}#{separator}#{name}"
35 end
36
37 def get_type
38 at_css('.breadcrumbs > li:nth-child(2)').content.split(' ')[0]
39 end
40 end
41 end
42end
lib/docs/filters/dom/clean_html.rb+1−1
@@ -30,7 +30,7 @@ module Docs · 29 unmodified lines
30 end30 end
31 31
32 # Remove <div> wrapping .overheadIndicator32 # Remove <div> wrapping .overheadIndicator
33 css('div > .overheadIndicator:first-child:last-child').each do |node|33 css('div > .overheadIndicator:first-child:last-child', 'div > .blockIndicator:first-child:last-child').each do |node|
34 node.parent.replace(node)34 node.parent.replace(node)
35 end35 end
36 36
lib/docs/filters/dom/entries.rb+28−3
@@ -6,10 +6,13 @@ module Docs · 5 unmodified lines
6 'EXT_' => 'WebGL',6 'EXT_' => 'WebGL',
7 'OES_' => 'WebGL',7 'OES_' => 'WebGL',
8 'WEBGL_' => 'WebGL',8 'WEBGL_' => 'WebGL',
9 'Sensor API' => 'Sensors',
9 'Ambient Light' => 'Ambient Light',10 'Ambient Light' => 'Ambient Light',
10 'Audio' => 'Audio',11 'Audio' => 'Audio',
11 'Battery Status' => 'Battery Status',12 'Battery Status' => 'Battery Status',
12 'Canvas ' => 'Canvas',13 'Canvas ' => 'Canvas',
14 'Clipboard' => 'Clipboard',
15 'Content Security' => 'Content Security Policy',
13 'Cooperative Scheduling' => 'Scheduling',16 'Cooperative Scheduling' => 'Scheduling',
14 'CSS Font Loading' => 'CSS',17 'CSS Font Loading' => 'CSS',
15 'CSS Object Model' => 'CSS',18 'CSS Object Model' => 'CSS',
@@ -20,10 +23,13 @@ module Docs · 4 unmodified lines
20 'Encrypted Media Extensions' => 'Encrypted Media',23 'Encrypted Media Extensions' => 'Encrypted Media',
21 'Fetch' => 'Fetch',24 'Fetch' => 'Fetch',
22 'File API' => 'File',25 'File API' => 'File',
26 'Fullscreen' => 'Fullscreen',
23 'Geolocation' => 'Geolocation',27 'Geolocation' => 'Geolocation',
24 'Geometry' => 'Geometry',28 'Geometry' => 'Geometry',
25 'High Resolution Time' => 'Performance',29 'High Resolution Time' => 'Performance',
26 'Intersection' => 'Intersection Observer',30 'Intersection' => 'Intersection Observer',
31 'Keyboard' => 'Keyboard',
32 'Media Capabilities' => 'Media',
27 'Media Capture' => 'Media',33 'Media Capture' => 'Media',
28 'Media Session' => 'Media',34 'Media Session' => 'Media',
29 'Media Source' => 'Media',35 'Media Source' => 'Media',
@@ -32,6 +38,7 @@ module Docs · 2 unmodified lines
32 'MIDI' => 'Audio',38 'MIDI' => 'Audio',
33 'Navigation Timing' => 'Performance',39 'Navigation Timing' => 'Performance',
34 'Network Information' => 'Network Information',40 'Network Information' => 'Network Information',
41 'Orientation Sensor' => 'Sensors',
35 'Payment' => 'Payments',42 'Payment' => 'Payments',
36 'Performance Timeline' => 'Performance',43 'Performance Timeline' => 'Performance',
37 'Pointer Events' => 'Pointer Events',44 'Pointer Events' => 'Pointer Events',
@@ -45,15 +52,22 @@ module Docs · 7 unmodified lines
45 'Stream API' => 'Media Streams',52 'Stream API' => 'Media Streams',
46 'Streams' => 'Media Streams',53 'Streams' => 'Media Streams',
47 'Touch Events' => 'Touch Events',54 'Touch Events' => 'Touch Events',
55 'Visual Viewport' => 'Visual Viewport',
48 'Web Animations' => 'Animation',56 'Web Animations' => 'Animation',
49 'Web App Manifest' => 'Web App Manifest',57 'Web App Manifest' => 'Web App Manifest',
50 'Budget' => 'Budget',58 'Budget' => 'Budget',
59 'Web Authentication' => 'Authentication',
60 'Web Locks' => 'Locks',
51 'Web Workers' => 'Web Workers',61 'Web Workers' => 'Web Workers',
52 'WebGL' => 'WebGL',62 'WebGL' => 'WebGL',
53 'WebRTC' => 'WebRTC',63 'WebRTC' => 'WebRTC',
54 'WebVR' => 'WebVR' }64 'WebUSB' => 'WebUSB',
65 'WebVR' => 'WebVR',
66 'WebVTT' => 'WebVTT' }
55 67
56 TYPE_BY_NAME_STARTS_WITH = {68 TYPE_BY_NAME_STARTS_WITH = {
69 'AbortController' => 'Fetch',
70 'AbortSignal' => 'Fetch',
57 'Ambient' => 'Ambient Light',71 'Ambient' => 'Ambient Light',
58 'Attr' => 'Nodes',72 'Attr' => 'Nodes',
59 'Audio' => 'Audio',73 'Audio' => 'Audio',
@@ -61,6 +75,7 @@ module Docs · 1 unmodified line
61 'Broadcast' => 'Broadcast Channel',75 'Broadcast' => 'Broadcast Channel',
62 'Budget' => 'Budget',76 'Budget' => 'Budget',
63 'Canvas' => 'Canvas',77 'Canvas' => 'Canvas',
78 'Clipboard' => 'Clipboard',
64 'CSS' => 'CSS',79 'CSS' => 'CSS',
65 'CharacterData' => 'Nodes',80 'CharacterData' => 'Nodes',
66 'ChildNode' => 'Nodes',81 'ChildNode' => 'Nodes',
@@ -69,6 +84,7 @@ module Docs · 2 unmodified lines
69 'CustomElement' => 'Custom Elements',84 'CustomElement' => 'Custom Elements',
70 'DataTransfer' => 'Drag & Drop',85 'DataTransfer' => 'Drag & Drop',
71 'document' => 'Document',86 'document' => 'Document',
87 'Document Object' => 'DOM',
72 'DocumentFragment' => 'DocumentFragment',88 'DocumentFragment' => 'DocumentFragment',
73 'DocumentType' => 'Nodes',89 'DocumentType' => 'Nodes',
74 'DOM' => 'DOM',90 'DOM' => 'DOM',
@@ -79,10 +95,12 @@ module Docs · 4 unmodified lines
79 'Fetch' => 'Fetch',95 'Fetch' => 'Fetch',
80 'File' => 'File',96 'File' => 'File',
81 'GlobalEventHandlers' => 'GlobalEventHandlers',97 'GlobalEventHandlers' => 'GlobalEventHandlers',
98 'HMDVR' => 'WebVR',
82 'history' => 'History',99 'history' => 'History',
83 'HTML Drag' => 'Drag & Drop',100 'HTML Drag' => 'Drag & Drop',
84 'HTML' => 'Elements',101 'HTML' => 'Elements',
85 'IDB' => 'IndexedDB',102 'IDB' => 'IndexedDB',
103 'Keyboard' => 'Keyboard',
86 'location' => 'Location',104 'location' => 'Location',
87 'navigator' => 'Navigator',105 'navigator' => 'Navigator',
88 'MediaKeySession' => 'Encrypted Media',106 'MediaKeySession' => 'Encrypted Media',
@@ -90,6 +108,7 @@ module Docs · 1 unmodified line
90 'MediaSession' => 'Media Session',108 'MediaSession' => 'Media Session',
91 'MediaTrack' => 'Media Streams',109 'MediaTrack' => 'Media Streams',
92 'Message' => 'Channel Messaging',110 'Message' => 'Channel Messaging',
111 'Mutation' => 'DOM',
93 'NamedNode' => 'Nodes',112 'NamedNode' => 'Nodes',
94 'Node' => 'Nodes',113 'Node' => 'Nodes',
95 'Notification' => 'Notification',114 'Notification' => 'Notification',
@@ -105,9 +124,13 @@ module Docs · 9 unmodified lines
105 'screen' => 'Screen',124 'screen' => 'Screen',
106 'Selection' => 'Selection',125 'Selection' => 'Selection',
107 'Shadow' => 'Shadow DOM',126 'Shadow' => 'Shadow DOM',
127 'StaticRange' => 'Range',
128 'Streams' => 'Media Streams',
129 'StyleProperty' => 'CSS',
108 'StyleSheet' => 'CSS',130 'StyleSheet' => 'CSS',
109 'Stylesheet' => 'CSS',131 'Stylesheet' => 'CSS',
110 'SVG' => 'SVG',132 'SVG' => 'SVG',
133 'TextTrack' => 'WebVTT',
111 'TimeRanges' => 'Media',134 'TimeRanges' => 'Media',
112 'timing' => 'Performance',135 'timing' => 'Performance',
113 'Timing' => 'Performance',136 'Timing' => 'Performance',
@@ -116,6 +139,7 @@ module Docs · 2 unmodified lines
116 'URL' => 'URL',139 'URL' => 'URL',
117 'VR' => 'WebVR',140 'VR' => 'WebVR',
118 'WebSocket' => 'Web Sockets',141 'WebSocket' => 'Web Sockets',
142 'USB' => 'WebUSB',
119 'window' => 'Window',143 'window' => 'Window',
120 'Window' => 'Window',144 'Window' => 'Window',
121 'XMLHttpRequest' => 'XMLHTTPRequest' }145 'XMLHttpRequest' => 'XMLHTTPRequest' }
@@ -145,6 +169,7 @@ module Docs · 23 unmodified lines
145 'timing' => 'Performance',169 'timing' => 'Performance',
146 'Timing' => 'Performance',170 'Timing' => 'Performance',
147 'udio' => 'Audio',171 'udio' => 'Audio',
172 'VRDevice' => 'WebVR',
148 'WebGL' => 'WebGL',173 'WebGL' => 'WebGL',
149 'WEBGL' => 'WebGL',174 'WEBGL' => 'WebGL',
150 'WebRTC' => 'WebRTC',175 'WebRTC' => 'WebRTC',
@@ -164,7 +189,6 @@ module Docs · 13 unmodified lines
164 189
165 CLEANUP_NAMES = %w(190 CLEANUP_NAMES = %w(
166 CSS\ Object\ Model.191 CSS\ Object\ Model.
167 Document\ Object\ Model.
168 Tutorial.192 Tutorial.
169 XMLHttpRequest.193 XMLHttpRequest.
170 ANGLE\ instanced\ arrays.)194 ANGLE\ instanced\ arrays.)
@@ -172,6 +196,7 @@ module Docs · 1 unmodified line
172 def get_name196 def get_name
173 name = super197 name = super
174 CLEANUP_NAMES.each { |str| name.remove!(str) }198 CLEANUP_NAMES.each { |str| name.remove!(str) }
199 name.sub! %r{Document\ Object\ Model\.}i, 'Document Object Model: '
175 name.sub! 'Input.', 'HTMLInputElement.'200 name.sub! 'Input.', 'HTMLInputElement.'
176 name.sub! 'window.navigator', 'navigator'201 name.sub! 'window.navigator', 'navigator'
177 name.sub! 'API.', 'API: '202 name.sub! 'API.', 'API: '
@@ -224,7 +249,7 @@ module Docs · 46 unmodified lines
224 249
225 def include_default_entry?250 def include_default_entry?
226 return true if type == 'Console'251 return true if type == 'Console'
227 return true unless node = doc.at_css('.overheadIndicator')252 return true unless node = doc.at_css('.overheadIndicator, .blockIndicator')
228 node = node.parent while node.parent != doc253 node = node.parent while node.parent != doc
229 return true if node.previous_element.try(:name).in?(%w(h2 h3))254 return true if node.previous_element.try(:name).in?(%w(h2 h3))
230 content = node.content255 content = node.content
lib/docs/filters/electron/clean_html.rb+10−2
@@ -4,7 +4,7 @@ module Docs · 3 unmodified lines
4 def call4 def call
5 css('.header-link', 'hr + .text-center', 'hr').remove5 css('.header-link', 'hr + .text-center', 'hr').remove
6 6
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|
8 node.before(node.children).remove8 node.before(node.children).remove
9 end9 end
10 10
@@ -16,18 +16,26 @@ module Docs · 5 unmodified lines
16 end16 end
17 end17 end
18 18
19 at_css('h2').name = 'h1' unless at_css('h1')19 at_css('h2').name = 'h1' if !at_css('h1') && at_css('h2')
20 20
21 css('h3', 'h4', 'h5').each do |node|21 css('h3', 'h4', 'h5').each do |node|
22 node.name = node.name.sub(/\d/) { |i| i.to_i - 1 } unless node.name == 'h3' && node.at_css('code')22 node.name = node.name.sub(/\d/) { |i| i.to_i - 1 } unless node.name == 'h3' && node.at_css('code')
23 end if !at_css('h2') && at_css('h4')23 end if !at_css('h2') && at_css('h4')
24 24
25 css('h1 > a', 'h2 > a', 'h3 > a', 'h4 > a').each do |node|
26 node.before(node.children).remove
27 end
28
25 css('div.highlighter-rouge').each do |node|29 css('div.highlighter-rouge').each do |node|
26 node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']30 node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']
27 node.content = node.content.strip31 node.content = node.content.strip
28 node.name = 'pre'32 node.name = 'pre'
29 end33 end
30 34
35 css('pre > code.hljs').each do |node|
36 node.parent['data-language'] = node['class'][/language-(\w+)/, 1]
37 end
38
31 css('.highlighter-rouge').remove_attr('class')39 css('.highlighter-rouge').remove_attr('class')
32 40
33 css('pre').each do |node|41 css('pre').each do |node|
lib/docs/filters/electron/entries.rb+8−8
@@ -2,6 +2,8 @@ module Docs · 1 unmodified line
2 class Electron2 class Electron
3 class EntriesFilter < Docs::EntriesFilter3 class EntriesFilter < Docs::EntriesFilter
4 def get_name4 def get_name
5 return 'API' if subpath == '/api'
6
5 name = at_css('h1, h2').content7 name = at_css('h1, h2').content
6 name.remove! 'Class: '8 name.remove! 'Class: '
7 name.remove! ' Object'9 name.remove! ' Object'
@@ -12,11 +14,13 @@ module Docs · 4 unmodified lines
12 end14 end
13 15
14 def get_type16 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))
16 'Guides'20 'Guides'
17 elsif subpath.start_with?('development')21 elsif subpath.start_with?('/development')
18 'Guides: Development'22 '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))
20 'API'24 'API'
21 elsif at_css('h1, h2').content.include?(' Object')25 elsif at_css('h1, h2').content.include?(' Object')
22 'API: Objects'26 'API: Objects'
@@ -26,7 +30,7 @@ module Docs · 3 unmodified lines
26 end30 end
27 31
28 def additional_entries32 def additional_entries
29 return [] unless slug.start_with?('api/')33 return [] unless subpath.start_with?('/api')
30 34
31 css('h3 > code', 'h4 > code').each_with_object [] do |node, entries|35 css('h3 > code', 'h4 > code').each_with_object [] do |node, entries|
32 next if node.previous.try(:content).present? || node.next.try(:content).present?36 next if node.previous.try(:content).present? || node.next.try(:content).present?
@@ -37,10 +41,6 @@ module Docs · 4 unmodified lines
37 entries << [name, node.parent['id']] unless name == self.name41 entries << [name, node.parent['id']] unless name == self.name
38 end42 end
39 end43 end
40
41 def include_default_entry?
42 slug != 'api/'
43 end
44 end44 end
45 end45 end
46end46end
lib/docs/filters/elixir/clean_html.rb+6−0
@@ -57,8 +57,14 @@ module Docs · 56 unmodified lines
57 node.parent.after(node)57 node.parent.after(node)
58 end58 end
59 59
60 css('.signature').each do |node|
61 non_text_children = node.xpath('node()[not(self::text())]')
62 non_text_children.to_a.reverse.each { |child| node.parent.add_next_sibling(child) }
63 end
64
60 css('pre').each do |node|65 css('pre').each do |node|
61 node['data-language'] = 'elixir'66 node['data-language'] = 'elixir'
67 node.content = node.content
62 end68 end
63 end69 end
64 end70 end
lib/docs/filters/eslint/clean_html.rb+28−0
@@ -0,0 +1,28 @@
1module 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
28end
lib/docs/filters/eslint/entries.rb+25−0
@@ -0,0 +1,25 @@
1module 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
25end
lib/docs/filters/express/clean_html.rb+5−0
@@ -42,6 +42,11 @@ module Docs · 41 unmodified lines
42 node.parent.content = node.parent.content42 node.parent.content = node.parent.content
43 end43 end
44 44
45 # Fix links to the method reference
46 css('a').each do |node|
47 node['href'] = node['href'].sub('4x/api', 'index')
48 end
49
45 doc50 doc
46 end51 end
47 end52 end
lib/docs/filters/flow/clean_html.rb+0−4
@@ -45,10 +45,6 @@ module Docs · 44 unmodified lines
45 end45 end
46 end46 end
47 47
48 css('.__cf_email__').each do |node|
49 node.replace(decode_cloudflare_email(node['data-cfemail']))
50 end
51
52 css('.editor').each do |node|48 css('.editor').each do |node|
53 pre = node.at_css('.editor-code > pre')49 pre = node.at_css('.editor-code > pre')
54 pre['data-language'] = 'javascript'50 pre['data-language'] = 'javascript'
lib/docs/filters/flow/entries.rb+1−0
@@ -6,6 +6,7 @@ module Docs · 5 unmodified lines
6 end6 end
7 7
8 def get_type8 def get_type
9 return 'React' if slug.start_with?('react')
9 type = at_css('.guide-nav .nav-item').content.strip10 type = at_css('.guide-nav .nav-item').content.strip
10 type.remove! %r{ \(.*}11 type.remove! %r{ \(.*}
11 type12 type
lib/docs/filters/github/clean_html.rb+1−1
@@ -9,7 +9,7 @@ module Docs · 8 unmodified lines
9 9
10 css('.highlight > pre').each do |node|10 css('.highlight > pre').each do |node|
11 node['data-language'] = node.parent['class'][/highlight-source-(\w+)/, 1]11 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
13 node.parent.replace(node)13 node.parent.replace(node)
14 end14 end
15 15
lib/docs/filters/go/clean_html.rb+1−0
@@ -17,6 +17,7 @@ module Docs · 16 unmodified lines
17 css('#plusone', '#nav', '.pkgGopher', '#footer', '.collapsed', '.permalink', '#pkg-callgraph').remove17 css('#plusone', '#nav', '.pkgGopher', '#footer', '.collapsed', '.permalink', '#pkg-callgraph').remove
18 18
19 css('span[style]', '.toggleVisible', '.expanded', 'div.toggle').each do |node|19 css('span[style]', '.toggleVisible', '.expanded', 'div.toggle').each do |node|
20 node.first_element_child['id'] = node['id'] if node['id']
20 node.before(node.children).remove21 node.before(node.children).remove
21 end22 end
22 23
lib/docs/filters/graphite/clean_html.rb+27−0
@@ -0,0 +1,27 @@
1module Docs
2 class Graphite
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove the paragraph icon after all headers
6 css('.headerlink').remove
7
8 css('dl.function > dt').each do |node|
9 node.content = node.content
10 end
11
12 css('.section').each do |node|
13 node.before(node.children).remove
14 end
15
16 css('div[class*="highlight-"]').each do |node|
17 node.content = node.content.strip
18 node.name = 'pre'
19 node['data-language'] = node['class'][/highlight\-(\w+)/, 1]
20 node.remove_attribute('class')
21 end
22
23 doc
24 end
25 end
26 end
27end
lib/docs/filters/graphite/entries.rb+46−0
@@ -0,0 +1,46 @@
1module Docs
2 class Graphite
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 at_css('h1').children[0].to_s
6 end
7
8 def get_type
9 get_name
10 end
11
12 def additional_entries
13 entries = []
14
15 # Sections
16 css('.section > .section').each do |node|
17 title = node.at_css('h2, h3')
18
19 next if title.nil?
20
21 # Move the id attribute to the title
22 # If this is excluded, the complete section will be highlighted in yellow when someone navigates to it
23 title['id'] = node['id']
24 node.remove_attribute('id')
25
26 parent_title_selector = "parent::div[@class='section']/preceding::#{title.name == 'h2' ? 'h1' : 'h2'}"
27
28 entries << [
29 title.children[0].to_s,
30 title['id'],
31 title.xpath(parent_title_selector).last.children[0].to_s
32 ]
33 end
34
35 # Functions
36 css('dl.function > dt').each do |node|
37 name = node.at_css('.descname').content
38 name << '()'
39 entries << [name, node['id'], 'Functions']
40 end
41
42 entries
43 end
44 end
45 end
46end
lib/docs/filters/handlebars/clean_html.rb+44−0
@@ -0,0 +1,44 @@
1module Docs
2 class Handlebars
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove the t-shirt shop advertisement
6 css('#callout').remove
7
8 # The title filter is used to add titles to pages without one, remove original headers
9 css('h1').remove
10
11 # Remove the link to the issue tracker
12 css('.issue-tracker').remove
13
14 css('pre').each do |node|
15 # Remove nested nodes inside pre tags
16 node.content = node.content
17
18 # Add syntax highlighting
19 node['data-language'] = 'html'
20 end
21
22 # Transform 'Learn More' links to headers in the "Getting Started" part of the homepage
23 # If this step is skipped, that section looks cluttered with 4 sub-sections without any dividers
24 css('#getting-started + .contents a.more-info').each do |node|
25 clone = node.clone
26
27 # Move it to the top of the sub-section
28 node.parent.prepend_child(clone)
29
30 # Turn it into a header
31 clone.name = 'h3'
32
33 # Remove the "Learn More: " part
34 clone.content = clone.content[12..-1]
35 end
36
37 # Remove class attributes from div elements to reduce file size
38 css('div').remove_attr('class')
39
40 doc
41 end
42 end
43 end
44end
lib/docs/filters/handlebars/entries.rb+19−0
@@ -0,0 +1,19 @@
1module Docs
2 class Handlebars
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 subpath[0..-6].titleize
6 end
7
8 def get_type
9 name
10 end
11
12 def additional_entries
13 css('h2, h3').to_a.map do |node|
14 [node.content.strip, node['id'], root_page? ? 'Manual' : nil]
15 end
16 end
17 end
18 end
19end
lib/docs/filters/haskell/clean_html.rb+4−0
@@ -65,6 +65,10 @@ module Docs · 64 unmodified lines
65 end65 end
66 end66 end
67 67
68 css('pre code').each do |node|
69 node.before(node.children).remove
70 end
71
68 doc72 doc
69 end73 end
70 end74 end
lib/docs/filters/haskell/entries.rb+2−2
@@ -52,7 +52,7 @@ module Docs · 51 unmodified lines
52 return [] if subpath.start_with?('users_guide')52 return [] if subpath.start_with?('users_guide')
53 return [] if IGNORE_ENTRIES_PATHS.include?(subpath.split('/').last)53 return [] if IGNORE_ENTRIES_PATHS.include?(subpath.split('/').last)
54 54
55 css('#synopsis > ul > li').each_with_object [] do |node, entries|55 css('#synopsis > details > ul > li').each_with_object [] do |node, entries|
56 link = node.at_css('a')56 link = node.at_css('a')
57 name = node.content.strip57 name = node.content.strip
58 name.remove! %r{\A(?:module|data|newtype|class|type family m|type)\s+}58 name.remove! %r{\A(?:module|data|newtype|class|type family m|type)\s+}
@@ -75,7 +75,7 @@ module Docs · 16 unmodified lines
75 end75 end
76 76
77 def include_default_entry?77 def include_default_entry?
78 subpath.start_with?('users_guide') || at_css('#synopsis > ul > li')78 subpath.start_with?('users_guide') || at_css('#synopsis > details > ul > li')
79 end79 end
80 end80 end
81 end81 end
lib/docs/filters/homebrew/clean_html.rb+24−0
@@ -0,0 +1,24 @@
1module 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
24end
lib/docs/filters/homebrew/entries.rb+34−0
@@ -0,0 +1,34 @@
1module 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
34end
lib/docs/filters/html/entries.rb+1−1
@@ -28,7 +28,7 @@ module Docs · 27 unmodified lines
28 28
29 def include_default_entry?29 def include_default_entry?
30 return false if %w(Element/Heading_Elements).include?(slug)30 return false if %w(Element/Heading_Elements).include?(slug)
31 (node = doc.at_css '.overheadIndicator').nil? || node.content.exclude?('not on a standards track')31 (node = doc.at_css '.overheadIndicator, .blockIndicator').nil? || node.content.exclude?('not on a standards track')
32 end32 end
33 33
34 def additional_entries34 def additional_entries
lib/docs/filters/javascript/clean_html.rb+2−2
@@ -11,12 +11,12 @@ module Docs · 10 unmodified lines
11 11
12 def other12 def other
13 # Remove "style" attribute13 # Remove "style" attribute
14 css('.inheritsbox', '.overheadIndicator').each do |node|14 css('.inheritsbox', '.overheadIndicator', '.blockIndicator').each do |node|
15 node.remove_attribute 'style'15 node.remove_attribute 'style'
16 end16 end
17 17
18 # Remove <div> wrapping .overheadIndicator18 # Remove <div> wrapping .overheadIndicator
19 css('div > .overheadIndicator:first-child:last-child').each do |node|19 css('div > .overheadIndicator:first-child:last-child', 'div > .blockIndicator:first-child:last-child').each do |node|
20 node.parent.replace(node)20 node.parent.replace(node)
21 end21 end
22 end22 end
lib/docs/filters/javascript/entries.rb+2−2
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Javascript2 class Javascript
3 class EntriesFilter < Docs::EntriesFilter3 class EntriesFilter < Docs::EntriesFilter
4 TYPES = %w(Array ArrayBuffer Atomics Boolean DataView Date Function4 TYPES = %w(Array ArrayBuffer Atomics Boolean DataView Date Function
5 Generator Intl JSON Map Math Number Object Promise Reflect RegExp5 Generator Intl JSON Map Math Number Object PluralRules Promise Reflect RegExp
6 Set SharedArrayBuffer SIMD String Symbol TypedArray WeakMap WeakSet)6 Set SharedArrayBuffer SIMD String Symbol TypedArray WeakMap WeakSet)
7 INTL_OBJECTS = %w(Collator DateTimeFormat NumberFormat)7 INTL_OBJECTS = %w(Collator DateTimeFormat NumberFormat)
8 8
@@ -88,7 +88,7 @@ module Docs · 79 unmodified lines
88 end88 end
89 89
90 def include_default_entry?90 def include_default_entry?
91 node = doc.at_css '.overheadIndicator, .warning'91 node = doc.at_css '.blockIndicator, .warning'
92 92
93 # Can't use :first-child because #doc is a DocumentFragment93 # Can't use :first-child because #doc is a DocumentFragment
94 return true unless node && node.parent == doc && !node.previous_element94 return true unless node && node.parent == doc && !node.previous_element
lib/docs/filters/jekyll/clean_html.rb+43−0
@@ -0,0 +1,43 @@
1module 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
43end
lib/docs/filters/jekyll/entries.rb+28−0
@@ -0,0 +1,28 @@
1module 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
28end
lib/docs/filters/jest/clean_html.rb+4−3
@@ -12,13 +12,14 @@ module Docs · 11 unmodified lines
12 node.before(node.children).remove12 node.before(node.children).remove
13 end13 end
14 14
15 css('.anchor[name]').each do |node|15 css('.anchor').each do |node|
16 node.parent['id'] = node['name']16 node.parent['id'] = node['id']
17 node.remove17 node.remove
18 end18 end
19 19
20 css('pre').each do |node|20 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']
22 node.content = node.content23 node.content = node.content
23 end24 end
24 25
lib/docs/filters/jest/entries.rb+1−1
@@ -6,7 +6,7 @@ module Docs · 5 unmodified lines
6 end6 end
7 7
8 def get_type8 def get_type
9 type = at_css('.navItemActive').ancestors('.navGroup').first.at_css('h3').content9 type = at_css('.navListItemActive').ancestors('.navGroup').first.at_css('h3').content
10 10
11 if type == 'Introduction'11 if type == 'Introduction'
12 'Guides: Introduction'12 'Guides: Introduction'
lib/docs/filters/jsdoc/clean_html.rb+26−0
@@ -0,0 +1,26 @@
1module 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
26end
lib/docs/filters/jsdoc/entries.rb+28−0
@@ -0,0 +1,28 @@
1module 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
28end
lib/docs/filters/koa/clean_html.rb+42−0
@@ -0,0 +1,42 @@
1# frozen_string_literal: true
2
3module 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
42end
lib/docs/filters/koa/entries.rb+36−0
@@ -0,0 +1,36 @@
1module 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
36end
lib/docs/filters/kotlin/clean_html.rb+7−0
@@ -46,6 +46,13 @@ module Docs · 45 unmodified lines
46 parent.content = parent.content46 parent.content = parent.content
47 parent['data-language'] = 'kotlin'47 parent['data-language'] = 'kotlin'
48 end48 end
49
50 css('.tags').each do |wrapper|
51 platforms = wrapper.css('.platform:not(.tag-value-Common)').to_a
52 platforms = platforms.map { |node| "#{node.content} (#{node['data-tag-version']})" }
53 platforms = "<b>Platform and version requirements:</b> #{platforms.join ", "}"
54 wrapper.replace(platforms)
55 end
49 end56 end
50 end57 end
51 end58 end
lib/docs/filters/kotlin/entries.rb+3−1
@@ -5,7 +5,9 @@ module Docs · 4 unmodified lines
5 if subpath.start_with?('api')5 if subpath.start_with?('api')
6 breadcrumbs[1..-1].join('.')6 breadcrumbs[1..-1].join('.')
7 else7 else
8 (at_css('h1') || at_css('h2')).content8 node = (at_css('h1') || at_css('h2'))
9 return node.content unless node.nil?
10 subpath[/\/([a-z0-9_-]+)\./][1..-2].titleize.sub('Faq', 'FAQ')
9 end11 end
10 end12 end
11 13
lib/docs/filters/laravel/clean_html.rb+0−4
@@ -8,10 +8,6 @@ module Docs · 7 unmodified lines
8 other8 other
9 end9 end
10 10
11 css('a.__cf_email__').each do |node|
12 node.replace(decode_cloudflare_email(node['data-cfemail']))
13 end
14
15 # Remove code highlighting11 # Remove code highlighting
16 css('pre').each do |node|12 css('pre').each do |node|
17 node.content = node.content13 node.content = node.content
lib/docs/filters/leaflet/clean_html.rb+40−0
@@ -0,0 +1,40 @@
1module 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
40end
lib/docs/filters/leaflet/entries.rb+33−0
@@ -0,0 +1,33 @@
1module 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
33end
lib/docs/filters/mdn/clean_html.rb+90−0
@@ -14,6 +14,8 @@ module Docs · 13 unmodified lines
14 '#Quick_Links',14 '#Quick_Links',
15 'hr']15 'hr']
16 16
17 BROWSER_UNNECESSARY_CLASS_REGEX = /\s*bc-browser[\w_-]+/
18
17 def call19 def call
18 css(*REMOVE_NODES).remove20 css(*REMOVE_NODES).remove
19 21
@@ -61,6 +63,94 @@ module Docs · 41 unmodified lines
61 node.previous_element << node63 node.previous_element << node
62 end64 end
63 65
66 # New compatibility tables
67
68 css('.bc-data #Legend + dl', '.bc-data #Legend', '.bc-data #Legend_2 + dl', '.bc-data #Legend_2', '.bc-browser-name').remove
69
70 css('abbr.only-icon[title="Full support"]',
71 'abbr.only-icon[title="Partial support"]',
72 'abbr.only-icon[title="No support"]',
73 'abbr.only-icon[title="See implementation notes"]').remove
74
75 css('.bc-data .ic-altname', '.bc-data .ic-deprecated', '.bc-data .ic-non-standard', '.bc-data .ic-experimental').each do |node|
76 node.parent.remove
77 end
78
79 css('abbr.only-icon').each do |node|
80 node.replace(node.content)
81 end
82
83 css('.bc-table .bc-platforms td', '.bc-table .bc-browsers td').each do |node|
84 node.name = 'th'
85 end
86
87 css('.bc-data').each do |node|
88 link = node.at_css('.bc-github-link')
89 prev = node.previous_element
90 prev = prev.previous_element until prev.name == 'h2'
91 prev.add_child(link)
92
93 node.before(node.children).remove
94 end
95
96 css('.bc-table').each do |node|
97 desktop_table = node
98
99 mobile_table = node.dup
100 desktop_table.after(mobile_table)
101
102 if desktop_table.at_css('.bc-platform-server')
103 server_table = node.dup
104 mobile_table.after(server_table)
105 end
106
107 desktop_columns = desktop_table.at_css('th.bc-platform-desktop')['colspan'].to_i
108 mobile_columns = desktop_table.at_css('th.bc-platform-mobile')['colspan'].to_i
109
110 desktop_table.css('.bc-platform-mobile').remove
111 desktop_table.css('.bc-platform-server').remove
112 desktop_table.css('.bc-browsers th').to_a[(desktop_columns + 1)..-1].each(&:remove)
113 desktop_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|
114 line.css('td').to_a[(desktop_columns)..-1].each(&:remove)
115 end
116
117 mobile_table.css('.bc-platform-desktop').remove
118 mobile_table.css('.bc-platform-server').remove
119 mobile_table.css('.bc-browsers th').to_a[1..(desktop_columns)].each(&:remove)
120 mobile_table.css('.bc-browsers th').to_a[(mobile_columns + 1)..-1].each(&:remove)
121 mobile_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|
122 line.css('td').to_a[0..(desktop_columns - 1)].each(&:remove)
123 line.css('td').to_a[(mobile_columns)..-1].each(&:remove)
124 end
125
126 if server_table
127 server_table.css('.bc-platform-desktop').remove
128 server_table.css('.bc-platform-mobile').remove
129 server_table.css('.bc-browsers th').to_a[1..(desktop_columns + mobile_columns)].each(&:remove)
130 server_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|
131 line.css('td').to_a[0..(desktop_columns + mobile_columns - 1)].each(&:remove)
132 end
133 end
134 end
135
136 # Reduce page size to make the offline bundle smaller.
137 css('.bc-supports-unknown').remove_attr('class')
138 css('td[class*="bc-platform"], th[class*="bc-platform"]').remove_attr('class')
139 css('td[class*="bc-browser"], th[class*="bc-browser"]').each do |node|
140 class_name = node['class']
141 class_name.remove!(BROWSER_UNNECESSARY_CLASS_REGEX)
142
143 if class_name.present?
144 node['class'] = class_name
145 else
146 node.remove_attribute('class')
147 end
148 end
149
150 css('abbr[title*="Compatibility unknown"]').each do |node|
151 node.before(node.children).remove
152 end
153
64 doc154 doc
65 end155 end
66 end156 end
lib/docs/filters/nginx/clean_html.rb+4−0
@@ -30,6 +30,10 @@ module Docs · 29 unmodified lines
30 node['data-language'] = 'nginx'30 node['data-language'] = 'nginx'
31 end31 end
32 32
33 css('code code').each do |node|
34 node.before(node.children).remove
35 end
36
33 doc37 doc
34 end38 end
35 end39 end
lib/docs/filters/nginx/entries.rb+1−1
@@ -20,7 +20,7 @@ module Docs · 19 unmodified lines
20 def additional_entries20 def additional_entries
21 css('h1 + ul a').each_with_object [] do |node, entries|21 css('h1 + ul a').each_with_object [] do |node, entries|
22 name = node.content.strip22 name = node.content.strip
23 next if name =~ /\A[A-Z]/23 next if name =~ /\A[A-Z]/ || name.start_with?('/')
24 24
25 id = node['href'].remove('#')25 id = node['href'].remove('#')
26 next if id.blank?26 next if id.blank?
lib/docs/filters/node/clean_html.rb+1−5
@@ -16,16 +16,12 @@ module Docs · 15 unmodified lines
16 16
17 css('pre').each do |node|17 css('pre').each do |node|
18 if lang = node.at_css('code')['class']18 if lang = node.at_css('code')['class']
19 node['data-language'] = lang.remove('lang-')19 node['data-language'] = lang.remove(%r{lang(uage)?-})
20 end20 end
21 21
22 node.content = node.content22 node.content = node.content
23 end23 end
24 24
25 css('.__cf_email__').each do |node|
26 node.replace(decode_cloudflare_email(node['data-cfemail']))
27 end
28
29 doc25 doc
30 end26 end
31 end27 end
lib/docs/filters/node/entries.rb+2−0
@@ -26,6 +26,7 @@ module Docs · 25 unmodified lines
26 26
27 def get_type27 def get_type
28 type = at_css('h1').content.strip28 type = at_css('h1').content.strip
29 type.remove! %r{\[.*\]}
29 REPLACE_TYPES[type] || "#{type.first.upcase}#{type[1..-1]}"30 REPLACE_TYPES[type] || "#{type.first.upcase}#{type[1..-1]}"
30 end31 end
31 32
@@ -40,6 +41,7 @@ module Docs · 8 unmodified lines
40 41
41 klass = nil if node.name == 'h2'42 klass = nil if node.name == 'h2'
42 name = node.content.strip43 name = node.content.strip
44 name.remove! %r{\s*\[src\]}
43 45
44 # Skip constructors46 # Skip constructors
45 if name.start_with? 'new '47 if name.start_with? 'new '
lib/docs/filters/npm/clean_html.rb+1−1
@@ -9,7 +9,7 @@ module Docs · 8 unmodified lines
9 css('meta', '.colophon').remove9 css('meta', '.colophon').remove
10 end10 end
11 11
12 css('a.deep-link[id]').each do |node|12 css('a.deep-link[id]', 'a.anchor[id]').each do |node|
13 node.parent['id'] = node['id']13 node.parent['id'] = node['id']
14 node.remove14 node.remove
15 end15 end
lib/docs/filters/npm/entries.rb+2−2
@@ -28,11 +28,11 @@ module Docs · 27 unmodified lines
28 when 'files/package.json'28 when 'files/package.json'
29 css('#page > h2').each_with_object [] do |node, entries|29 css('#page > h2').each_with_object [] do |node, entries|
30 next if node.content =~ /\A[A-Z]/30 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']]
32 end32 end
33 when 'misc/config'33 when 'misc/config'
34 css('h2:contains("Config Settings") ~ h3').map do |node|34 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']]
36 end36 end
37 else37 else
38 []38 []
lib/docs/filters/pandas/entries.rb+5−3
@@ -2,8 +2,10 @@ module Docs · 1 unmodified line
2 class Pandas2 class Pandas
3 class EntriesFilter < Docs::EntriesFilter3 class EntriesFilter < Docs::EntriesFilter
4 def get_name4 def get_name
5 if subpath.start_with?('generated')5 if subpath.start_with?('generated') || (subpath.include?('reference') && !subpath.include?('reference/index'))
6 name = at_css('dt').content.strip6 name_node = at_css('dt')
7 name_node = at_css('h1') if name_node.nil?
8 name = name_node.content.strip
7 name.sub! %r{\(.*}, '()'9 name.sub! %r{\(.*}, '()'
8 name.remove! %r{\s=.*}10 name.remove! %r{\s=.*}
9 name.remove! %r{\A(class(method)?) (pandas\.)?}11 name.remove! %r{\A(class(method)?) (pandas\.)?}
@@ -16,7 +18,7 @@ module Docs · 6 unmodified lines
16 end18 end
17 19
18 def get_type20 def get_type
19 if subpath.start_with?('generated')21 if subpath.start_with?('generated') || (subpath.include?('reference') && !subpath.include?('reference/index'))
20 css('.toctree-l2.current > a').last.content.remove(/\s\(.+?\)/)22 css('.toctree-l2.current > a').last.content.remove(/\s\(.+?\)/)
21 else23 else
22 'Manual'24 'Manual'
lib/docs/filters/postgresql/clean_html.rb+34−5
@@ -13,18 +13,22 @@ module Docs · 12 unmodified lines
13 def other13 def other
14 @doc = at_css('#docContent')14 @doc = at_css('#docContent')
15 15
16 css('.NAVHEADER', 'hr', '.NAVFOOTER a[accesskey="H"]').remove16 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
17 21
18 css('a[name]').each do |node|22 css('a[name]').each do |node|
19 node.parent['id'] = node['name']23 node.parent['id'] = node['name']
20 node.before(node.children).remove24 node.before(node.children).remove
21 end25 end
22 26
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|
24 node.before(node.children).remove28 node.before(node.children).remove
25 end29 end
26 30
27 css('div.CAUTION table.CAUTION').each do |node|31 css('div.caution table.caution').each do |node|
28 parent = node.parent32 parent = node.parent
29 title = node.at_css('.c2, .c3, .c4, .c5').content33 title = node.at_css('.c2, .c3, .c4, .c5').content
30 node.replace(node.css('p'))34 node.replace(node.css('p'))
@@ -43,11 +47,27 @@ module Docs · 12 unmodified lines
43 node.remove_attribute 'valign'47 node.remove_attribute 'valign'
44 end48 end
45 49
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
46 css('tt').each do |node|58 css('tt').each do |node|
47 node.name = 'code'59 node.name = 'code'
48 end60 end
49 61
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|
51 node.name = 'pre'71 node.name = 'pre'
52 node.content = node.content72 node.content = node.content
53 end73 end
@@ -56,9 +76,18 @@ module Docs · 2 unmodified lines
56 node.before(node.children).remove76 node.before(node.children).remove
57 end77 end
58 78
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|
60 node['data-language'] = 'sql'84 node['data-language'] = 'sql'
61 end85 end
86
87 css('h1', 'ul', 'li', 'pre').each do |node|
88 node.remove_attribute 'class'
89 node.remove_attribute 'style'
90 end
62 end91 end
63 end92 end
64 end93 end
lib/docs/filters/postgresql/entries.rb+23−21
@@ -90,33 +90,33 @@ module Docs · 89 unmodified lines
90 return config_additional_entries if type && type.include?('Configuration')90 return config_additional_entries if type && type.include?('Configuration')
91 return data_types_additional_entries if type == 'Data Types'91 return data_types_additional_entries if type == 'Data Types'
92 return command_additional_entries if type == 'Commands'92 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'
94 94
95 entries = get_heading_entries('h2[id]')95 entries = get_heading_entries('h2[id], .sect2[id] > h2:first-child')
96 96
97 case slug97 case slug
98 when 'queries-union'98 when 'queries-union'
99 entries.concat get_custom_entries('p > .LITERAL:first-child')99 entries.concat get_custom_entries('p > .literal:first-child')
100 when 'queries-table-expressions'100 when 'queries-table-expressions'
101 entries.concat get_heading_entries('h3[id]')101 entries.concat get_heading_entries('h3[id], .sect3[id] > h3:first-child')
102 entries.concat get_custom_entries('dt > .LITERAL:first-child')102 entries.concat get_custom_entries('dt > .literal:first-child')
103 when 'functions-logical'103 when 'functions-logical'
104 entries.concat get_custom_entries('> table td:first-child > code')104 entries.concat get_custom_entries('> table td:first-child > code')
105 when 'functions-formatting'105 when 'functions-formatting'
106 entries.concat get_custom_entries('#FUNCTIONS-FORMATTING-TABLE td:first-child > code')106 entries.concat get_custom_entries('#FUNCTIONS-FORMATTING-TABLE td:first-child > code')
107 when 'functions-admin'107 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')
109 when 'functions-string'109 when 'functions-string'
110 entries.concat get_custom_entries('> div[id^="FUNC"] td:first-child > code')110 entries.concat get_custom_entries('> div[id^="FUNC"] td:first-child > code')
111 entries.concat get_custom_entries('> div[id^="FORMAT"] td:first-child > code')111 entries.concat get_custom_entries('> div[id^="FORMAT"] td:first-child > code')
112 else112 else
113 if type && type.start_with?('Functions')113 if type && type.start_with?('Functions')
114 entries.concat get_custom_entries('> .TABLE td:first-child > code.LITERAL:first-child')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')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')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')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')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')119 entries.concat get_custom_entries('> .table td:first-child > p > code:not(.literal):first-child + code.literal')
120 if slug == 'functions-comparison' && !at_css('#FUNCTIONS-COMPARISON-PRED-TABLE') # before 9.6120 if slug == 'functions-comparison' && !at_css('#FUNCTIONS-COMPARISON-PRED-TABLE') # before 9.6
121 entries.concat %w(IS NULL BETWEEN DISTINCT\ FROM).map { |name| ["#{self.name}: #{name}"] }121 entries.concat %w(IS NULL BETWEEN DISTINCT\ FROM).map { |name| ["#{self.name}: #{name}"] }
122 end122 end
@@ -127,8 +127,8 @@ module Docs · 4 unmodified lines
127 end127 end
128 128
129 def config_additional_entries129 def config_additional_entries
130 css('.VARIABLELIST dt[id]').map do |node|130 css('.variablelist dt[id]').map do |node|
131 name = node.at_css('.VARNAME').content131 name = node.at_css('.varname').content
132 ["Config: #{name}", node['id']]132 ["Config: #{name}", node['id']]
133 end133 end
134 end134 end
@@ -136,27 +136,27 @@ module Docs · 1 unmodified line
136 def data_types_additional_entries136 def data_types_additional_entries
137 selector = case slug137 selector = case slug
138 when 'rangetypes'138 when 'rangetypes'
139 'li > p > .TYPE:first-child'139 'li > p > .type:first-child'
140 when 'datatype-textsearch'140 when 'datatype-textsearch'
141 '.SECT2 > .TYPE'141 '.title > .type, .sect2 > .type'
142 else142 else
143 '.CALSTABLE td:first-child > .TYPE'143 '.table-contents td:first-child > .type, .calstable td:first-child > .type'
144 end144 end
145 get_custom_entries(selector)145 get_custom_entries(selector)
146 end146 end
147 147
148 def command_additional_entries148 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|
150 next unless heading = node.at_css('h3')150 next unless heading = node.at_css('h3')
151 next unless heading.content.strip =~ /[A-Z_\-]+ Clause/151 next unless heading.content.strip =~ /[A-Z_\-]+ Clause/
152 name = heading.at_css('.LITERAL').content152 name = heading.at_css('.literal').content
153 name.prepend "#{self.name} ... "153 name.prepend "#{self.name} ... "
154 entries << [name, node['id']]154 entries << [name, node['id']]
155 end155 end
156 end156 end
157 157
158 def include_default_entry?158 def include_default_entry?
159 !initial_page? && !at_css('.TOC') && type159 !initial_page? && (!at_css('.toc') || at_css('.sect2, .variablelist, .refsect1')) && type
160 end160 end
161 161
162 SKIP_ENTRIES_SLUGS = [162 SKIP_ENTRIES_SLUGS = [
@@ -199,7 +199,9 @@ module Docs · 36 unmodified lines
199 css(selector).each_with_object([]) do |node, entries|199 css(selector).each_with_object([]) do |node, entries|
200 name = node.content200 name = node.content
201 clean_heading_name(name)201 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)
203 end205 end
204 end206 end
205 207
lib/docs/filters/postgresql/extract_metadata.rb+10−8
@@ -8,19 +8,21 @@ module Docs · 7 unmodified lines
8 end8 end
9 9
10 def extract_up_path10 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"]')
12 result[:pg_up_path] = node['href']12 result[:pg_up_path] = node['href']
13 end13 end
14 end14 end
15 15
16 def extract_chapter16 def extract_chapter
17 return unless text = at_css('.NAVHEADER td[align="center"]').content17 css('.navheader td[align="center"], .navheader th[align="center"]').each do |node|
18 if match = text.match(/\AChapter (\d+)\. (.+)\z/)18 text = node.content.strip
19 result[:pg_chapter] = match[1].to_i19 if match = text.match(/\AChapter (\d+)\. (.+)\z/)
20 result[:pg_chapter_name] = match[2].strip20 result[:pg_chapter] = match[1].to_i
21 elsif match = text.match(/\AAppendix ([A-Z])\. (.+)\z/)21 result[:pg_chapter_name] = match[2].strip
22 result[:pg_appendix] = match[1]22 elsif match = text.match(/\AAppendix ([A-Z])\. (.+)\z/)
23 result[:pg_appendix_name] = match[2].strip23 result[:pg_appendix] = match[1]
24 result[:pg_appendix_name] = match[2].strip
25 end
24 end26 end
25 end27 end
26 end28 end
lib/docs/filters/postgresql/normalize_class_names.rb+13−0
@@ -0,0 +1,13 @@
1module 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
13end
lib/docs/filters/puppeteer/clean_html.rb+24−0
@@ -0,0 +1,24 @@
1module Docs
2 class Puppeteer
3 class CleanHtmlFilter < Filter
4 def call
5 at_css('h1').content = 'Puppeteer Documentation'
6
7 # None of the elements to remove have classes, so the order of the remove calls is trivial
8
9 # Remove links to previous versions of the reference
10 at_css('h1 + ul').remove
11
12 # Remove table of contents
13 at_css('h1 + h5').remove
14 at_css('h1 + ul').remove
15
16 # Make headers bigger by transforming them into a bigger variant
17 css('h3').each { |node| node.name = 'h2' }
18 css('h4').each { |node| node.name = 'h3' }
19
20 doc
21 end
22 end
23 end
24end
lib/docs/filters/puppeteer/entries.rb+41−0
@@ -0,0 +1,41 @@
1module Docs
2 class Puppeteer
3 class EntriesFilter < Docs::EntriesFilter
4 # The entire reference is one big page, so get_name and get_type are not necessary
5
6 def additional_entries
7 entries = []
8
9 css('h3').each do |node|
10 name = node.content.split(': ').last
11
12 # Find all sub-items (all h4's between the current h3 and the next)
13 current = node.next
14 while !current.nil? && current.name != 'h3'
15 if current.name == 'h4'
16 current_name = current.content
17
18 # Prepend events with the class name
19 if current_name.start_with?('event: ')
20 current_name = "#{name} event: #{current_name[/'(.*)'/, 1]}"
21 end
22
23 # Remove arguments from functions
24 if current_name.include?('(')
25 current_name = current_name.split('(')[0] + '()'
26 end
27
28 entries << [current_name, current['id'], name]
29 end
30
31 current = current.next
32 end
33
34 entries << [name, node['id'], name]
35 end
36
37 entries
38 end
39 end
40 end
41end
lib/docs/filters/pygame/clean_html.rb+102−0
@@ -0,0 +1,102 @@
1module Docs
2 class Pygame
3 class CleanHtmlFilter < Filter
4 def call
5 @doc = at_css '.body'
6
7 if root_page?
8 # remove unneeded stuff
9 at_css('.modindex-jumpbox').remove
10 css('[role="navigation"],.pcap, .cap, .footer').remove
11 # table -> list
12 list = at_css('table')
13 list.replace(list.children)
14 list.name = 'ul'
15 css('tr').each do |row|
16 row.name = 'li'
17 row.remove_attribute('class')
18 end
19 at_css('h1').content = 'Pygame'
20 return doc
21 end
22
23 # remove unwanted stuff
24 # .headerlink => ¶ after links
25 # .toc => table of content
26 # .tooltip-content => tooltips after links to functions
27 css('table.toc.docutils, .headerlink, .tooltip-content').remove
28
29 # Remove wrapper .section
30 section = at_css('.section')
31 definition = at_css('.definition')
32 definition['id'] = section['id']
33 section.replace(section.children)
34
35 # Format code for it be highlighted
36 css('.highlight-default.notranslate').each do |node|
37 pre = node.at_css('pre')
38 node.replace(pre)
39 # gets rid of the already existing syntax highlighting
40 pre.content = pre.content
41 pre['class'] = 'language-python'
42 pre['data-language'] = "python"
43 end
44
45 # change descriptions of functions/attributes to blockquote
46 css('.line-block').each do |node|
47 node.name = 'blockquote'
48 end
49
50 # change functions
51 css('.definition').each do |d|
52
53 # the header is the function/attribute name. It might look something like
54 # this:
55 # pygame.image.load()
56 # It'll end up being something like this:
57 # pygame.image.load(filename) -> Surface
58 # pygame.image.load(fileobj, namehint="") -> Surface
59
60 header = d.at_css('dt.title')
61 if d['class'].include?('class') or d['class'].include?('module')
62 header.name = 'h1'
63 @section = header.content.strip
64 else
65 header.name = 'h3'
66 end
67 # save the original header
68 initial_header = header.content.strip
69 # save the real name for the entries
70 header['data-name'] = initial_header
71 # empty the header
72 if header.name == 'h3'
73 header.inner_html = ''
74 end
75 # to replace it with the signatures
76 next_el = header.next_element
77 signatures = next_el.css('.signature')
78 signatures.each do |sig|
79 sig.name = 'code'
80 if header.name == 'h3'
81 sig.parent = header
82 # the signature don't contain pygame.module. I think it's better
83 # to display them, as it avoids confusion with methods (have a
84 # look at the pygame.Rect page)
85 if initial_header.start_with?(@section)
86 sig.content = @section + '.' + sig.text
87 end
88 # separate the signatures on different lines.
89 header.add_child "<br>"
90 end
91 end
92 end
93
94 css('> dl', '> dl > dd', 'h1 code').each do |node|
95 node.before(node.children).remove
96 end
97
98 doc
99 end
100 end
101 end
102end
lib/docs/filters/pygame/entries.rb+29−0
@@ -0,0 +1,29 @@
1module Docs
2 class Pygame
3 class EntriesFilter < Docs::EntriesFilter
4 def get_name
5 at_css('h1').content.remove('pygame.')
6 end
7
8 def get_type
9 at_css('h1').content
10 end
11
12 def additional_entries
13 return [] if root_page?
14
15 css('h1, h2, h3').each_with_object [] do |node, entries|
16 name = node['id'] || node['data-name']
17
18 if node.parent['class'].include?('function') or node.parent['class'].include?('method')
19 name << '()'
20 end
21
22 name.remove!('pygame.')
23
24 entries << [name, node['id']] unless name == self.name
25 end
26 end
27 end
28 end
29end
lib/docs/filters/python/clean_html.rb+5−3
@@ -15,15 +15,17 @@ module Docs · 14 unmodified lines
15 15
16 def other16 def other
17 css('h1').each do |node|17 css('h1').each do |node|
18 node.content = node.content.sub!(/\A[\d\.]+/) do |str|18 node.content = node.content.sub(/\A[\d\.]+/) do |str|
19 rgx = /\A#{str}/19 rgx = /\A#{str}/
20 @levelRegexp = @levelRegexp ? Regexp.union(@levelRegexp, rgx) : rgx20 @levelRegexp = @levelRegexp ? Regexp.union(@levelRegexp, rgx) : rgx
21 ''21 ''
22 end22 end
23 end23 end
24 24
25 css('h2', 'h3', 'h4').each do |node|25 unless @levelRegexp.nil?
26 node.inner_html = node.inner_html.remove @levelRegexp26 css('h2', 'h3', 'h4').each do |node|
27 node.inner_html = node.inner_html.remove @levelRegexp
28 end
27 end29 end
28 end30 end
29 end31 end
lib/docs/filters/python/entries_v2.rb+1−7
@@ -57,16 +57,10 @@ module Docs · 56 unmodified lines
57 clean_id_attributes57 clean_id_attributes
58 entries = []58 entries = []
59 59
60 css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]').each do |node|60 css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]', '.data > dt[id]').each do |node|
61 entries << [node['id'], node['id']]61 entries << [node['id'], node['id']]
62 end62 end
63 63
64 css('.data > dt[id]').each do |node|
65 if node['id'].split('.').last.upcase! # skip constants
66 entries << [node['id'], node['id']]
67 end
68 end
69
70 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|64 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|
71 entries << [node['id'] + '()', node['id']]65 entries << [node['id'] + '()', node['id']]
72 end66 end
lib/docs/filters/python/entries_v3.rb+15−17
@@ -2,16 +2,18 @@ module Docs · 1 unmodified line
2 class Python2 class Python
3 class EntriesV3Filter < Docs::EntriesFilter3 class EntriesV3Filter < Docs::EntriesFilter
4 REPLACE_TYPES = {4 REPLACE_TYPES = {
5 'Cryptographic' => 'Cryptography',5 'contextvars — Context Variables' => 'Context Variables',
6 'Custom Interpreters' => 'Interpreters',6 'Cryptographic' => 'Cryptography',
7 'Data Compression & Archiving' => 'Data Compression',7 'Custom Interpreters' => 'Interpreters',
8 'Generic Operating System' => 'Operating System',8 'Data Compression & Archiving' => 'Data Compression',
9 'Graphical User Interfaces with Tk' => 'Tk',9 'email — An email & MIME handling package' => 'Email',
10 'Internet Data Handling' => 'Internet Data',10 'Generic Operating System' => 'Operating System',
11 'Internet Protocols & Support' => 'Internet',11 'Graphical User Interfaces with Tk' => 'Tk',
12 'Interprocess Communication & Networking' => 'Networking',12 'Internet Data Handling' => 'Internet Data',
13 'Program Frameworks' => 'Frameworks',13 'Internet Protocols & Support' => 'Internet',
14 'Structured Markup Processing Tools' => 'Structured Markup' }14 'Interprocess Communication & Networking' => 'Networking',
15 'Program Frameworks' => 'Frameworks',
16 'Structured Markup Processing Tools' => 'Structured Markup' }
15 17
16 def get_name18 def get_name
17 name = at_css('h1').content19 name = at_css('h1').content
@@ -25,6 +27,7 @@ module Docs · 7 unmodified lines
25 27
26 def get_type28 def get_type
27 return 'Logging' if slug.start_with? 'library/logging'29 return 'Logging' if slug.start_with? 'library/logging'
30 return 'Asynchronous I/O' if slug.start_with? 'library/asyncio'
28 31
29 type = at_css('.related a[accesskey="U"]').content32 type = at_css('.related a[accesskey="U"]').content
30 33
@@ -45,6 +48,7 @@ module Docs · 14 unmodified lines
45 end48 end
46 49
47 def include_default_entry?50 def include_default_entry?
51 return true if slug == 'library/asyncio'
48 !at_css('.body > .section:only-child > .toctree-wrapper:last-child') && !type.in?(%w(Superseded))52 !at_css('.body > .section:only-child > .toctree-wrapper:last-child') && !type.in?(%w(Superseded))
49 end53 end
50 54
@@ -53,16 +57,10 @@ module Docs · 2 unmodified lines
53 clean_id_attributes57 clean_id_attributes
54 entries = []58 entries = []
55 59
56 css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]').each do |node|60 css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]', '.data > dt[id]').each do |node|
57 entries << [node['id'], node['id']]61 entries << [node['id'], node['id']]
58 end62 end
59 63
60 css('.data > dt[id]').each do |node|
61 if node['id'].split('.').last.upcase! # skip constants
62 entries << [node['id'], node['id']]
63 end
64 end
65
66 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|64 css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|
67 entries << [node['id'] + '()', node['id']]65 entries << [node['id'] + '()', node['id']]
68 end66 end
lib/docs/filters/qt/clean_html.rb+32−0
@@ -0,0 +1,32 @@
1module Docs
2 class Qt
3 class CleanHtmlFilter < Filter
4 def call
5 # Remove unneeded elements
6 css('.copy-notice, .navigationbar, .headerNavi, .footerNavi, .sidebar, .toc, #ec_toggle', '.landingicons img', 'br').remove
7
8 # QML property/method header
9 css('.qmlproto').each do |node|
10 id = node.at_css('tr')['id']
11 id = node.at_css('a')['name'] if id.blank?
12 node.inner_html = node.at_css('td').inner_html
13 node.name = 'h3'
14 node['id'] = id
15 end
16
17 css('.main-rounded', '.content', '.line', '.context', '.descr', '.types', '.func', '.table', 'div:not([class])', '.landing', '.col-1', '.heading', '.qmlitem', '.qmldoc', 'div.pre').each do |node|
18 node.before(node.children).remove
19 end
20
21 css('pre').each do |node|
22 node.content = node.content
23 node['data-language'] = 'cpp' if node['class'].include?('cpp')
24 node['data-language'] = 'qml' if node['class'].include?('qml')
25 node.remove_attribute('class')
26 end
27
28 doc
29 end
30 end
31 end
32end
lib/docs/filters/qt/entries.rb+142−0
@@ -0,0 +1,142 @@
1# frozen_string_literal: true
2
3module Docs
4 class Qt
5 class EntriesFilter < Docs::EntriesFilter
6 def get_name
7 header = at_css('h1.title + .small-subtitle a') || at_css('h1.title') || at_css('.context h2')
8 name = header.content
9 name.sub! %r{ Class$}, ' (class)'
10 name.sub! %r{ QML Type$}, ' (QML type)'
11 name.sub! %r{ QML Basic Type$}, ' (QML basic type)'
12
13 # Add '(class)' to the class pages where the subtitle name is used (e.g. qset-const-iterator.html)
14 if at_css('h1.title').content.strip.end_with?(' Class') && !name.include?('(class)')
15 name = "#{name} (class) "
16 end
17
18 name
19 end
20
21 def get_type
22 breadcrumb = css('#main_title_bar + ul li')
23 category = if breadcrumb.length < 3
24 then 'Qt'.dup
25 else breadcrumb.at(1).content
26 end
27
28 if category == 'Qt'
29 return 'Qt Platforms' if name.include?(' for ') || name == 'Qt Platform Abstraction'
30 return 'Qt Quick' if name == 'Qt Quick Test' || name == 'Qt Quick Test Reference Documentation'
31
32 alwaysInQt = ['Qt Configure Options', 'Qt Image Formats']
33 category = name if name.start_with?('Qt ') && !alwaysInQt.include?(name)
34 end
35
36 qtPlatformsTypes = ['Qt Platform Headers', 'Qt Android Extras', 'Qt Mac Extras', 'Qt Windows Extras', 'Qt X11 Extras']
37 return 'Qt Platforms' if qtPlatformsTypes.include?(category)
38
39 category.remove!(' Manual')
40 category
41 end
42
43 def include_default_entry?
44 name != 'All Classes' && name != 'All QML Types'
45 end
46
47 def additional_entries
48 entries = []
49 titles = []
50
51 className = at_css('h1.title').content.strip.remove(' Class')
52 displayedClassName = className
53 alternativeClassName = at_css('h1.title + .small-subtitle a')
54 displayedClassName = alternativeClassName.content if alternativeClassName
55
56 # Functions signatures
57 css('h3.fn').each do |node|
58 header = node.clone
59
60 # Skip typenames
61 next if header.content.strip.start_with?('typename ')
62
63 # Remove leading <a name="">
64 header.children.css('a[name]').remove
65
66 # Remove leading <code> tag (virtual/static/… attributes)
67 code = header.children.first
68 code.remove if code.name == 'code'
69
70 # Remove leading ‘const’
71 header.children.first.remove if header.content.strip.start_with?('const ')
72
73 # Remove return type
74 returnType = header.children.first
75 returnType.remove if returnType['class'] == 'type'
76
77 title = header.content.strip
78
79 # Remove leading '&'/'*'
80 title[0] = '' if title[0] == '&' || title[0] == '*'
81
82 # Ignore operator overloads
83 next if title.start_with?('operator')
84
85 # Remove function parameters
86 title.sub! %r{\(.*\)}, '()'
87
88 # Remove template generics
89 title.remove!(%r{^<.*> })
90
91 # Remove ‘const’ at the end
92 title.remove!(%r{ const$})
93
94 # Enum/typedef formatting
95 title.sub! %r{(enum|typedef) (.*)}, '\2 (\1)'
96
97 # Remove property type
98 title = "#{displayedClassName}::#{title}" if title.sub!(%r{ : .*$}, '')
99
100 # Replace the class name by the alternative class name if available
101 title = title.sub(className, displayedClassName) if alternativeClassName
102
103 unless titles.include?(title) # Remove duplicates (function overloading)
104 entries << [title, header['id']]
105 titles.push title
106 end
107 end
108
109 # QML properties/functions
110 qmlTypeName = at_css('h1.title').content.remove(' QML Type', '')
111 css('.qmlproto').each do |node|
112 title = node.content.strip
113 id = node.at_css('tr')['id']
114 id = node.at_css('a')['name'] if id.blank?
115
116 # Remove options
117 title.remove!(%r{^\[.*\] })
118
119 # Remove function parameters
120 title.sub! %r{\(.*\)}, '()'
121
122 # Remove property type
123 title.remove!(%r{ : .*$})
124
125 # Remove return type
126 title.remove!(%r{.* })
127
128 # Remove return type
129 title.remove!(%r{.* })
130
131 title = "#{qmlTypeName}.#{title.strip}"
132 unless titles.include?(title) # Remove duplicates (function overloading)
133 entries << [title, id]
134 titles.push(title)
135 end
136 end
137
138 entries
139 end
140 end
141 end
142end
lib/docs/filters/ramda/clean_html.rb+1−1
@@ -11,7 +11,7 @@ module Docs · 10 unmodified lines
11 css('.card').each do |card|11 css('.card').each do |card|
12 title = card.at_css('h2')12 title = card.at_css('h2')
13 added_in = card.at_css('small')13 added_in = card.at_css('small')
14 added_in.parent = title14 added_in.parent = title if added_in.present?
15 end15 end
16 16
17 css('.params').each do |node|17 css('.params').each do |node|
lib/docs/filters/rdoc/clean_html.rb+4−2
@@ -37,8 +37,10 @@ module Docs · 36 unmodified lines
37 end37 end
38 38
39 # Add class to differentiate Ruby code from C code39 # Add class to differentiate Ruby code from C code
40 css('.method-source-code > pre').each do |node|40 css('.method-source-code').each do |node|
41 node['class'] = node.at_css('.ruby-keyword') ? 'ruby' : 'c'41 node.parent.prepend_child(node)
42 pre = node.at_css('pre')
43 pre['class'] = pre.at_css('.ruby-keyword') ? 'ruby' : 'c'
42 end44 end
43 45
44 # Remove code highlighting46 # Remove code highlighting
lib/docs/filters/react/entries.rb+7−2
@@ -6,8 +6,11 @@ module Docs · 5 unmodified lines
6 end6 end
7 7
8 def get_type8 def get_type
9 link = at_css("nav a[href='#{result[:path].split('/').last}']")9 link = css("nav a[href='#{result[:path].split('/').last}']").last
10 link.ancestors('ul').last.previous_element.content10 return 'Miscellaneous' unless link
11 type = link.ancestors('ul').last.previous_element.content
12 type.remove! %r{\s*\(.*\)}
13 type
11 end14 end
12 15
13 def additional_entries16 def additional_entries
@@ -24,6 +27,8 @@ module Docs · 10 unmodified lines
24 'Reference: Component'27 'Reference: Component'
25 elsif slug == 'react-api'28 elsif slug == 'react-api'
26 'Reference: React'29 'Reference: React'
30 elsif slug == 'hooks-reference'
31 'Hooks'
27 else32 else
28 'Reference'33 'Reference'
29 end34 end
lib/docs/filters/react_native/clean_html.rb+7−2
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class ReactNative2 class ReactNative
3 class CleanHtmlFilter < Filter3 class CleanHtmlFilter < Filter
4 def call4 def call
5 @doc = at_css('.inner-content, article.withtoc')5 @doc = at_css('.post')
6 6
7 if root_page?7 if root_page?
8 at_css('h1').content = 'React Native Documentation'8 at_css('h1').content = 'React Native Documentation'
@@ -38,6 +38,11 @@ module Docs · 29 unmodified lines
38 node.content = node.content38 node.content = node.content
39 end39 end
40 40
41 css('pre > code.hljs').each do |node|
42 node.parent['data-language'] = 'jsx'
43 node.before(node.children).remove
44 end
45
41 css('blockquote > p:first-child').each do |node|46 css('blockquote > p:first-child').each do |node|
42 node.remove if node.content.strip == 'Note:'47 node.remove if node.content.strip == 'Note:'
43 end48 end
@@ -45,7 +50,7 @@ module Docs · 1 unmodified line
45 css('h3#props', 'h3#methods').each { |node| node.name = 'h2' }50 css('h3#props', 'h3#methods').each { |node| node.name = 'h2' }
46 css('h4.propTitle').each { |node| node.name = 'h3' }51 css('h4.propTitle').each { |node| node.name = 'h3' }
47 52
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|
49 node.before(node.children).remove54 node.before(node.children).remove
50 end55 end
51 56
lib/docs/filters/react_native/entries.rb+9−11
@@ -12,9 +12,9 @@ module Docs · 11 unmodified lines
12 end12 end
13 13
14 def get_type14 def get_type
15 link = at_css('.nav-docs-section .active, .toc .active')15 link = at_css('.navListItemActive')
16 return 'Miscellaneous' unless link16 return 'Miscellaneous' unless link
17 section = link.ancestors('.nav-docs-section, section').first17 section = link.ancestors('.navGroup').first
18 type = section.at_css('h3').content.strip18 type = section.at_css('h3').content.strip
19 type = REPLACE_TYPES[type] || type19 type = REPLACE_TYPES[type] || type
20 type += ": #{name}" if type == 'Components'20 type += ": #{name}" if type == 'Components'
@@ -22,15 +22,13 @@ module Docs · 1 unmodified line
22 end22 end
23 23
24 def additional_entries24 def additional_entries
25 css('.props > .prop > .propTitle', '.props > .prop > .methodTitle').each_with_object [] do |node, entries|25 css('.mainContainer h3').each_with_object [] do |node, entries|
26 name = node.children.find(&:text?).try(:content)26 subname = node.text
27 next if name.blank?27 next if subname.blank? || node.css('code').empty?
28 sep = node.content.include?('static') ? '.' : '#'28 sep = subname.include?('()') ? '.' : '#'
29 name.prepend(self.name + sep)29 subname.prepend(name + sep)
30 name << '()' if node['class'].include?('methodTitle')30 id = node.at_css('.anchor')['id']
31 name.remove! %r{\??\:\s*\z}31 entries << [subname, id]
32 id = node.at_css('.anchor')['name']
33 entries << [name, id]
34 end32 end
35 end33 end
36 end34 end
lib/docs/filters/redis/entries.rb+2−1
@@ -12,7 +12,7 @@ module Docs · 11 unmodified lines
12 when 'HDEL' then 'Hashes'12 when 'HDEL' then 'Hashes'
13 when 'BLPOP' then 'Lists'13 when 'BLPOP' then 'Lists'
14 when 'SADD' then 'Sets'14 when 'SADD' then 'Sets'
15 when 'ZADD' then 'Sorted Sets'15 when 'BZPOPMAX' then 'Sorted Sets'
16 when 'PSUBSCRIBE' then 'Pub/Sub'16 when 'PSUBSCRIBE' then 'Pub/Sub'
17 when 'DISCARD' then 'Transactions'17 when 'DISCARD' then 'Transactions'
18 when 'EVAL' then 'Scripting'18 when 'EVAL' then 'Scripting'
@@ -21,6 +21,7 @@ module Docs · 2 unmodified lines
21 when 'PFADD' then 'HyperLogLog'21 when 'PFADD' then 'HyperLogLog'
22 when 'CLUSTER ADDSLOTS' then 'Cluster'22 when 'CLUSTER ADDSLOTS' then 'Cluster'
23 when 'GEOADD' then 'Geo'23 when 'GEOADD' then 'Geo'
24 when 'XACK' then 'Stream'
24 else 'Miscellaneous'25 else 'Miscellaneous'
25 end26 end
26 end27 end
lib/docs/filters/rust/clean_html.rb+39−6
@@ -1,11 +1,11 @@
1# frozen_string_literal: true
2
1module Docs3module Docs
2 class Rust4 class Rust
3 class CleanHtmlFilter < Filter5 class CleanHtmlFilter < Filter
4 def call6 def call
5 puts subpath if at_css('#versioninfo')
6
7 if slug.start_with?('book') || slug.start_with?('reference')7 if slug.start_with?('book') || slug.start_with?('reference')
8 @doc = at_css('#content')8 @doc = at_css('#content main')
9 elsif slug == 'error-index'9 elsif slug == 'error-index'
10 css('.error-undescribed').remove10 css('.error-undescribed').remove
11 11
@@ -29,12 +29,16 @@ module Docs · 17 unmodified lines
29 29
30 css('.rusttest', '.test-arrow', 'hr').remove30 css('.rusttest', '.test-arrow', 'hr').remove
31 31
32 css('.docblock.attributes').each do |node|
33 node.remove if node.content.include?('#[must_use]')
34 end
35
32 css('a.header').each do |node|36 css('a.header').each do |node|
33 node.first_element_child['id'] = node['name']37 node.first_element_child['id'] = node['name'] || node['id']
34 node.before(node.children).remove38 node.before(node.children).remove
35 end39 end
36 40
37 css('.docblock > h1').each { |node| node.name = 'h4' }41 css('.docblock > h1:not(.section-header)').each { |node| node.name = 'h4' }
38 css('h2.section-header').each { |node| node.name = 'h3' }42 css('h2.section-header').each { |node| node.name = 'h3' }
39 css('h1.section-header').each { |node| node.name = 'h2' }43 css('h1.section-header').each { |node| node.name = 'h2' }
40 44
@@ -44,7 +48,7 @@ module Docs · 3 unmodified lines
44 end48 end
45 end49 end
46 50
47 css('> .impl-items', '> .docblock', 'pre > pre').each do |node|51 css('> .impl-items', '> .docblock', 'pre > pre', '.tooltiptext', '.tooltip').each do |node|
48 node.before(node.children).remove52 node.before(node.children).remove
49 end53 end
50 54
@@ -62,6 +66,35 @@ module Docs · 11 unmodified lines
62 node['data-language'] = 'rust' if node['class'] && node['class'].include?('rust')66 node['data-language'] = 'rust' if node['class'] && node['class'].include?('rust')
63 end67 end
64 68
69 doc.first_element_child.name = 'h1' if doc.first_element_child.name = 'h2'
70 at_css('h1').content = 'Rust Documentation' if root_page?
71
72 css('.table-display').each do |node|
73 node.css('td').each do |td|
74 node.before(td.children)
75 end
76 node.remove
77 end
78
79 css('.important-traits').to_a.each_with_index do |node, index|
80 content = node.at_css('.content.hidden .content')
81 node.at_css('.content.hidden').replace(content) if content
82 node.parent.after(node) if node.parent.name.in?(%(h2 h3 h4))
83 end
84
85 css('code.content').each do |node|
86 node.name = 'pre'
87 node.css('.fmt-newline').each do |line|
88 line.inner_html = line.inner_html + "\n"
89 end
90 node.inner_html = node.inner_html.gsub('<br>', "\n")
91 node.content = node.content
92 end
93
94 css('.since + .srclink').each do |node|
95 node.previous_element.before(node)
96 end
97
65 doc98 doc
66 end99 end
67 end100 end
lib/docs/filters/rust/entries.rb+7−5
@@ -42,11 +42,13 @@ module Docs · 41 unmodified lines
42 entries << [node.content, node['id']] unless node.content.include?('Note:')42 entries << [node.content, node['id']] unless node.content.include?('Note:')
43 end43 end
44 else44 else
45 css('#methods + * + div > .method', '#required-methods + div > .method', '#provided-methods + div > .method').map do |node|45 css('.method')
46 name = node.at_css('.fnname').content46 .each_with_object({}) { |node, entries|
47 name.prepend "#{self.name}::"47 name = node.at_css('.fnname').try(:content)
48 [name, node['id']]48 next unless name
49 end49 name.prepend "#{self.name}::"
50 entries[name] ||= [name, node['id']]
51 }.values
50 end52 end
51 end53 end
52 end54 end
lib/docs/filters/scikit_learn/entries.rb+1−0
@@ -27,6 +27,7 @@ module Docs · 26 unmodified lines
27 type = at_css('dt > .descclassname').content.strip27 type = at_css('dt > .descclassname').content.strip
28 type.remove! 'sklearn.'28 type.remove! 'sklearn.'
29 type.remove! %r{\.\z}29 type.remove! %r{\.\z}
30 type = 'sklearn' if type.blank?
30 type31 type
31 elsif subpath.start_with?('tutorial')32 elsif subpath.start_with?('tutorial')
32 'Tutorials'33 'Tutorials'
lib/docs/filters/sqlite/entries.rb+2−1
@@ -34,7 +34,8 @@ module Docs · 33 unmodified lines
34 'rtree' => 'R*Tree Module',34 'rtree' => 'R*Tree Module',
35 'rbu' => 'RBU Extension',35 'rbu' => 'RBU Extension',
36 'limits' => 'Limits',36 'limits' => 'Limits',
37 'howtocorrupt' => 'How To Corrupt'37 'howtocorrupt' => 'How To Corrupt',
38 'geopoly' => 'Geopoly'
38 }39 }
39 40
40 def get_type41 def get_type
lib/docs/filters/svg/entries.rb+2−2
@@ -3,8 +3,8 @@ module Docs · 2 unmodified lines
3 class EntriesFilter < Docs::EntriesFilter3 class EntriesFilter < Docs::EntriesFilter
4 def get_name4 def get_name
5 name = super5 name = super
6 name.gsub!('Element.', '').try(:downcase!)6 name.gsub!('Element.', '')
7 name.gsub!('Attribute.', '').try(:downcase!)7 name.gsub!('Attribute.', '')
8 name.gsub!('Tutorial.', '')8 name.gsub!('Tutorial.', '')
9 name.gsub!('_', '')9 name.gsub!('_', '')
10 10
lib/docs/filters/terraform/clean_html.rb+33−0
@@ -0,0 +1,33 @@
1module 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
33end
lib/docs/filters/terraform/entries.rb+76−0
@@ -0,0 +1,76 @@
1module 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
76end
lib/docs/filters/typescript/entries.rb+1−0
@@ -9,6 +9,7 @@ module Docs · 8 unmodified lines
9 9
10 def get_type10 def get_type
11 return 'Declaration Files' if subpath.include?('declaration-files')11 return 'Declaration Files' if subpath.include?('declaration-files')
12 return 'Project Configuration' if slug == 'handbook/configuring-watch'
12 type = at_css('#main-nav a.active').ancestors('.panel').first.at_css('> a').content13 type = at_css('#main-nav a.active').ancestors('.panel').first.at_css('> a').content
13 type = name if type == 'Handbook'14 type = name if type == 'Handbook'
14 type15 type
lib/docs/filters/vagrant/entries.rb+1−0
@@ -29,6 +29,7 @@ module Docs · 28 unmodified lines
29 if node.name == 'h2'29 if node.name == 'h2'
30 h2 = node['id']30 h2 = node['id']
31 elsif h2 == 'available-settings' && (code = node.at_css('code')) && (name = code.content) && name.start_with?('config.')31 elsif h2 == 'available-settings' && (code = node.at_css('code')) && (name = code.content) && name.start_with?('config.')
32 name.sub! %r{\s+=.*}, '='
32 id = code.parent['id'] = name.parameterize33 id = code.parent['id'] = name.parameterize
33 entries << [name, id, 'Config']34 entries << [name, id, 'Config']
34 end35 end
lib/docs/filters/vue/clean_html.rb+0−4
@@ -9,10 +9,6 @@ module Docs · 8 unmodified lines
9 9
10 css('.demo', '.guide-links', '.footer', '#ad').remove10 css('.demo', '.guide-links', '.footer', '#ad').remove
11 11
12 css('.__cf_email__').each do |node|
13 node.replace(decode_cloudflare_email(node['data-cfemail']))
14 end
15
16 # Remove code highlighting12 # Remove code highlighting
17 css('figure').each do |node|13 css('figure').each do |node|
18 node.name = 'pre'14 node.name = 'pre'
lib/docs/filters/webpack/entries.rb+2−2
@@ -26,9 +26,9 @@ module Docs · 25 unmodified lines
26 next if node.previous.try(:content).present?26 next if node.previous.try(:content).present?
27 entries << [node.content, node.parent['id']]27 entries << [node.content, node.parent['id']]
28 end28 end
29 elsif slug.start_with?('api') && slug != 'api/plugins/parser'29 elsif slug.start_with?('api') && slug != 'api/parser'
30 css('.header[id] code').each_with_object [] do |node, entries|30 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?
32 name = node.content.sub(/\(.*\)/, '()')32 name = node.content.sub(/\(.*\)/, '()')
33 name.prepend "#{self.name.split(':').first}: "33 name.prepend "#{self.name.split(':').first}: "
34 entries << [name, node.parent['id']]34 entries << [name, node.parent['id']]
lib/docs/filters/yarn/clean_html.rb+0−4
@@ -43,10 +43,6 @@ module Docs · 42 unmodified lines
43 end43 end
44 end44 end
45 45
46 css('.__cf_email__').each do |node|
47 node.replace(decode_cloudflare_email(node['data-cfemail']))
48 end
49
50 css('div.highlighter-rouge').each do |node|46 css('div.highlighter-rouge').each do |node|
51 node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']47 node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']
52 node.content = node.content.strip48 node.content = node.content.strip
lib/docs/scrapers/angular.rb+73−21
@@ -11,33 +11,17 @@ module Docs · 10 unmodified lines
11 options[:max_image_size] = 256_00011 options[:max_image_size] = 256_000
12 12
13 options[:attribution] = <<-HTML13 options[:attribution] = <<-HTML
14 &copy; 2010&ndash;2017 Google, Inc.<br>14 &copy; 2010&ndash;2018 Google, Inc.<br>
15 Licensed under the Creative Commons Attribution License 4.0.15 Licensed under the Creative Commons Attribution License 4.0.
16 HTML16 HTML
17 17
18 version do18 module Common
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
35 private19 private
36 20
37 def initial_urls21 def initial_urls
38 initial_urls = []22 initial_urls = []
39 23
40 Request.run 'https://angular.io/generated/navigation.json' do |response|24 Request.run "#{self.class.base_url}generated/navigation.json" do |response|
41 data = JSON.parse(response.body)25 data = JSON.parse(response.body)
42 dig = ->(entry) do26 dig = ->(entry) do
43 initial_urls << url_for("generated/docs/#{entry['url']}.json") if entry['url'] && entry['url'] != 'api'27 initial_urls << url_for("generated/docs/#{entry['url']}.json") if entry['url'] && entry['url'] != 'api'
@@ -46,7 +30,7 @@ module Docs · 2 unmodified lines
46 data['SideNav'].each(&dig)30 data['SideNav'].each(&dig)
47 end31 end
48 32
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|
50 data = JSON.parse(response.body)34 data = JSON.parse(response.body)
51 dig = ->(entry) do35 dig = ->(entry) do
52 initial_urls << url_for("generated/docs/#{entry['path']}.json") if entry['path']36 initial_urls << url_for("generated/docs/#{entry['path']}.json") if entry['path']
@@ -61,7 +45,11 @@ module Docs · 8 unmodified lines
61 45
62 def handle_response(response)46 def handle_response(response)
63 if response.mime_type.include?('json')47 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
65 response.headers['Content-Type'] = 'text/html'53 response.headers['Content-Type'] = 'text/html'
66 response.url.path = response.url.path.sub('/generated/docs/', '/').remove('.json')54 response.url.path = response.url.path.sub('/generated/docs/', '/').remove('.json')
67 response.effective_url.path = response.effective_url.path.sub('/generated/docs/', '/').remove('.json')55 response.effective_url.path = response.effective_url.path.sub('/generated/docs/', '/').remove('.json')
@@ -70,6 +58,66 @@ module Docs · 2 unmodified lines
70 end58 end
71 end59 end
72 60
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
73 version '2' do121 version '2' do
74 self.release = '2.4.10'122 self.release = '2.4.10'
75 self.base_url = 'https://v2.angular.io/docs/ts/latest/'123 self.base_url = 'https://v2.angular.io/docs/ts/latest/'
@@ -107,6 +155,10 @@ module Docs · 31 unmodified lines
107 end155 end
108 end156 end
109 157
158 def get_latest_version(opts)
159 get_npm_version('@angular/core', opts)
160 end
161
110 private162 private
111 163
112 def parse(response)164 def parse(response)
lib/docs/scrapers/angularjs.rb+16−3
@@ -31,10 +31,14 @@ module Docs · 30 unmodified lines
31 end31 end
32 32
33 options[:only_patterns] = [%r{\Aapi}, %r{\Aguide}]33 options[:only_patterns] = [%r{\Aapi}, %r{\Aguide}]
34 options[:skip] = %w(api/ng.html)34 options[:skip] = %w(api/ng.html guide/tutorial/step_14.html guide/api.html guide/tutorial/.html)
35 options[:skip_patterns] = [
36 /error\/\$compile/,
37 /misc/,
38 ]
35 39
36 options[:attribution] = <<-HTML40 options[:attribution] = <<-HTML
37 &copy; 2010&ndash;2017 Google, Inc.<br>41 &copy; 2010&ndash;2018 Google, Inc.<br>
38 Licensed under the Creative Commons Attribution License 4.0.42 Licensed under the Creative Commons Attribution License 4.0.
39 HTML43 HTML
40 44
@@ -45,8 +49,13 @@ module Docs · 4 unmodified lines
45 capybara.execute_script("return document.querySelector('.side-navigation').innerHTML")49 capybara.execute_script("return document.querySelector('.side-navigation').innerHTML")
46 end50 end
47 51
52 version '1.7' do
53 self.release = '1.7.8'
54 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
55 end
56
48 version '1.6' do57 version '1.6' do
49 self.release = '1.6.5'58 self.release = '1.6.9'
50 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"59 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
51 end60 end
52 61
@@ -69,5 +78,9 @@ module Docs · 16 unmodified lines
69 self.release = '1.2.32'78 self.release = '1.2.32'
70 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"79 self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
71 end80 end
81
82 def get_latest_version(opts)
83 get_npm_version('angular', opts)
84 end
72 end85 end
73end86end
lib/docs/scrapers/ansible.rb+57−13
@@ -2,27 +2,71 @@ module Docs · 1 unmodified line
2 class Ansible < UrlScraper2 class Ansible < UrlScraper
3 self.name = 'Ansible'3 self.name = 'Ansible'
4 self.type = 'sphinx'4 self.type = 'sphinx'
5 self.release = '2.4.0'
6 self.base_url = 'https://docs.ansible.com/ansible/latest/'
7 self.links = {5 self.links = {
8 home: 'https://www.ansible.com/',6 home: 'https://www.ansible.com/',
9 code: 'https://github.com/ansible/ansible'7 code: 'https://github.com/ansible/ansible'
10 }8 }
11 9
12 html_filters.push 'ansible/entries', 'ansible/clean_html', 'sphinx/clean_html'10 html_filters.push 'ansible/entries', 'sphinx/clean_html', 'ansible/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)
21 11
22 options[:attribution] = <<-HTML12 options[:attribution] = <<-HTML
23 &copy; 2012&ndash;2017 Michael DeHaan<br>13 &copy; 2012&ndash;2018 Michael DeHaan<br>
24 &copy; 2017 Red Hat, Inc.<br>14 &copy; 2018&ndash;2019 Red Hat, Inc.<br>
25 Licensed under the GNU General Public License version 3.15 Licensed under the GNU General Public License version 3.
26 HTML16 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
27 end71 end
28end72end
lib/docs/scrapers/apache.rb+7−2
@@ -3,7 +3,7 @@ module Docs · 2 unmodified lines
3 self.name = 'Apache HTTP Server'3 self.name = 'Apache HTTP Server'
4 self.slug = 'apache_http_server'4 self.slug = 'apache_http_server'
5 self.type = 'apache'5 self.type = 'apache'
6 self.release = '2.4.27'6 self.release = '2.4.37'
7 self.base_url = 'https://httpd.apache.org/docs/2.4/en/'7 self.base_url = 'https://httpd.apache.org/docs/2.4/en/'
8 self.links = {8 self.links = {
9 home: 'https://httpd.apache.org/'9 home: 'https://httpd.apache.org/'
@@ -30,8 +30,13 @@ module Docs · 20 unmodified lines
30 /\Adeveloper\// ]30 /\Adeveloper\// ]
31 31
32 options[:attribution] = <<-HTML32 options[:attribution] = <<-HTML
33 &copy; 2017 The Apache Software Foundation<br>33 &copy; 2018 The Apache Software Foundation<br>
34 Licensed under the Apache License, Version 2.0.34 Licensed under the Apache License, Version 2.0.
35 HTML35 HTML
36
37 def get_latest_version(opts)
38 doc = fetch_doc('http://httpd.apache.org/docs/', opts)
39 doc.at_css('#apcontents > ul a')['href'][0...-1]
40 end
36 end41 end
37end42end
lib/docs/scrapers/apache_pig.rb+6−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class ApachePig < UrlScraper2 class ApachePig < UrlScraper
3 self.name = 'Apache Pig'3 self.name = 'Apache Pig'
4 self.slug = 'apache_pig'4 self.slug = 'apache_pig'
5 self.type = 'apache_pig'5 self.type = 'simple'
6 self.links = {6 self.links = {
7 home: 'https://pig.apache.org/'7 home: 'https://pig.apache.org/'
8 }8 }
@@ -43,5 +43,10 @@ module Docs · 34 unmodified lines
43 self.base_url = "https://pig.apache.org/docs/r#{release}/"43 self.base_url = "https://pig.apache.org/docs/r#{release}/"
44 end44 end
45 45
46 def get_latest_version(opts)
47 doc = fetch_doc('https://pig.apache.org/', opts)
48 item = doc.at_css('div[id="menu_1.2"] > .menuitem:last-child')
49 item.content.strip.sub(/Release /, '')
50 end
46 end51 end
47end52end
lib/docs/scrapers/async.rb+7−2
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Async < UrlScraper2 class Async < UrlScraper
3 self.type = 'async'3 self.type = 'async'
4 self.release = '2.5.0'4 self.release = '2.6.1'
5 self.base_url = 'https://caolan.github.io/async/'5 self.base_url = 'https://caolan.github.io/async/'
6 self.root_path = 'docs.html'6 self.root_path = 'docs.html'
7 self.links = {7 self.links = {
@@ -14,8 +14,13 @@ module Docs · 6 unmodified lines
14 options[:skip_links] = true14 options[:skip_links] = true
15 15
16 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
17 &copy; 2010&ndash;2017 Caolan McMahon<br>17 &copy; 2010&ndash;2018 Caolan McMahon<br>
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 doc = fetch_doc('https://caolan.github.io/async/v3/', opts)
23 doc.at_css('#version-dropdown > a').content.strip[1..-1]
24 end
20 end25 end
21end26end
lib/docs/scrapers/babel.rb+31−0
@@ -0,0 +1,31 @@
1module 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
31end
lib/docs/scrapers/backbone.rb+5−0
@@ -20,5 +20,10 @@ module Docs · 19 unmodified lines
20 &copy; 2010&ndash;2016 Jeremy Ashkenas, DocumentCloud<br>20 &copy; 2010&ndash;2016 Jeremy Ashkenas, DocumentCloud<br>
21 Licensed under the MIT License.21 Licensed under the MIT License.
22 HTML22 HTML
23
24 def get_latest_version(opts)
25 doc = fetch_doc('https://backbonejs.org/', opts)
26 doc.at_css('.version').content[1...-1]
27 end
23 end28 end
24end29end
lib/docs/scrapers/bash.rb+26−0
@@ -0,0 +1,26 @@
1module Docs
2 class Bash < UrlScraper
3 self.type = 'bash'
4 self.release = '5.0'
5 self.base_url = 'https://www.gnu.org/software/bash/manual'
6 self.root_path = '/html_node/index.html'
7 self.links = {
8 home: 'https://www.gnu.org/software/bash/',
9 code: 'http://git.savannah.gnu.org/cgit/bash.git'
10 }
11
12 html_filters.push 'bash/entries', 'bash/clean_html'
13
14 options[:only_patterns] = [/\/html_node\//]
15
16 options[:attribution] = <<-HTML
17 Copyright &copy; 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.<br>
18 Licensed under the GNU Free Documentation License.
19 HTML
20
21 def get_latest_version(opts)
22 body = fetch('https://www.gnu.org/software/bash/manual/html_node/index.html', opts)
23 body.scan(/, Version ([0-9.]+)/)[0][0][0...-1]
24 end
25 end
26end
lib/docs/scrapers/bluebird.rb+26−0
@@ -0,0 +1,26 @@
1module 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
26end
lib/docs/scrapers/bootstrap.rb+10−4
@@ -8,16 +8,17 @@ module Docs · 7 unmodified lines
8 8
9 options[:trailing_slash] = true9 options[:trailing_slash] = true
10 10
11 # https://github.com/twbs/bootstrap/blob/master/LICENSE
11 options[:attribution] = <<-HTML12 options[:attribution] = <<-HTML
12 &copy; 2011&ndash;2017 Twitter, Inc.<br>13 &copy; 2011&ndash;2019 Twitter, Inc.<br>
13 &copy; 2011&ndash;2017 The Bootstrap Authors<br>14 &copy; 2011&ndash;2019 The Bootstrap Authors<br>
14 Code licensed under the MIT License.<br>15 Code licensed under the MIT License.<br>
15 Documentation licensed under the Creative Commons Attribution License v3.0.16 Documentation licensed under the Creative Commons Attribution License v3.0.
16 HTML17 HTML
17 18
18 version '4' do19 version '4' do
19 self.release = '4.0.0-beta'20 self.release = '4.3.1'
20 self.base_url = 'https://getbootstrap.com/docs/4.0/'21 self.base_url = 'https://getbootstrap.com/docs/4.3/'
21 self.root_path = 'getting-started/introduction/'22 self.root_path = 'getting-started/introduction/'
22 23
23 html_filters.push 'bootstrap/entries_v4', 'bootstrap/clean_html_v4'24 html_filters.push 'bootstrap/entries_v4', 'bootstrap/clean_html_v4'
@@ -34,5 +35,10 @@ module Docs · 10 unmodified lines
34 35
35 options[:only] = %w(getting-started/ css/ components/ javascript/)36 options[:only] = %w(getting-started/ css/ components/ javascript/)
36 end37 end
38
39 def get_latest_version(opts)
40 doc = fetch_doc('https://getbootstrap.com/', opts)
41 doc.at_css('#bd-versions').content.strip[1..-1]
42 end
37 end43 end
38end44end
lib/docs/scrapers/bottle.rb+6−0
@@ -27,5 +27,11 @@ module Docs · 26 unmodified lines
27 self.release = '0.11.7'27 self.release = '0.11.7'
28 self.base_url = "https://bottlepy.org/docs/#{self.version}/"28 self.base_url = "https://bottlepy.org/docs/#{self.version}/"
29 end29 end
30
31 def get_latest_version(opts)
32 doc = fetch_doc('https://bottlepy.org/docs/stable/', opts)
33 label = doc.at_css('.sphinxsidebarwrapper > ul > li > b')
34 label.content.sub(/Bottle /, '')
35 end
30 end36 end
31end37end
lib/docs/scrapers/bower.rb+7−3
@@ -1,8 +1,8 @@
1module Docs1module Docs
2 class Bower < UrlScraper2 class Bower < UrlScraper
3 self.name = 'Bower'3 self.name = 'Bower'
4 self.type = 'bower'4 self.type = 'simple'
5 self.release = '1.8.2'5 self.release = '1.8.4'
6 self.base_url = 'https://bower.io/docs/'6 self.base_url = 'https://bower.io/docs/'
7 self.root_path = 'api'7 self.root_path = 'api'
8 self.links = {8 self.links = {
@@ -16,8 +16,12 @@ module Docs · 7 unmodified lines
16 options[:skip] = %w(tools about)16 options[:skip] = %w(tools about)
17 17
18 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
19 &copy; 2017 Bower contributors<br>19 &copy; 2018 Bower contributors<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_npm_version('bower', opts)
25 end
22 end26 end
23end27end
lib/docs/scrapers/c.rb+14−1
@@ -1,7 +1,6 @@
1module Docs1module Docs
2 class C < FileScraper2 class C < FileScraper
3 self.type = 'c'3 self.type = 'c'
4 self.dir = '/Users/Thibaut/DevDocs/Docs/C/c'
5 self.base_url = 'http://en.cppreference.com/w/c/'4 self.base_url = 'http://en.cppreference.com/w/c/'
6 self.root_path = 'header.html'5 self.root_path = 'header.html'
7 6
@@ -9,6 +8,7 @@ module Docs · 1 unmodified line
9 html_filters.push 'c/entries', 'c/clean_html', 'title'8 html_filters.push 'c/entries', 'c/clean_html', 'title'
10 text_filters.push 'c/fix_urls'9 text_filters.push 'c/fix_urls'
11 10
11 options[:decode_and_clean_paths] = true
12 options[:container] = '#content'12 options[:container] = '#content'
13 options[:title] = false13 options[:title] = false
14 options[:root_title] = 'C Programming Language'14 options[:root_title] = 'C Programming Language'
@@ -25,5 +25,18 @@ module Docs · 10 unmodified lines
25 &copy; cppreference.com<br>25 &copy; cppreference.com<br>
26 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.26 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
27 HTML27 HTML
28
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
28 end41 end
29end42end
lib/docs/scrapers/cakephp.rb+12−2
@@ -15,14 +15,19 @@ module Docs · 14 unmodified lines
15 options[:skip_patterns] = [/\Asource-/]15 options[:skip_patterns] = [/\Asource-/]
16 16
17 options[:attribution] = <<-HTML17 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>
19 Licensed under the MIT License.<br>19 Licensed under the MIT License.<br>
20 CakePHP is a registered trademark of Cake Software Foundation, Inc.<br>20 CakePHP is a registered trademark of Cake Software Foundation, Inc.<br>
21 We are not endorsed by or affiliated with CakePHP.21 We are not endorsed by or affiliated with CakePHP.
22 HTML22 HTML
23 23
24 version '3.6' do
25 self.release = '3.6.2'
26 self.base_url = 'https://api.cakephp.org/3.6/'
27 end
28
24 version '3.5' do29 version '3.5' do
25 self.release = '3.5.2'30 self.release = '3.5.15'
26 self.base_url = 'https://api.cakephp.org/3.5/'31 self.base_url = 'https://api.cakephp.org/3.5/'
27 end32 end
28 33
@@ -66,6 +71,11 @@ module Docs · 37 unmodified lines
66 self.base_url = 'https://api.cakephp.org/2.7/'71 self.base_url = 'https://api.cakephp.org/2.7/'
67 end72 end
68 73
74 def get_latest_version(opts)
75 doc = fetch_doc('https://api.cakephp.org/3.7/', opts)
76 doc.at_css('.version-picker .dropdown-toggle').content.strip
77 end
78
69 private79 private
70 80
71 def parse(response)81 def parse(response)
lib/docs/scrapers/chai.rb+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Chai < UrlScraper2 class Chai < UrlScraper
3 self.name = 'Chai'3 self.name = 'Chai'
4 self.type = 'chai'4 self.type = 'simple'
5 self.release = '4.1.0'5 self.release = '4.1.0'
6 self.base_url = 'http://chaijs.com'6 self.base_url = 'http://chaijs.com'
7 self.root_path = '/api/'7 self.root_path = '/api/'
@@ -23,5 +23,9 @@ module Docs · 15 unmodified lines
23 &copy; 2016 Chai.js Assertion Library<br>23 &copy; 2016 Chai.js Assertion Library<br>
24 Licensed under the MIT License.24 Licensed under the MIT License.
25 HTML25 HTML
26
27 def get_latest_version(opts)
28 get_npm_version('chai', opts)
29 end
26 end30 end
27end31end
lib/docs/scrapers/chef.rb+5−0
@@ -47,5 +47,10 @@ module Docs · 46 unmodified lines
47 47
48 options[:only_patterns] = [/\A#{client_path}\//, /\A#{server_path}\//]48 options[:only_patterns] = [/\A#{client_path}\//, /\A#{server_path}\//]
49 end49 end
50
51 def get_latest_version(opts)
52 doc = fetch_doc('https://downloads.chef.io/chef', opts)
53 doc.at_css('h1.product-heading > span').content.strip
54 end
50 end55 end
51end56end
lib/docs/scrapers/clojure.rb+18−3
@@ -13,14 +13,29 @@ module Docs · 12 unmodified lines
13 Licensed under the Eclipse Public License 1.0.13 Licensed under the Eclipse Public License 1.0.
14 HTML14 HTML
15 15
16 version '1.8' do16 version '1.10' do
17 self.release = '1.8'17 self.release = '1.10 (stable)'
18 self.base_url = 'https://clojure.github.io/clojure/'18 self.base_url = 'https://clojure.github.io/clojure/'
19 end19 end
20 20
21 version '1.9' do
22 self.release = '1.9 (legacy)'
23 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.9.0/'
24 end
25
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
21 version '1.7' do31 version '1.7' do
22 self.release = '1.7'32 self.release = '1.7 (legacy)'
23 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'33 self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'
24 end34 end
35
36 def get_latest_version(opts)
37 doc = fetch_doc('http://clojure.github.io/clojure/index.html', opts)
38 doc.at_css('#header-version').content[1..-1]
39 end
25 end40 end
26end41end
lib/docs/scrapers/cmake.rb+44−8
@@ -13,36 +13,72 @@ module Docs · 12 unmodified lines
13 options[:title] = false13 options[:title] = false
14 options[:root_title] = 'CMake Reference Documentation'14 options[:root_title] = 'CMake Reference Documentation'
15 options[:skip] = %w(release/index.html genindex.html search.html)15 options[:skip] = %w(release/index.html genindex.html search.html)
16 options[:skip_patterns] = [/\Agenerator/, /\Ainclude/, /\Arelease/]16 options[:skip_patterns] = [/\Agenerator/, /\Acpack_gen/, /\Ainclude/, /\Arelease/]
17 17
18 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
19 &copy; 2000&ndash;2017 Kitware, Inc. and Contributors<br>19 &copy; 2000&ndash;2019 Kitware, Inc. and Contributors<br>
20 Licensed under the BSD 3-clause License.20 Licensed under the BSD 3-clause License.
21 HTML21 HTML
22 22
23 version '3.15' do
24 self.release = '3.15.2'
25 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
26 end
27
28 version '3.14' do
29 self.release = '3.14.6'
30 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
31 end
32
33 version '3.13' do
34 self.release = '3.13.5'
35 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
36 end
37
38 version '3.12' do
39 self.release = '3.12.4'
40 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
41 end
42
43 version '3.11' do
44 self.release = '3.11.4'
45 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
46 end
47
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
23 version '3.9' do53 version '3.9' do
24 self.release = '3.9.0'54 self.release = '3.9.6'
25 self.base_url = 'https://cmake.org/cmake/help/v3.9/'55 self.base_url = "https://cmake.org/cmake/help/v#{self.version}/"
26 end56 end
27 57
28 version '3.8' do58 version '3.8' do
29 self.release = '3.8.2'59 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}/"
31 end61 end
32 62
33 version '3.7' do63 version '3.7' do
34 self.release = '3.7.2'64 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}/"
36 end66 end
37 67
38 version '3.6' do68 version '3.6' do
39 self.release = '3.6.3'69 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}/"
41 end71 end
42 72
43 version '3.5' do73 version '3.5' do
44 self.release = '3.5.2'74 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]
46 end82 end
47 end83 end
48end84end
lib/docs/scrapers/codeception.rb+9−4
@@ -2,11 +2,11 @@ module Docs · 1 unmodified line
2 class Codeception < UrlScraper2 class Codeception < UrlScraper
3 self.name = 'Codeception'3 self.name = 'Codeception'
4 self.type = 'codeception'4 self.type = 'codeception'
5 self.release = '2.3.5'5 self.release = '2.5.1'
6 self.base_url = 'http://codeception.com/docs/'6 self.base_url = 'https://codeception.com/docs/'
7 self.root_path = 'index.html'7 self.root_path = 'index.html'
8 self.links = {8 self.links = {
9 home: 'http://codeception.com/',9 home: 'https://codeception.com/',
10 code: 'https://github.com/codeception/codeception'10 code: 'https://github.com/codeception/codeception'
11 }11 }
12 12
@@ -15,8 +15,13 @@ module Docs · 2 unmodified lines
15 options[:skip_patterns] = [/install/]15 options[:skip_patterns] = [/install/]
16 16
17 options[:attribution] = <<-HTML17 options[:attribution] = <<-HTML
18 &copy; 2011&ndash;2017 Michael Bodnarchuk and contributors<br>18 &copy; 2011 Michael Bodnarchuk and contributors<br>
19 Licensed under the MIT License.19 Licensed under the MIT License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 doc = fetch_doc('https://codeception.com/changelog', opts)
24 doc.at_css('#page > h4').content
25 end
21 end26 end
22end27end
lib/docs/scrapers/codeceptjs.rb+8−4
@@ -1,12 +1,12 @@
1module Docs1module Docs
2 class Codeceptjs < UrlScraper2 class Codeceptjs < UrlScraper
3 self.name = 'CodeceptJS'3 self.name = 'CodeceptJS'
4 self.type = 'codeceptjs'4 self.type = 'simple'
5 self.root_path = 'index.html'5 self.root_path = 'index.html'
6 self.release = '1.0.1'6 self.release = '1.4.4'
7 self.base_url = 'http://codecept.io/'7 self.base_url = 'https://codecept.io/'
8 self.links = {8 self.links = {
9 home: 'http://codecept.io/',9 home: 'https://codecept.io/',
10 code: 'https://github.com/codeception/codeceptjs'10 code: 'https://github.com/codeception/codeceptjs'
11 }11 }
12 12
@@ -21,5 +21,9 @@ module Docs · 8 unmodified lines
21 &copy; 2015 DavertMik &lt;davert@codegyre.com&gt; (http://codegyre.com)<br>21 &copy; 2015 DavertMik &lt;davert@codegyre.com&gt; (http://codegyre.com)<br>
22 Licensed under the MIT License.22 Licensed under the MIT License.
23 HTML23 HTML
24
25 def get_latest_version(opts)
26 get_npm_version('codeceptjs', opts)
27 end
24 end28 end
25end29end
lib/docs/scrapers/codeigniter.rb+8−2
@@ -31,12 +31,18 @@ module Docs · 30 unmodified lines
31 ]31 ]
32 32
33 options[:attribution] = <<-HTML33 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>
35 Licensed under the MIT License.35 Licensed under the MIT License.
36 HTML36 HTML
37 37
38 version '3' do38 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]
40 end46 end
41 end47 end
42end48end
lib/docs/scrapers/coffeescript.rb+6−2
@@ -11,12 +11,12 @@ module Docs · 10 unmodified lines
11 options[:skip_links] = true11 options[:skip_links] = true
12 12
13 options[:attribution] = <<-HTML13 options[:attribution] = <<-HTML
14 &copy; 2009&ndash;2017 Jeremy Ashkenas<br>14 &copy; 2009&ndash;2018 Jeremy Ashkenas<br>
15 Licensed under the MIT License.15 Licensed under the MIT License.
16 HTML16 HTML
17 17
18 version '2' do18 version '2' do
19 self.release = '2.0.1'19 self.release = '2.3.0'
20 self.base_url = 'http://coffeescript.org/'20 self.base_url = 'http://coffeescript.org/'
21 21
22 html_filters.push 'coffeescript/entries', 'coffeescript/clean_html', 'title'22 html_filters.push 'coffeescript/entries', 'coffeescript/clean_html', 'title'
@@ -30,5 +30,9 @@ module Docs · 7 unmodified lines
30 30
31 options[:container] = '.container'31 options[:container] = '.container'
32 end32 end
33
34 def get_latest_version(opts)
35 get_npm_version('coffeescript', opts)
36 end
33 end37 end
34end38end
lib/docs/scrapers/cordova.rb+17−2
@@ -24,12 +24,17 @@ module Docs · 23 unmodified lines
24 end24 end
25 25
26 options[:attribution] = <<-HTML26 options[:attribution] = <<-HTML
27 &copy; 2012&ndash;2017 The Apache Software Foundation<br>27 &copy; 2012&ndash;2018 The Apache Software Foundation<br>
28 Licensed under the Apache License 2.0.28 Licensed under the Apache License 2.0.
29 HTML29 HTML
30 30
31 version '8' do
32 self.release = '8.1.2'
33 self.base_url = 'https://cordova.apache.org/docs/en/8.x/'
34 end
35
31 version '7' do36 version '7' do
32 self.release = '7.0.1'37 self.release = '7.1.0'
33 self.base_url = 'https://cordova.apache.org/docs/en/7.x/'38 self.base_url = 'https://cordova.apache.org/docs/en/7.x/'
34 end39 end
35 40
@@ -37,5 +42,15 @@ module Docs · 1 unmodified line
37 self.release = '6.5.0'42 self.release = '6.5.0'
38 self.base_url = 'https://cordova.apache.org/docs/en/6.x/'43 self.base_url = 'https://cordova.apache.org/docs/en/6.x/'
39 end44 end
45
46 def get_latest_version(opts)
47 doc = fetch_doc('https://cordova.apache.org/docs/en/latest/', opts)
48
49 label = doc.at_css('#versionDropdown').content.strip
50 version = label.scan(/([0-9.]+)/)[0][0]
51 version = version[0...-1] if version.end_with?('.')
52
53 version
54 end
40 end55 end
41end56end
lib/docs/scrapers/cpp.rb+15−1
@@ -3,7 +3,6 @@ module Docs · 2 unmodified lines
3 self.name = 'C++'3 self.name = 'C++'
4 self.slug = 'cpp'4 self.slug = 'cpp'
5 self.type = 'c'5 self.type = 'c'
6 self.dir = '/Users/Thibaut/DevDocs/Docs/C/cpp'
7 self.base_url = 'http://en.cppreference.com/w/cpp/'6 self.base_url = 'http://en.cppreference.com/w/cpp/'
8 self.root_path = 'header.html'7 self.root_path = 'header.html'
9 8
@@ -11,6 +10,7 @@ module Docs · 1 unmodified line
11 html_filters.push 'cpp/entries', 'c/clean_html', 'title'10 html_filters.push 'cpp/entries', 'c/clean_html', 'title'
12 text_filters.push 'cpp/fix_urls'11 text_filters.push 'cpp/fix_urls'
13 12
13 options[:decode_and_clean_paths] = true
14 options[:container] = '#content'14 options[:container] = '#content'
15 options[:title] = false15 options[:title] = false
16 options[:root_title] = 'C++ Programming Language'16 options[:root_title] = 'C++ Programming Language'
@@ -33,5 +33,19 @@ module Docs · 16 unmodified lines
33 &copy; cppreference.com<br>33 &copy; cppreference.com<br>
34 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.34 Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
35 HTML35 HTML
36
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
36 end50 end
37end51end
lib/docs/scrapers/crystal.rb+7−2
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Crystal < UrlScraper2 class Crystal < UrlScraper
3 self.type = 'crystal'3 self.type = 'crystal'
4 self.release = '0.23.0'4 self.release = '0.27.0'
5 self.base_url = 'https://crystal-lang.org/'5 self.base_url = 'https://crystal-lang.org/'
6 self.root_path = "api/#{release}/index.html"6 self.root_path = "api/#{release}/index.html"
7 self.initial_paths = %w(docs/index.html)7 self.initial_paths = %w(docs/index.html)
@@ -29,10 +29,15 @@ module Docs · 21 unmodified lines
29 HTML29 HTML
30 else30 else
31 <<-HTML31 <<-HTML
32 &copy; 2012&ndash;2017 Manas Technology Solutions.<br>32 &copy; 2012&ndash;2018 Manas Technology Solutions.<br>
33 Licensed under the Apache License, Version 2.0.33 Licensed under the Apache License, Version 2.0.
34 HTML34 HTML
35 end35 end
36 }36 }
37
38 def get_latest_version(opts)
39 body = fetch('https://crystal-lang.org/api', opts)
40 body.scan(/Crystal Docs ([0-9.]+)/)[0][0]
41 end
37 end42 end
38end43end
lib/docs/scrapers/d.rb+7−2
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class D < UrlScraper2 class D < UrlScraper
3 include MultipleBaseUrls3 include MultipleBaseUrls
4 4
5 self.release = '2.076.0'5 self.release = '2.083.0'
6 self.type = 'd'6 self.type = 'd'
7 self.base_urls = ['https://dlang.org/phobos/', 'https://dlang.org/spec/']7 self.base_urls = ['https://dlang.org/phobos/', 'https://dlang.org/spec/']
8 self.root_path = 'index.html'8 self.root_path = 'index.html'
@@ -19,12 +19,17 @@ module Docs · 10 unmodified lines
19 options[:title] = false19 options[:title] = false
20 20
21 options[:attribution] = <<-HTML21 options[:attribution] = <<-HTML
22 &copy; 1999&ndash;2017 The D Language Foundation<br>22 &copy; 1999&ndash;2018 The D Language Foundation<br>
23 Licensed under the Boost License 1.0.23 Licensed under the Boost License 1.0.
24 HTML24 HTML
25 25
26 def initial_urls26 def initial_urls
27 %w(https://dlang.org/phobos/index.html https://dlang.org/spec/intro.html)27 %w(https://dlang.org/phobos/index.html https://dlang.org/spec/intro.html)
28 end28 end
29
30 def get_latest_version(opts)
31 doc = fetch_doc('https://dlang.org/changelog/', opts)
32 doc.at_css('#content > ul > li:nth-child(2) > a')['id']
33 end
29 end34 end
30end35end
lib/docs/scrapers/d3.rb+22−2
@@ -12,12 +12,28 @@ module Docs · 11 unmodified lines
12 options[:container] = '.markdown-body'12 options[:container] = '.markdown-body'
13 13
14 options[:attribution] = <<-HTML14 options[:attribution] = <<-HTML
15 &copy; 2010&ndash;2017 Michael Bostock<br>15 &copy; 2010&ndash;2018 Michael Bostock<br>
16 Licensed under the BSD License.16 Licensed under the BSD License.
17 HTML17 HTML
18 18
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
19 version '4' do35 version '4' do
20 self.release = '4.11.0'36 self.release = '4.12.2'
21 self.base_url = 'https://github.com/d3/'37 self.base_url = 'https://github.com/d3/'
22 self.root_path = 'd3/blob/master/API.md'38 self.root_path = 'd3/blob/master/API.md'
23 39
@@ -42,5 +58,9 @@ module Docs · 18 unmodified lines
42 options[:root_title] = 'D3.js'58 options[:root_title] = 'D3.js'
43 options[:only_patterns] = [/\.md\z/]59 options[:only_patterns] = [/\.md\z/]
44 end60 end
61
62 def get_latest_version(opts)
63 get_npm_version('d3', opts)
64 end
45 end65 end
46end66end
lib/docs/scrapers/dart.rb+41−0
@@ -0,0 +1,41 @@
1module Docs
2 class Dart < FileScraper
3 self.type = 'dart'
4 self.root_path = 'index.html'
5 self.links = {
6 home: 'https://www.dartlang.org/',
7 code: 'https://github.com/dart-lang/sdk'
8 }
9
10 html_filters.push 'dart/entries', 'dart/clean_html'
11
12 options[:fix_urls] = ->(url) do
13 # localhost/dart-web_audio/..dart-io/dart-io-library.html > localhost/dart-io/dart-io-library.html
14 url.remove!(/[^\/]+\/\.\./)
15 url
16 end
17
18 options[:attribution] = <<-HTML
19 &copy; 2012 the Dart project authors<br>
20 Licensed under the Creative Commons Attribution-ShareAlike License v4.0.
21 HTML
22
23 # Download the documentation from https://www.dartlang.org/tools/sdk/archive
24
25 version '2' do
26 self.release = '2.0.0'
27 self.base_url = "https://api.dartlang.org/stable/#{release}/"
28 end
29
30 version '1' do
31 self.release = '1.24.3'
32 self.base_url = "https://api.dartlang.org/stable/#{release}/"
33 end
34
35 def get_latest_version(opts)
36 doc = fetch_doc('https://api.dartlang.org/', opts)
37 label = doc.at_css('footer > span').content.strip
38 label.sub(/Dart /, '')
39 end
40 end
41end
lib/docs/scrapers/django.rb+21−5
@@ -34,28 +34,44 @@ module Docs · 33 unmodified lines
34 Licensed under the BSD License.34 Licensed under the BSD License.
35 HTML35 HTML
36 36
37 version '2.2' do
38 self.release = '2.2.4'
39 self.base_url = 'https://docs.djangoproject.com/en/2.2/'
40 end
41
42 version '2.1' do
43 self.release = '2.1.11'
44 self.base_url = 'https://docs.djangoproject.com/en/2.1/'
45 end
46
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
37 version '1.11' do52 version '1.11' do
38 self.release = '1.11.5'53 self.release = '1.11.23'
39 self.dir = '/Users/Thibaut/DevDocs/Docs/Django111'
40 self.base_url = 'https://docs.djangoproject.com/en/1.11/'54 self.base_url = 'https://docs.djangoproject.com/en/1.11/'
41 end55 end
42 56
43 version '1.10' do57 version '1.10' do
44 self.release = '1.10.8'58 self.release = '1.10.8'
45 self.dir = '/Users/Thibaut/DevDocs/Docs/Django110'
46 self.base_url = 'https://docs.djangoproject.com/en/1.10/'59 self.base_url = 'https://docs.djangoproject.com/en/1.10/'
47 end60 end
48 61
49 version '1.9' do62 version '1.9' do
50 self.release = '1.9.13'63 self.release = '1.9.13'
51 self.dir = '/Users/Thibaut/DevDocs/Docs/Django19'
52 self.base_url = 'https://docs.djangoproject.com/en/1.9/'64 self.base_url = 'https://docs.djangoproject.com/en/1.9/'
53 end65 end
54 66
55 version '1.8' do67 version '1.8' do
56 self.release = '1.8.18'68 self.release = '1.8.18'
57 self.dir = '/Users/Thibaut/DevDocs/Docs/Django18'
58 self.base_url = 'https://docs.djangoproject.com/en/1.8/'69 self.base_url = 'https://docs.djangoproject.com/en/1.8/'
59 end70 end
71
72 def get_latest_version(opts)
73 doc = fetch_doc('https://docs.djangoproject.com/', opts)
74 doc.at_css('#doc-versions > li.current > span > strong').content
75 end
60 end76 end
61end77end
lib/docs/scrapers/docker.rb+154−27
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Docker < UrlScraper2 class Docker < UrlScraper
3 self.type = 'docker'3 self.type = 'simple'
4 self.links = {4 self.links = {
5 home: 'https://docker.com/',5 home: 'https://docker.com/',
6 code: 'https://github.com/docker/docker'6 code: 'https://github.com/docker/docker'
@@ -15,15 +15,15 @@ module Docs · 8 unmodified lines
15 end15 end
16 16
17 options[:attribution] = <<-HTML17 options[:attribution] = <<-HTML
18 &copy; 2017 Docker, Inc.<br>18 &copy; 2019 Docker, Inc.<br>
19 Licensed under the Apache License, Version 2.0.<br>19 Licensed under the Apache License, Version 2.0.<br>
20 Docker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries.<br>20 Docker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries.<br>
21 Docker, Inc. and other parties may also have trademark rights in other terms used herein.21 Docker, Inc. and other parties may also have trademark rights in other terms used herein.
22 HTML22 HTML
23 23
24 version '17' do24 version '19' do
25 self.release = '17.06'25 self.release = '19.03'
26 self.base_url = 'https://docs.docker.com/'26 self.base_url = "https://docs.docker.com/"
27 27
28 html_filters.push 'docker/entries', 'docker/clean_html'28 html_filters.push 'docker/entries', 'docker/clean_html'
29 29
@@ -33,29 +33,150 @@ module Docs · 3 unmodified lines
33 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]33 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]
34 34
35 options[:replace_paths] = {35 options[:replace_paths] = {
36 'engine/installation/linux/docker-ee/linux-postinstall/' => 'engine/installation/linux/linux-postinstall/',36 'install/linux/ubuntu/' => 'install/linux/docker-ce',
37 'engine/installation/linux/docker-ee/' => 'engine/installation/',37 'get-started/part1' => 'get-started',
38 'engine/installation/linux/docker-ce/' => 'engine/installation/',38 'engine/installation/' => 'install',
39 'engine/installation/linux/' => 'engine/installation/',39 'engine/installation/linux/linux-postinstall/' => 'install/linux',
40 'engine/installation/windows/' => 'engine/installation/',40 'compose/overview/' => 'compose',
41 'engine/userguide/intro/' => 'engine/userguide/',41 'docker-cloud/' => 'docker-hub',
42 'engine/tutorials/dockervolumes/' => 'engine/admin/volumes/volumes/',42 'datacenter/install/linux/' => 'ee',
43 'engine/getstarted/' => 'get-started/',43 'engine/userguide/' => 'config/daemon',
44 'engine/tutorials/dockerimages/' => 'get-started/',44 'engine/admin/' => 'config/daemon',
45 'engine/tutorials/dockerrepos/' => 'get-started/',45 'opensource/get-help/' => 'opensource',
46 'engine/admin/host_integration/' => 'engine/admin/start-containers-automatically/',46 'engine/tutorials/dockerimages/' => 'get-started',
47 'engine/installation/linux/rhel/' => 'engine/installation/linux/docker-ee/rhel/',47 'engine/admin/volumes/bind-mounts/' => 'storage',
48 'engine/installation/linux/ubuntulinux/' => 'engine/installation/linux/docker-ee/ubuntu/',48 'engine/tutorials/dockervolumes/' => 'storage',
49 'engine/installation/linux/suse/' => 'engine/installation/linux/docker-ee/suse/',49 'engine/admin/volumes/volumes/' => 'storage',
50 'engine/admin/logging/' => 'engine/admin/logging/view_container_logs/',50 'engine/userguide/labels-custom-metadata/' => 'config',
51 'engine/swarm/how-swarm-mode-works/' => 'engine/swarm/how-swarm-mode-works/nodes/',51 'engine/userguide/eng-image/multistage-build/' => 'develop/develop-images',
52 'engine/installation/binaries/' => 'engine/installation/linux/docker-ce/binaries/',52 'engine/swarm/networking/' => 'network',
53 'engine/admin/resource_constraints/' => 'config/containers',
54 'engine/admin/logging/overview/' => 'config/containers/logging',
55 'engine/userguide/eng-image/dockerfile_best-practices/' => 'develop/develop-images',
56 'engine/tutorials/dockerrepos/' => 'get-started',
57 'engine/userguide/networking/' => 'network',
58 'engine/userguide/networking/get-started-overlay/' => 'network',
59 'engine/reference/commandline/swarm_join_token/' => 'engine/reference/commandline',
60 'engine/understanding-docker/' => 'engine',
61 'engine/userguide/dockervolumes/' => 'storage',
62 'engine/installation/binaries/' => 'install/linux/docker-ce',
63 'engine/userguide/networking/default_network/dockerlinks/' => 'network',
64 'engine/reference/api/' => 'develop/sdk',
65 'engine/admin/systemd/' => 'config/daemon',
66 'engine/userguide/storagedriver/imagesandcontainers/' => 'storage/storagedriver',
67 'engine/api/' => 'develop/sdk',
68 'engine/userguide/networking/get-started-overlay' => 'network',
69 'engine/userguide/networking/overlay-security-model/' => 'network',
70 'engine/installation/linux/docker-ce/binaries/' => 'install/linux/docker-ce',
71 'engine/admin/volumes/' => 'storage/volumes/',
72 'engine/userguide/networking//' => 'network',
73 'engine/reference/commandline' => 'engine/reference/commandline/docker',
53 'engine/reference/commandline/' => 'engine/reference/commandline/docker/',74 'engine/reference/commandline/' => 'engine/reference/commandline/docker/',
54 'engine/reference/api/' => 'engine/api/',75 }
55 'engine/userguide/dockervolumes/' => 'engine/admin/volumes/volumes/',76 end
56 'engine/understanding-docker/' => 'engine/docker-overview/',77
57 'engine/reference/commandline/swarm_join_token/' => 'engine/reference/commandline/swarm_join-token/',78 version '18' do
58 'engine/api/getting-started/' => 'engine/api/get-started/',79 self.release = '18.09'
80 self.base_url = "https://docs.docker.com/v#{release}/"
81
82 html_filters.push 'docker/entries', 'docker/clean_html'
83
84 options[:container] = '.wrapper .container-fluid .row'
85
86 options[:only_patterns] = [/\Aget-started\//, /\Aengine\//, /\Acompose\//, /\Amachine\//, /\Anotary\//]
87 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]
88
89 options[:replace_paths] = {
90 'install/linux/ubuntu/' => 'install/linux/docker-ce',
91 'get-started/part1' => 'get-started',
92 'engine/installation/' => 'install',
93 'engine/installation/linux/linux-postinstall/' => 'install/linux',
94 'compose/overview/' => 'compose',
95 'datacenter/install/linux/' => 'ee',
96 'engine/userguide/' => 'config/daemon',
97 'engine/admin/' => 'config/daemon',
98 'opensource/get-help/' => 'opensource',
99 'engine/tutorials/dockerimages/' => 'get-started',
100 'engine/admin/volumes/bind-mounts/' => 'storage',
101 'engine/tutorials/dockervolumes/' => 'storage',
102 'engine/admin/volumes/volumes/' => 'storage',
103 'engine/userguide/labels-custom-metadata/' => 'config',
104 'engine/reference/api/' => 'develop/sdk',
105 'engine/userguide/eng-image/multistage-build/' => 'develop/develop-images',
106 'engine/swarm/networking/' => 'network',
107 'engine/admin/resource_constraints/' => 'config/containers',
108 'engine/admin/logging/overview/' => 'config/containers/logging',
109 'engine/userguide/eng-image/dockerfile_best-practices/' => 'develop/develop-images',
110 'engine/tutorials/dockerrepos/' => 'get-started',
111 'engine/userguide/networking/' => 'network',
112 'engine/userguide/networking/get-started-overlay/' => 'network',
113 'engine/understanding-docker/' => 'engine',
114 'engine/reference/commandline/swarm_join_token/' => 'engine/reference/commandline',
115 'engine/userguide/dockervolumes/' => 'storage',
116 'engine/admin/systemd/' => 'config/daemon',
117 'engine/userguide/storagedriver/imagesandcontainers/' => 'storage/storagedriver',
118 'engine/installation/binaries/' => 'install/linux/docker-ce',
119 'engine/userguide/networking/default_network/dockerlinks/' => 'network',
120 'engine/userguide/networking/overlay-security-model/' => 'network',
121 'engine/userguide/networking/get-started-overlay' => 'network',
122 'engine/api/' => 'develop/sdk',
123 'engine/installation/linux/docker-ce/binaries/' => 'install/linux/docker-ce',
124 'engine/admin/volumes/' => 'storage/volumes/',
125 'engine/userguide/networking//' => 'network',
126 'engine/reference/commandline' => 'engine/reference/commandline/docker',
127 'engine/reference/commandline/' => 'engine/reference/commandline/docker/',
128 }
129 end
130
131 version '17' do
132 self.release = '17.12'
133 self.base_url = "https://docs.docker.com/v#{release}/"
134
135 html_filters.push 'docker/entries', 'docker/clean_html'
136
137 options[:container] = '.wrapper .container-fluid .row'
138
139 options[:only_patterns] = [/\Aget-started\//, /\Aengine\//, /\Acompose\//, /\Amachine\//, /\Anotary\//]
140 options[:skip_patterns] = [/\Aengine\/api\/v/, /glossary/, /docker-ee/]
141
142 options[:replace_paths] = {
143 'get-started/part1' => 'get-started',
144 'engine/installation/' => 'install',
145 'engine/installation/linux/linux-postinstall/' => 'install/linux',
146 'opensource/get-help/' => 'opensource',
147 'engine/admin/volumes/volumes/' => 'storage',
148 'engine/tutorials/dockerimages/' => 'get-started',
149 'engine/admin/volumes/bind-mounts/' => 'storage',
150 'engine/tutorials/dockervolumes/' => 'storage',
151 'datacenter/install/aws/' => 'docker-for-aws',
152 'engine/userguide/' => 'config/daemon',
153 'engine/admin/' => 'config/daemon',
154 'engine/userguide/labels-custom-metadata/' => 'config',
155 'engine/userguide/eng-image/multistage-build/' => 'develop/develop-images',
156 'engine/swarm/networking/' => 'network',
157 'engine/admin/resource_constraints/' => 'config/containers',
158 'engine/admin/logging/overview/' => 'config/containers/logging',
159 'engine/understanding-docker/' => 'engine',
160 'engine/userguide/eng-image/dockerfile_best-practices/' => 'develop/develop-images',
161 'engine/tutorials/dockerrepos/' => 'get-started',
162 'engine/userguide/networking/' => 'network',
163 'engine/reference/commandline/swarm_join_token/' => 'edge/engine/reference/commandline',
164 'engine/userguide/networking/get-started-overlay/' => 'network',
165 'engine/userguide/dockervolumes/' => 'storage',
166 'engine/installation/binaries/' => 'install/linux/docker-ce',
167 'engine/userguide/networking/default_network/dockerlinks/' => 'network',
168 'engine/reference/api/' => 'develop/sdk',
169 'engine/admin/live-restore/' => 'config/containers',
170 'engine/api/' => 'develop/sdk',
171 'engine/userguide/networking/get-started-overlay' => 'network',
172 'security/security/' => 'engine/security',
173 'engine/installation/linux/docker-ce/binaries/' => 'install/linux/docker-ce',
174 'engine/reference/commandline/' => 'edge/engine/reference/commandline',
175 'engine/admin/systemd/' => 'config/daemon',
176 'engine/userguide/storagedriver/imagesandcontainers/' => 'storage/storagedriver',
177 'engine/userguide/networking/overlay-security-model/' => 'network',
178 'engine/admin/volumes/' => 'storage/volumes/',
179 'engine/userguide/networking//' => 'network',
59 }180 }
60 end181 end
61 182
@@ -137,5 +258,11 @@ module Docs · 75 unmodified lines
137 options[:container] = '#docs'258 options[:container] = '#docs'
138 options[:only_patterns] << /\Aswarm\//259 options[:only_patterns] << /\Aswarm\//
139 end260 end
261
262 def get_latest_version(opts)
263 doc = fetch_doc('https://docs.docker.com/', opts)
264 label = doc.at_css('.nav-container button.dropdown-toggle').content.strip
265 label.scan(/([0-9.]+)/)[0][0]
266 end
140 end267 end
141end268end
lib/docs/scrapers/dojo.rb+5−0
@@ -36,6 +36,11 @@ module Docs · 35 unmodified lines
36 urls.map { |url| "<a href='#{url}'>#{url}</a>" }.join36 urls.map { |url| "<a href='#{url}'>#{url}</a>" }.join
37 end37 end
38 38
39 def get_latest_version(opts)
40 doc = fetch_doc('https://dojotoolkit.org/api/', opts)
41 doc.at_css('#versionSelector > option[selected]').content
42 end
43
39 private44 private
40 45
41 def get_url_list(json, set = Set.new)46 def get_url_list(json, set = Set.new)
lib/docs/scrapers/drupal.rb+5−0
@@ -98,5 +98,10 @@ module Docs · 97 unmodified lines
98 /\A[\w\-\.]+\.php\/7\.x\z/98 /\A[\w\-\.]+\.php\/7\.x\z/
99 ]99 ]
100 end100 end
101
102 def get_latest_version(opts)
103 json = fetch_json('https://packagist.org/packages/drupal/drupal.json', opts)
104 json['package']['versions'].keys.find {|version| !version.end_with?('-dev')}
105 end
101 end106 end
102end107end
lib/docs/scrapers/electron.rb+14−9
@@ -1,26 +1,31 @@
1module Docs1module Docs
2 class Electron < UrlScraper2 class Electron < UrlScraper
3 self.type = 'electron'3 self.type = 'simple'
4 self.base_url = 'https://electron.atom.io/docs/'4 self.base_url = 'https://electronjs.org/docs'
5 self.release = '1.7.5'5 self.release = '2.0.2'
6 self.links = {6 self.links = {
7 home: 'https://electron.atom.io/',7 home: 'https://electronjs.org/',
8 code: 'https://github.com/electron/electron'8 code: 'https://github.com/electron/electron'
9 }9 }
10 10
11 html_filters.push 'electron/clean_html', 'electron/entries'11 html_filters.push 'electron/clean_html', 'electron/entries'
12 12
13 options[:trailing_slash] = true13 options[:trailing_slash] = false
14 options[:container] = '.page-section > .container, .page-section > .container-narrow'14 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/]
16 options[:replace_paths] = {17 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/'
19 }19 }
20 20
21 options[:attribution] = <<-HTML21 options[:attribution] = <<-HTML
22 &copy; 2013&ndash;2017 GitHub Inc.<br>22 &copy; 2013&ndash;2018 GitHub Inc.<br>
23 Licensed under the MIT license.23 Licensed under the MIT license.
24 HTML24 HTML
25
26 def get_latest_version(opts)
27 doc = fetch_doc('https://electronjs.org/docs', opts)
28 doc.at_css('.docs-version').content
29 end
25 end30 end
26end31end
lib/docs/scrapers/elixir.rb+59−2
@@ -19,7 +19,7 @@ module Docs · 18 unmodified lines
19 options[:root_title] = 'Elixir'19 options[:root_title] = 'Elixir'
20 20
21 options[:attribution] = <<-HTML21 options[:attribution] = <<-HTML
22 &copy; 2012&ndash;2017 Plataformatec<br>22 &copy; 2012 Plataformatec<br>
23 Licensed under the Apache License, Version 2.0.23 Licensed under the Apache License, Version 2.0.
24 HTML24 HTML
25 25
@@ -33,8 +33,60 @@ module Docs · 7 unmodified lines
33 "https://elixir-lang.org/getting-started/introduction.html" ]33 "https://elixir-lang.org/getting-started/introduction.html" ]
34 end34 end
35 35
36 version '1.9' do
37 self.release = '1.9.1'
38 self.base_urls = [
39 "https://hexdocs.pm/elixir/#{release}/",
40 "https://hexdocs.pm/eex/#{release}/",
41 "https://hexdocs.pm/ex_unit/#{release}/",
42 "https://hexdocs.pm/iex/#{release}/",
43 "https://hexdocs.pm/logger/#{release}/",
44 "https://hexdocs.pm/mix/#{release}/",
45 'https://elixir-lang.org/getting-started/'
46 ]
47 end
48
49 version '1.8' do
50 self.release = '1.8.2'
51 self.base_urls = [
52 "https://hexdocs.pm/elixir/#{release}/",
53 "https://hexdocs.pm/eex/#{release}/",
54 "https://hexdocs.pm/ex_unit/#{release}/",
55 "https://hexdocs.pm/iex/#{release}/",
56 "https://hexdocs.pm/logger/#{release}/",
57 "https://hexdocs.pm/mix/#{release}/",
58 'https://elixir-lang.org/getting-started/'
59 ]
60 end
61
62 version '1.7' do
63 self.release = '1.7.4'
64 self.base_urls = [
65 "https://hexdocs.pm/elixir/#{release}/",
66 "https://hexdocs.pm/eex/#{release}/",
67 "https://hexdocs.pm/ex_unit/#{release}/",
68 "https://hexdocs.pm/iex/#{release}/",
69 "https://hexdocs.pm/logger/#{release}/",
70 "https://hexdocs.pm/mix/#{release}/",
71 'https://elixir-lang.org/getting-started/'
72 ]
73 end
74
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
36 version '1.5' do88 version '1.5' do
37 self.release = '1.5.2'89 self.release = '1.5.3'
38 self.base_urls = [90 self.base_urls = [
39 "https://hexdocs.pm/elixir/#{release}/",91 "https://hexdocs.pm/elixir/#{release}/",
40 "https://hexdocs.pm/eex/#{release}/",92 "https://hexdocs.pm/eex/#{release}/",
@@ -71,5 +123,10 @@ module Docs · 30 unmodified lines
71 'https://elixir-lang.org/getting-started/'123 'https://elixir-lang.org/getting-started/'
72 ]124 ]
73 end125 end
126
127 def get_latest_version(opts)
128 doc = fetch_doc('https://hexdocs.pm/elixir/api-reference.html', opts)
129 doc.at_css('h2.sidebar-projectVersion').content.strip[1..-1]
130 end
74 end131 end
75end132end
lib/docs/scrapers/ember.rb+5−0
@@ -56,5 +56,10 @@ module Docs · 55 unmodified lines
56 https://emberjs.com/api/ember-data/2.14/classes/DS56 https://emberjs.com/api/ember-data/2.14/classes/DS
57 )57 )
58 end58 end
59
60 def get_latest_version(opts)
61 doc = fetch_doc('https://emberjs.com/api/ember/release', opts)
62 doc.at_css('.sidebar > .select-container .ember-power-select-selected-item').content.strip
63 end
59 end64 end
60end65end
lib/docs/scrapers/erlang.rb+10−4
@@ -40,19 +40,25 @@ module Docs · 39 unmodified lines
40 Licensed under the Apache License, Version 2.0.40 Licensed under the Apache License, Version 2.0.
41 HTML41 HTML
42 42
43 version '21' do
44 self.release = '21.0'
45 end
46
43 version '20' do47 version '20' do
44 self.release = '20.1'48 self.release = '20.3'
45 self.dir = '/Users/Thibaut/DevDocs/Docs/Erlang20'
46 end49 end
47 50
48 version '19' do51 version '19' do
49 self.release = '19.3'52 self.release = '19.3'
50 self.dir = '/Users/Thibaut/DevDocs/Docs/Erlang19'
51 end53 end
52 54
53 version '18' do55 version '18' do
54 self.release = '18.3'56 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 /, '')
56 end62 end
57 end63 end
58end64end
lib/docs/scrapers/eslint.rb+28−0
@@ -0,0 +1,28 @@
1module 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
28end
lib/docs/scrapers/express.rb+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Express < UrlScraper2 class Express < UrlScraper
3 self.name = 'Express'3 self.name = 'Express'
4 self.type = 'express'4 self.type = 'express'
5 self.release = '4.16.1'5 self.release = '4.16.3'
6 self.base_url = 'http://expressjs.com/en/'6 self.base_url = 'http://expressjs.com/en/'
7 self.root_path = '4x/api.html'7 self.root_path = '4x/api.html'
8 self.initial_paths = %w(8 self.initial_paths = %w(
@@ -28,5 +28,9 @@ module Docs · 19 unmodified lines
28 &copy; 2017 StrongLoop, IBM, and other expressjs.com contributors.<br>28 &copy; 2017 StrongLoop, IBM, and other expressjs.com contributors.<br>
29 Licensed under the Creative Commons Attribution-ShareAlike License v3.0.29 Licensed under the Creative Commons Attribution-ShareAlike License v3.0.
30 HTML30 HTML
31
32 def get_latest_version(opts)
33 get_npm_version('express', opts)
34 end
31 end35 end
32end36end
lib/docs/scrapers/falcon.rb+11−1
@@ -15,10 +15,15 @@ module Docs · 14 unmodified lines
15 options[:skip_patterns] = [/\Achanges/, /\A_modules/, /\Acommunity/]15 options[:skip_patterns] = [/\Achanges/, /\A_modules/, /\Acommunity/]
16 16
17 options[:attribution] = <<-HTML17 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>
19 Licensed under the Apache License, Version 2.0.19 Licensed under the Apache License, Version 2.0.
20 HTML20 HTML
21 21
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
22 version '1.3' do27 version '1.3' do
23 self.release = '1.3.0'28 self.release = '1.3.0'
24 self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"29 self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"
@@ -28,5 +33,10 @@ module Docs · 3 unmodified lines
28 self.release = '1.2.0'33 self.release = '1.2.0'
29 self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"34 self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"
30 end35 end
36
37 def get_latest_version(opts)
38 doc = fetch_doc('https://falcon.readthedocs.io/en/stable/changes/index.html', opts)
39 doc.at_css('#changelogs ul > li > a').content
40 end
31 end41 end
32end42end
lib/docs/scrapers/fish.rb+17−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Fish < UrlScraper2 class Fish < UrlScraper
3 self.name = 'Fish'3 self.name = 'Fish'
4 self.type = 'fish'4 self.type = 'simple'
5 self.root_path = 'index.html'5 self.root_path = 'index.html'
6 self.links = {6 self.links = {
7 home: 'https://fishshell.com/',7 home: 'https://fishshell.com/',
@@ -12,11 +12,22 @@ module Docs · 4 unmodified lines
12 12
13 options[:skip] = %w(design.html license.html)13 options[:skip] = %w(design.html license.html)
14 14
15 # https://fishshell.com/docs/current/license.html
15 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
16 &copy; 2005&ndash;2009 Axel Liljencrantz<br>17 &copy; 2005&ndash;2009 Axel Liljencrantz<br>
17 Licensed under the GNU General Public License, version 2.18 Licensed under the GNU General Public License, version 2.
18 HTML19 HTML
19 20
21 version '3.0' do
22 self.release = '3.0.1'
23 self.base_url = "https://fishshell.com/docs/#{version}/"
24 end
25
26 version '2.7' do
27 self.release = '2.7.1'
28 self.base_url = "https://fishshell.com/docs/#{version}/"
29 end
30
20 version '2.6' do31 version '2.6' do
21 self.release = '2.6.0'32 self.release = '2.6.0'
22 self.base_url = "https://fishshell.com/docs/#{version}/"33 self.base_url = "https://fishshell.com/docs/#{version}/"
@@ -41,5 +52,10 @@ module Docs · 18 unmodified lines
41 self.release = '2.2.0'52 self.release = '2.2.0'
42 self.base_url = "https://fishshell.com/docs/#{version}/"53 self.base_url = "https://fishshell.com/docs/#{version}/"
43 end54 end
55
56 def get_latest_version(opts)
57 doc = fetch_doc('http://fishshell.com/docs/current/index.html', opts)
58 doc.at_css('#toc-index').content.scan(/([0-9.]+)/)[0][0]
59 end
44 end60 end
45end61end
lib/docs/scrapers/flow.rb+7−3
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Flow < UrlScraper2 class Flow < UrlScraper
3 self.type = 'flow'3 self.type = 'simple'
4 self.release = '0.56.0'4 self.release = '0.85.0'
5 self.base_url = 'https://flow.org/en/docs/'5 self.base_url = 'https://flow.org/en/docs/'
6 self.links = {6 self.links = {
7 home: 'https://flow.org/',7 home: 'https://flow.org/',
@@ -16,7 +16,11 @@ module Docs · 8 unmodified lines
16 16
17 options[:attribution] = <<-HTML17 options[:attribution] = <<-HTML
18 &copy; 2013&ndash;present Facebook Inc.<br>18 &copy; 2013&ndash;present Facebook Inc.<br>
19 Licensed under the BSD License.19 Licensed under the MIT License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 get_npm_version('flow-bin', opts)
24 end
21 end25 end
22end26end
lib/docs/scrapers/git.rb+7−2
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Git < UrlScraper2 class Git < UrlScraper
3 self.type = 'git'3 self.type = 'git'
4 self.release = '2.14.1'4 self.release = '2.19.1'
5 self.base_url = 'https://git-scm.com/docs'5 self.base_url = 'https://git-scm.com/docs'
6 self.initial_paths = %w(/git.html)6 self.initial_paths = %w(/git.html)
7 self.links = {7 self.links = {
@@ -16,8 +16,13 @@ module Docs · 8 unmodified lines
16 options[:skip] = %w(/howto-index.html)16 options[:skip] = %w(/howto-index.html)
17 17
18 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
19 &copy; 2005&ndash;2017 Linus Torvalds and others<br>19 &copy; 2005&ndash;2018 Linus Torvalds and others<br>
20 Licensed under the GNU General Public License version 2.20 Licensed under the GNU General Public License version 2.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 doc = fetch_doc('https://git-scm.com/', opts)
25 doc.at_css('.version').content.strip
26 end
22 end27 end
23end28end
lib/docs/scrapers/gnu.rb+1−1
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Gnu < FileScraper2 class Gnu < FileScraper
3 self.type = 'gnu'3 self.type = 'simple'
4 self.root_path = 'index.html'4 self.root_path = 'index.html'
5 self.abstract = 'true'5 self.abstract = 'true'
6 6
lib/docs/scrapers/gnu/gcc.rb+8−10
@@ -47,14 +47,12 @@ module Docs · 46 unmodified lines
47 }47 }
48 48
49 version '7' do49 version '7' do
50 self.release = '7.2.0'50 self.release = '7.3.0'
51 self.dir = '/Users/Thibaut/DevDocs/Docs/gcc7'
52 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"51 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
53 end52 end
54 53
55 version '7 CPP' do54 version '7 CPP' do
56 self.release = '7.2.0'55 self.release = '7.3.0'
57 self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp7'
58 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"56 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
59 57
60 options[:replace_paths] = CPP_PATHS58 options[:replace_paths] = CPP_PATHS
@@ -62,7 +60,6 @@ module Docs · 1 unmodified line
62 60
63 version '6' do61 version '6' do
64 self.release = '6.4.0'62 self.release = '6.4.0'
65 self.dir = '/Users/Thibaut/DevDocs/Docs/gcc6'
66 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"63 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
67 64
68 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'65 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -70,7 +67,6 @@ module Docs · 1 unmodified line
70 67
71 version '6 CPP' do68 version '6 CPP' do
72 self.release = '6.4.0'69 self.release = '6.4.0'
73 self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp6'
74 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"70 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
75 71
76 options[:replace_paths] = CPP_PATHS72 options[:replace_paths] = CPP_PATHS
@@ -78,7 +74,6 @@ module Docs · 1 unmodified line
78 74
79 version '5' do75 version '5' do
80 self.release = '5.4.0'76 self.release = '5.4.0'
81 self.dir = '/Users/Thibaut/DevDocs/Docs/gcc5'
82 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"77 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
83 78
84 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'79 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -86,7 +81,6 @@ module Docs · 1 unmodified line
86 81
87 version '5 CPP' do82 version '5 CPP' do
88 self.release = '5.4.0'83 self.release = '5.4.0'
89 self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp5'
90 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"84 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
91 85
92 options[:replace_paths] = CPP_PATHS86 options[:replace_paths] = CPP_PATHS
@@ -94,7 +88,6 @@ module Docs · 1 unmodified line
94 88
95 version '4' do89 version '4' do
96 self.release = '4.9.3'90 self.release = '4.9.3'
97 self.dir = '/Users/Thibaut/DevDocs/Docs/gcc4'
98 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"91 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/"
99 92
100 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'93 options[:root_title] = 'Using the GNU Compiler Collection (GCC)'
@@ -102,10 +95,15 @@ module Docs · 1 unmodified line
102 95
103 version '4 CPP' do96 version '4 CPP' do
104 self.release = '4.9.3'97 self.release = '4.9.3'
105 self.dir = '/Users/Thibaut/DevDocs/Docs/gcpp4'
106 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"98 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/"
107 99
108 options[:replace_paths] = CPP_PATHS100 options[:replace_paths] = CPP_PATHS
109 end101 end
102
103 def get_latest_version(opts)
104 doc = fetch_doc('https://gcc.gnu.org/onlinedocs/', opts)
105 label = doc.at_css('ul > li > ul > li > a').content.strip
106 label.scan(/([0-9.]+)/)[0][0]
107 end
110 end108 end
111end109end
lib/docs/scrapers/gnu/gnu_fortran.rb+7−5
@@ -7,27 +7,29 @@ module Docs · 6 unmodified lines
7 }7 }
8 8
9 version '7' do9 version '7' do
10 self.release = '7.2.0'10 self.release = '7.3.0'
11 self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran7'
12 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"11 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
13 end12 end
14 13
15 version '6' do14 version '6' do
16 self.release = '6.4.0'15 self.release = '6.4.0'
17 self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran6'
18 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"16 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
19 end17 end
20 18
21 version '5' do19 version '5' do
22 self.release = '5.4.0'20 self.release = '5.4.0'
23 self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran5'
24 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"21 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
25 end22 end
26 23
27 version '4' do24 version '4' do
28 self.release = '4.9.3'25 self.release = '4.9.3'
29 self.dir = '/Users/Thibaut/DevDocs/Docs/gfortran4'
30 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"26 self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
31 end27 end
28
29 def get_latest_version(opts)
30 doc = fetch_doc('https://gcc.gnu.org/onlinedocs/', opts)
31 label = doc.at_css('ul > li > ul > li > a').content.strip
32 label.scan(/([0-9.]+)/)[0][0]
33 end
32 end34 end
33end35end
lib/docs/scrapers/go.rb+11−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Go < UrlScraper2 class Go < UrlScraper
3 self.type = 'go'3 self.type = 'go'
4 self.release = '1.9.0'4 self.release = '1.12'
5 self.base_url = 'https://golang.org/pkg/'5 self.base_url = 'https://golang.org/pkg/'
6 self.links = {6 self.links = {
7 home: 'https://golang.org/',7 home: 'https://golang.org/',
@@ -13,12 +13,22 @@ module Docs · 5 unmodified lines
13 options[:trailing_slash] = true13 options[:trailing_slash] = true
14 options[:container] = '#page .container'14 options[:container] = '#page .container'
15 options[:skip] = %w(runtime/msan/)15 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
16 21
17 options[:attribution] = <<-HTML22 options[:attribution] = <<-HTML
18 &copy; Google, Inc.<br>23 &copy; Google, Inc.<br>
19 Licensed under the Creative Commons Attribution License 3.0.24 Licensed under the Creative Commons Attribution License 3.0.
20 HTML25 HTML
21 26
27 def get_latest_version(opts)
28 doc = fetch_doc('https://golang.org/project/', opts)
29 doc.at_css('#page ul > li > a').text[3..-1]
30 end
31
22 private32 private
23 33
24 def parse(response) # Hook here because Nokogori removes whitespace from textareas34 def parse(response) # Hook here because Nokogori removes whitespace from textareas
lib/docs/scrapers/godot.rb+12−2
@@ -17,20 +17,30 @@ module Docs · 16 unmodified lines
17 options[:attribution] = ->(filter) do17 options[:attribution] = ->(filter) do
18 if filter.subpath.start_with?('classes')18 if filter.subpath.start_with?('classes')
19 <<-HTML19 <<-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>
21 Licensed under the MIT License.21 Licensed under the MIT License.
22 HTML22 HTML
23 else23 else
24 <<-HTML24 <<-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>
26 Licensed under the Creative Commons Attribution Unported License v3.0.26 Licensed under the Creative Commons Attribution Unported License v3.0.
27 HTML27 HTML
28 end28 end
29 end29 end
30 30
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
31 version '2.1' do36 version '2.1' do
32 self.release = '2.1'37 self.release = '2.1'
33 self.base_url = "http://docs.godotengine.org/en/#{self.version}/"38 self.base_url = "http://docs.godotengine.org/en/#{self.version}/"
34 end39 end
40
41 def get_latest_version(opts)
42 doc = fetch_doc('https://docs.godotengine.org/', opts)
43 doc.at_css('.version').content.strip
44 end
35 end45 end
36end46end
lib/docs/scrapers/graphite.rb+26−0
@@ -0,0 +1,26 @@
1module Docs
2 class Graphite < UrlScraper
3 self.type = 'graphite'
4 self.release = '1.1.4'
5 self.base_url = 'https://graphite.readthedocs.io/en/latest/'
6 self.links = {
7 code: 'https://github.com/graphite-project/graphite-web'
8 }
9
10 html_filters.push 'graphite/entries', 'graphite/clean_html'
11
12 options[:container] = '.document > div'
13 options[:skip] = %w(releases.html who-is-using.html composer.html search.html py-modindex.html genindex.html)
14
15 options[:attribution] = <<-HTML
16 &copy; 2008&ndash;2012 Chris Davis<br>
17 &copy; 2011&ndash;2016 The Graphite Project<br>
18 Licensed under the Apache License, Version 2.0.
19 HTML
20
21 def get_latest_version(opts)
22 doc = fetch_doc('https://graphite.readthedocs.io/en/latest/releases.html', opts)
23 doc.at_css('#release-notes li > a').content
24 end
25 end
26end
lib/docs/scrapers/grunt.rb+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Grunt < UrlScraper2 class Grunt < UrlScraper
3 self.name = 'Grunt'3 self.name = 'Grunt'
4 self.type = 'grunt'4 self.type = 'simple'
5 self.release = '1.0.1'5 self.release = '1.0.1'
6 self.base_url = 'https://gruntjs.com/'6 self.base_url = 'https://gruntjs.com/'
7 self.root_path = 'getting-started'7 self.root_path = 'getting-started'
@@ -26,5 +26,9 @@ module Docs · 18 unmodified lines
26 &copy; GruntJS Team<br>26 &copy; GruntJS Team<br>
27 Licensed under the MIT License.27 Licensed under the MIT License.
28 HTML28 HTML
29
30 def get_latest_version(opts)
31 get_npm_version('grunt-cli', opts)
32 end
29 end33 end
30end34end
lib/docs/scrapers/handlebars.rb+27−0
@@ -0,0 +1,27 @@
1module Docs
2 class Handlebars < UrlScraper
3 self.name = 'Handlebars.js'
4 self.slug = 'handlebars'
5 self.type = 'simple'
6 self.release = '4.0.12'
7 self.base_url = 'https://handlebarsjs.com/'
8 self.links = {
9 home: 'https://handlebarsjs.com/',
10 code: 'https://github.com/wycats/handlebars.js/'
11 }
12
13 html_filters.push 'handlebars/entries', 'handlebars/clean_html', 'title'
14
15 options[:container] = '#contents'
16 options[:root_title] = 'Handlebars.js'
17
18 options[:attribution] = <<-HTML
19 &copy; 2011&ndash;2017 by Yehuda Katz<br>
20 Licensed under the MIT License.
21 HTML
22
23 def get_latest_version(opts)
24 get_npm_version('handlebars', opts)
25 end
26 end
27end
lib/docs/scrapers/haskell.rb+9−2
@@ -10,7 +10,7 @@ module Docs · 9 unmodified lines
10 10
11 html_filters.push 'haskell/entries', 'haskell/clean_html'11 html_filters.push 'haskell/entries', 'haskell/clean_html'
12 12
13 options[:container] = ->(filter) { filter.subpath.start_with?('users_guide') ? '.body' : '#content' }13 options[:container] = ->(filter) {filter.subpath.start_with?('users_guide') ? '.body' : '#content'}
14 14
15 options[:only_patterns] = [/\Alibraries\//, /\Ausers_guide\//]15 options[:only_patterns] = [/\Alibraries\//, /\Ausers_guide\//]
16 options[:skip_patterns] = [16 options[:skip_patterns] = [
@@ -57,7 +57,7 @@ module Docs · 40 unmodified lines
57 end57 end
58 58
59 version '8' do59 version '8' do
60 self.release = '8.2.1'60 self.release = '8.6.1'
61 self.base_url = "https://downloads.haskell.org/~ghc/#{release}/docs/html/"61 self.base_url = "https://downloads.haskell.org/~ghc/#{release}/docs/html/"
62 end62 end
63 63
@@ -68,5 +68,12 @@ module Docs · 4 unmodified lines
68 68
69 options[:only_patterns] = [/\Alibraries\//]69 options[:only_patterns] = [/\Alibraries\//]
70 end70 end
71
72 def get_latest_version(opts)
73 doc = fetch_doc('https://downloads.haskell.org/~ghc/latest/docs/html/', opts)
74 links = doc.css('a').to_a
75 versions = links.map {|link| link['href'].scan(/ghc-([0-9.]+)/)}
76 versions.find {|version| !version.empty?}[0][0]
77 end
71 end78 end
72end79end
lib/docs/scrapers/haxe.rb+32−14
@@ -1,58 +1,76 @@
1module Docs1module Docs
2 class Haxe < UrlScraper2 class Haxe < UrlScraper
3 self.name = 'Haxe'3 self.name = 'Haxe'
4 self.type = 'haxe'4 self.type = 'simple'
5 self.release = '3.4.1'5 self.release = '3.4.7'
6 self.base_url = 'http://api.haxe.org/'6 self.base_url = 'https://api.haxe.org/'
7 7
8 html_filters.push 'haxe/clean_html', 'haxe/entries'8 html_filters.push 'haxe/clean_html', 'haxe/entries'
9 9
10 options[:container] = '.span9'10 options[:container] = '.span9'
11 11
12 options[:attribution] = <<-HTML12 options[:attribution] = <<-HTML
13 &copy; 2005&ndash;2016 Haxe Foundation<br>13 &copy; 2005&ndash;2018 Haxe Foundation<br>
14 Licensed under a MIT license.14 Licensed under a MIT license.
15 HTML15 HTML
16 16
17 version do17 version do
18 self.links = {18 self.links = {
19 home: 'http://haxe.org',19 home: 'https://haxe.org',
20 code: 'https://github.com/HaxeFoundation/haxe'20 code: 'https://github.com/HaxeFoundation/haxe'
21 }21 }
22 22
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]
24 end24 end
25 25
26 version 'C++' do26 version 'C++' do
27 self.base_url = 'http://api.haxe.org/cpp/'27 self.base_url = 'https://api.haxe.org/cpp/'
28 end28 end
29 29
30 version 'C#' do30 version 'C#' do
31 self.base_url = 'http://api.haxe.org/cs/'31 self.base_url = 'https://api.haxe.org/cs/'
32 end32 end
33 33
34 version 'Flash' do34 version 'Flash' do
35 self.base_url = 'http://api.haxe.org/flash/'35 self.base_url = 'https://api.haxe.org/flash/'
36 end36 end
37 37
38 version 'Java' do38 version 'Java' do
39 self.base_url = 'http://api.haxe.org/java/'39 self.base_url = 'https://api.haxe.org/java/'
40 end40 end
41 41
42 version 'JavaScript' do42 version 'JavaScript' do
43 self.base_url = 'http://api.haxe.org/js/'43 self.base_url = 'https://api.haxe.org/js/'
44 end44 end
45 45
46 version 'Neko' do46 version 'Neko' do
47 self.base_url = 'http://api.haxe.org/neko/'47 self.base_url = 'https://api.haxe.org/neko/'
48 end48 end
49 49
50 version 'PHP' do50 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/'
52 end64 end
53 65
54 version 'Python' do66 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 /, '')
56 end74 end
57 end75 end
58end76end
lib/docs/scrapers/homebrew.rb+27−0
@@ -0,0 +1,27 @@
1module 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
27end
lib/docs/scrapers/http.rb+3−0
@@ -7,10 +7,13 @@ module Docs · 6 unmodified lines
7 7
8 html_filters.push 'http/clean_html', 'http/entries', 'title'8 html_filters.push 'http/clean_html', 'http/entries', 'title'
9 9
10 options[:mdn_tag] = 'HTTP'
11
10 options[:root_title] = 'HTTP'12 options[:root_title] = 'HTTP'
11 options[:title] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? false : filter.default_title }13 options[:title] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? false : filter.default_title }
12 options[:container] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? '.content' : nil }14 options[:container] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? '.content' : nil }
13 options[:skip_links] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? true : false }15 options[:skip_links] = ->(filter) { filter.current_url.host == 'tools.ietf.org' ? true : false }
16 options[:replace_paths] = { '/Access_control_CORS' => '/CORS' }
14 options[:fix_urls] = ->(url) do17 options[:fix_urls] = ->(url) do
15 url.sub! %r{(Status/\d\d\d)_[A-Z].+}, '\1'18 url.sub! %r{(Status/\d\d\d)_[A-Z].+}, '\1'
16 url19 url
lib/docs/scrapers/immutable.rb+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Immutable < UrlScraper2 class Immutable < UrlScraper
3 self.name = 'Immutable.js'3 self.name = 'Immutable.js'
4 self.slug = 'immutable'4 self.slug = 'immutable'
5 self.type = 'immutable'5 self.type = 'simple'
6 self.release = '3.8.1'6 self.release = '3.8.1'
7 self.base_url = 'https://facebook.github.io/immutable-js/docs/'7 self.base_url = 'https://facebook.github.io/immutable-js/docs/'
8 self.links = {8 self.links = {
@@ -54,5 +54,9 @@ module Docs · 45 unmodified lines
54 JS54 JS
55 capybara.html55 capybara.html
56 end56 end
57
58 def get_latest_version(opts)
59 get_npm_version('immutable', opts)
60 end
57 end61 end
58end62end
lib/docs/scrapers/influxdata.rb+7−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Influxdata < UrlScraper2 class Influxdata < UrlScraper
3 self.name = 'InfluxData'3 self.name = 'InfluxData'
4 self.type = 'influxdata'4 self.type = 'simple'
5 self.release = '1.3'5 self.release = '1.3'
6 self.base_url = 'https://docs.influxdata.com/'6 self.base_url = 'https://docs.influxdata.com/'
7 self.links = {7 self.links = {
@@ -46,5 +46,11 @@ module Docs · 38 unmodified lines
46 &copy; 2015 InfluxData, Inc.<br>46 &copy; 2015 InfluxData, Inc.<br>
47 Licensed under the MIT license.47 Licensed under the MIT license.
48 HTML48 HTML
49
50 def get_latest_version(opts)
51 doc = fetch_doc('https://docs.influxdata.com/influxdb/', opts)
52 label = doc.at_css('.navbar--current-product').content.strip
53 label.scan(/([0-9.]+)/)[0][0]
54 end
49 end55 end
50end56end
lib/docs/scrapers/jasmine.rb+7−3
@@ -1,8 +1,8 @@
1module Docs1module Docs
2 class Jasmine < UrlScraper2 class Jasmine < UrlScraper
3 self.type = 'jasmine'3 self.type = 'simple'
4 self.release = '2.8.0'4 self.release = '3.3.0'
5 self.base_url = 'https://jasmine.github.io/api/2.7/'5 self.base_url = 'https://jasmine.github.io/api/3.2/'
6 self.root_path = 'index.html'6 self.root_path = 'index.html'
7 self.links = {7 self.links = {
8 home: 'https://jasmine.github.io/',8 home: 'https://jasmine.github.io/',
@@ -17,5 +17,9 @@ module Docs · 8 unmodified lines
17 &copy; 2008&ndash;2017 Pivotal Labs<br>17 &copy; 2008&ndash;2017 Pivotal Labs<br>
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 get_latest_github_release('jasmine', 'jasmine', opts)
23 end
20 end24 end
21end25end
lib/docs/scrapers/jekyll.rb+37−0
@@ -0,0 +1,37 @@
1module 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
37end
lib/docs/scrapers/jest.rb+9−4
@@ -1,9 +1,9 @@
1module Docs1module Docs
2 class Jest < UrlScraper2 class Jest < UrlScraper
3 self.type = 'jest'3 self.type = 'simple'
4 self.release = '21.2.1'4 self.release = '23.5.0'
5 self.base_url = 'https://facebook.github.io/jest/docs/en/'5 self.base_url = 'https://jestjs.io/docs/en/'
6 self.root_path = 'getting-started.html'6 self.root_path = 'getting-started'
7 self.links = {7 self.links = {
8 home: 'https://facebook.github.io/jest/',8 home: 'https://facebook.github.io/jest/',
9 code: 'https://github.com/facebook/jest'9 code: 'https://github.com/facebook/jest'
@@ -17,5 +17,10 @@ module Docs · 7 unmodified lines
17 &copy; 2014&ndash;present Facebook Inc.<br>17 &copy; 2014&ndash;present Facebook Inc.<br>
18 Licensed under the BSD License.18 Licensed under the BSD License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 doc = fetch_doc('https://jestjs.io/docs/en/getting-started', opts)
23 doc.at_css('header > a > h3').content
24 end
20 end25 end
21end26end
lib/docs/scrapers/jquery/jquery_core.rb+9−1
@@ -1,9 +1,13 @@
1module Docs1module Docs
2 class JqueryCore < Jquery2 class JqueryCore < Jquery
3 self.name = 'jQuery'3 self.name = 'jQuery'
4 self.release = 'up to 3.2.1'4 self.release = '3.3.1'
5 self.base_url = 'https://api.jquery.com/'5 self.base_url = 'https://api.jquery.com/'
6 self.initial_paths = %w(/index/index)6 self.initial_paths = %w(/index/index)
7 self.links = {
8 home: 'https://jquery.com/',
9 code: 'https://github.com/jquery/jquery'
10 }
7 11
8 html_filters.insert_before 'jquery/clean_html', 'jquery_core/entries'12 html_filters.insert_before 'jquery/clean_html', 'jquery_core/entries'
9 13
@@ -18,5 +22,9 @@ module Docs · 8 unmodified lines
18 /Selectors\/odd/i,22 /Selectors\/odd/i,
19 /index/i23 /index/i
20 ]24 ]
25
26 def get_latest_version(opts)
27 get_npm_version('jquery', opts)
28 end
21 end29 end
22end30end
lib/docs/scrapers/jquery/jquery_mobile.rb+5−0
@@ -16,5 +16,10 @@ module Docs · 15 unmodified lines
16 options[:fix_urls] = ->(url) do16 options[:fix_urls] = ->(url) do
17 url.sub! 'http://api.jquerymobile.com/', 'https://api.jquerymobile.com/'17 url.sub! 'http://api.jquerymobile.com/', 'https://api.jquerymobile.com/'
18 end18 end
19
20 def get_latest_version(opts)
21 doc = fetch_doc('https://jquerymobile.com/', opts)
22 doc.at_css('.download-box > .download-option:last-child > span').content.sub(/Version /, '')
23 end
19 end24 end
20end25end
lib/docs/scrapers/jquery/jquery_ui.rb+4−0
@@ -15,5 +15,9 @@ module Docs · 14 unmodified lines
15 options[:fix_urls] = ->(url) do15 options[:fix_urls] = ->(url) do
16 url.sub! 'http://api.jqueryui.com/', 'https://api.jqueryui.com/'16 url.sub! 'http://api.jqueryui.com/', 'https://api.jqueryui.com/'
17 end17 end
18
19 def get_latest_version(opts)
20 get_npm_version('jquery-ui', opts)
21 end
18 end22 end
19end23end
lib/docs/scrapers/jsdoc.rb+29−0
@@ -0,0 +1,29 @@
1module 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
29end
lib/docs/scrapers/julia.rb+28−4
@@ -8,13 +8,33 @@ module Docs · 7 unmodified lines
8 options[:only_patterns] = [/\Amanual\//, /\Astdlib\//]8 options[:only_patterns] = [/\Amanual\//, /\Astdlib\//]
9 9
10 options[:attribution] = <<-HTML10 options[:attribution] = <<-HTML
11 &copy; 2009&ndash;2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors<br>11 &copy; 2009&ndash;2018 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors<br>
12 Licensed under the MIT License.12 Licensed under the MIT License.
13 HTML13 HTML
14 14
15 version '1.0' do
16 self.release = '1.0.1'
17 self.base_url = "https://docs.julialang.org/en/v#{release}/"
18 self.type = 'julia'
19
20 html_filters.push 'julia/entries', 'julia/clean_html'
21
22 options[:container] = '#docs'
23 end
24
25 version '0.7' do
26 self.release = '0.7.0'
27 self.base_url = "https://docs.julialang.org/en/v#{release}/"
28 self.type = 'julia'
29
30 html_filters.push 'julia/entries', 'julia/clean_html'
31
32 options[:container] = '#docs'
33 end
34
15 version '0.6' do35 version '0.6' do
16 self.release = '0.6.0'36 self.release = '0.6.2'
17 self.base_url = 'https://docs.julialang.org/en/release-0.6/'37 self.base_url = "https://docs.julialang.org/en/v#{release}/"
18 self.type = 'julia'38 self.type = 'julia'
19 39
20 html_filters.push 'julia/entries', 'julia/clean_html'40 html_filters.push 'julia/entries', 'julia/clean_html'
@@ -24,10 +44,14 @@ module Docs · 3 unmodified lines
24 44
25 version '0.5' do45 version '0.5' do
26 self.release = '0.5.2'46 self.release = '0.5.2'
27 self.base_url = 'https://docs.julialang.org/en/release-0.5/'47 self.base_url = "https://docs.julialang.org/en/v#{release}/"
28 self.type = 'sphinx_simple'48 self.type = 'sphinx_simple'
29 49
30 html_filters.push 'julia/entries_sphinx', 'julia/clean_html_sphinx', 'sphinx/clean_html'50 html_filters.push 'julia/entries_sphinx', 'julia/clean_html_sphinx', 'sphinx/clean_html'
31 end51 end
52
53 def get_latest_version(opts)
54 get_latest_github_release('JuliaLang', 'julia', opts)
55 end
32 end56 end
33end57end
lib/docs/scrapers/knockout.rb+4−0
@@ -33,5 +33,9 @@ module Docs · 32 unmodified lines
33 &copy; Steven Sanderson, the Knockout.js team, and other contributors<br>33 &copy; Steven Sanderson, the Knockout.js team, and other contributors<br>
34 Licensed under the MIT License.34 Licensed under the MIT License.
35 HTML35 HTML
36
37 def get_latest_version(opts)
38 get_latest_github_release('knockout', 'knockout', opts)
39 end
36 end40 end
37end41end
lib/docs/scrapers/koa.rb+42−0
@@ -0,0 +1,42 @@
1# frozen_string_literal: true
2
3module 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
42end
lib/docs/scrapers/kotlin.rb+15−3
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Kotlin < UrlScraper2 class Kotlin < UrlScraper
3 self.type = 'kotlin'3 self.type = 'kotlin'
4 self.release = '1.1.4'4 self.release = '1.3.41'
5 self.base_url = 'https://kotlinlang.org/'5 self.base_url = 'https://kotlinlang.org/'
6 self.root_path = 'api/latest/jvm/stdlib/index.html'6 self.root_path = 'api/latest/jvm/stdlib/index.html'
7 self.links = {7 self.links = {
@@ -22,11 +22,23 @@ module Docs · 14 unmodified lines
22 docs/events.html22 docs/events.html
23 docs/resources.html23 docs/resources.html
24 docs/reference/grammar.html)24 docs/reference/grammar.html)
25 options[:replace_paths] = { 'api/latest/jvm/stdlib/' => 'api/latest/jvm/stdlib/index.html' }25 options[:replace_paths] = {
26 'api/latest/jvm/stdlib/' => 'api/latest/jvm/stdlib/index.html',
27 'docs/reference/coroutines.html' => 'docs/reference/coroutines-overview.html',
28 'api/latest/jvm/stdlib/kotlin/fold.html' => 'api/latest/jvm/stdlib/kotlin.collections/fold.html',
29 'api/latest/jvm/stdlib/kotlin/get-or-else.html' => 'api/latest/jvm/stdlib/kotlin.collections/get-or-else.html',
30 'api/latest/jvm/stdlib/kotlin/map.html' => 'api/latest/jvm/stdlib/kotlin.collections/map.html',
31 'docs/tutorials/native/targeting-multiple-platforms.html' => 'docs/tutorials/native/basic-kotlin-native-app.html',
32 'api/latest/jvm/stdlib/kotlin/-throwable/print-stack-trace.html' => 'api/latest/jvm/stdlib/kotlin/print-stack-trace.html',
33 }
26 34
27 options[:attribution] = <<-HTML35 options[:attribution] = <<-HTML
28 &copy; 2010&ndash;2017 JetBrains s.r.o.<br>36 &copy; 2010&ndash;2019 JetBrains s.r.o.<br>
29 Licensed under the Apache License, Version 2.0.37 Licensed under the Apache License, Version 2.0.
30 HTML38 HTML
39
40 def get_latest_version(opts)
41 get_latest_github_release('JetBrains', 'kotlin', opts)
42 end
31 end43 end
32end44end
lib/docs/scrapers/laravel.rb+31−1
@@ -29,8 +29,34 @@ module Docs · 28 unmodified lines
29 Laravel is a trademark of Taylor Otwell.29 Laravel is a trademark of Taylor Otwell.
30 HTML30 HTML
31 31
32 version '5.7' do
33 self.release = '5.7.7'
34 self.root_path = '/api/5.7/index.html'
35 self.initial_paths = %w(/docs/5.7/installation /api/5.7/classes.html)
36
37 options[:only_patterns] = [%r{\A/api/5\.7/}, %r{\A/docs/5\.7/}]
38
39 options[:fix_urls] = ->(url) do
40 url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\/(?!\d)}, "#{Laravel.base_url}/docs/5.7/"
41 url
42 end
43 end
44
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
32 version '5.5' do58 version '5.5' do
33 self.release = '5.5.0'59 self.release = '5.5.28'
34 self.root_path = '/api/5.5/index.html'60 self.root_path = '/api/5.5/index.html'
35 self.initial_paths = %w(/docs/5.5/installation /api/5.5/classes.html)61 self.initial_paths = %w(/docs/5.5/installation /api/5.5/classes.html)
36 62
@@ -107,5 +133,9 @@ module Docs · 70 unmodified lines
107 url133 url
108 end134 end
109 end135 end
136
137 def get_latest_version(opts)
138 get_latest_github_release('laravel', 'laravel', opts)
139 end
110 end140 end
111end141end
lib/docs/scrapers/leaflet.rb+58−0
@@ -0,0 +1,58 @@
1module 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
58end
lib/docs/scrapers/less.rb+7−1
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Less < UrlScraper2 class Less < UrlScraper
3 self.type = 'less'3 self.type = 'simple'
4 self.release = '2.7.2'4 self.release = '2.7.2'
5 self.base_url = 'http://lesscss.org'5 self.base_url = 'http://lesscss.org'
6 self.root_path = '/features'6 self.root_path = '/features'
@@ -21,5 +21,11 @@ module Docs · 14 unmodified lines
21 &copy; 2009&ndash;2016 The Core Less Team<br>21 &copy; 2009&ndash;2016 The Core Less Team<br>
22 Licensed under the Creative Commons Attribution License 3.0.22 Licensed under the Creative Commons Attribution License 3.0.
23 HTML23 HTML
24
25 def get_latest_version(opts)
26 doc = fetch_doc('http://lesscss.org/features/', opts)
27 label = doc.at_css('.footer-links > li').content
28 label.scan(/([0-9.]+)/)[0][0]
29 end
24 end30 end
25end31end
lib/docs/scrapers/liquid.rb+5−0
@@ -19,5 +19,10 @@ module Docs · 18 unmodified lines
19 &copy; 2005, 2006 Tobias Luetke<br>19 &copy; 2005, 2006 Tobias Luetke<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 tags = get_github_tags('Shopify', 'liquid', opts)
25 tags[0]['name'][1..-1]
26 end
22 end27 end
23end28end
lib/docs/scrapers/lodash.rb+8−3
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Lodash < UrlScraper2 class Lodash < UrlScraper
3 self.name = 'lodash'3 self.name = 'lodash'
4 self.slug = 'lodash'4 self.slug = 'lodash'
5 self.type = 'lodash'5 self.type = 'simple'
6 self.links = {6 self.links = {
7 home: 'https://lodash.com/',7 home: 'https://lodash.com/',
8 code: 'https://github.com/lodash/lodash/'8 code: 'https://github.com/lodash/lodash/'
@@ -14,12 +14,12 @@ module Docs · 5 unmodified lines
14 options[:skip_links] = true14 options[:skip_links] = true
15 15
16 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
17 &copy; 2012&ndash;2016 The Dojo Foundation<br>17 &copy; JS Foundation and other contributors<br>
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20 20
21 version '4' do21 version '4' do
22 self.release = '4.17.4'22 self.release = '4.17.10'
23 self.base_url = "https://lodash.com/docs/#{release}"23 self.base_url = "https://lodash.com/docs/#{release}"
24 end24 end
25 25
@@ -32,5 +32,10 @@ module Docs · 6 unmodified lines
32 self.release = '2.4.2'32 self.release = '2.4.2'
33 self.base_url = "https://lodash.com/docs/#{release}"33 self.base_url = "https://lodash.com/docs/#{release}"
34 end34 end
35
36 def get_latest_version(opts)
37 doc = fetch_doc('https://lodash.com/docs/', opts)
38 doc.at_css('#version > option[selected]').content
39 end
35 end40 end
36end41end
lib/docs/scrapers/love.rb+5−0
@@ -39,5 +39,10 @@ module Docs · 38 unmodified lines
39 &copy; 2006&ndash;2016 L&Ouml;VE Development Team<br>39 &copy; 2006&ndash;2016 L&Ouml;VE Development Team<br>
40 Licensed under the GNU Free Documentation License, Version 1.3.40 Licensed under the GNU Free Documentation License, Version 1.3.
41 HTML41 HTML
42
43 def get_latest_version(opts)
44 doc = fetch_doc('https://love2d.org/wiki/Version_History', opts)
45 doc.at_css('#mw-content-text table a').content
46 end
42 end47 end
43end48end
lib/docs/scrapers/lua.rb+5−0
@@ -26,5 +26,10 @@ module Docs · 25 unmodified lines
26 self.release = '5.1.5'26 self.release = '5.1.5'
27 self.base_url = 'https://www.lua.org/manual/5.1/'27 self.base_url = 'https://www.lua.org/manual/5.1/'
28 end28 end
29
30 def get_latest_version(opts)
31 doc = fetch_doc('https://www.lua.org/manual/', opts)
32 doc.at_css('p.menubar > a').content
33 end
29 end34 end
30end35end
lib/docs/scrapers/marionette.rb+13−2
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Marionette < UrlScraper2 class Marionette < UrlScraper
3 self.name = 'Marionette.js'3 self.name = 'Marionette.js'
4 self.slug = 'marionette'4 self.slug = 'marionette'
5 self.type = 'marionette'5 self.type = 'simple'
6 self.root_path = 'index'6 self.root_path = 'index'
7 self.links = {7 self.links = {
8 home: 'https://marionettejs.com/',8 home: 'https://marionettejs.com/',
@@ -18,8 +18,15 @@ module Docs · 9 unmodified lines
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20 20
21 version '4' do
22 self.release = '4.0.0'
23 self.base_url = "https://marionettejs.com/docs/v#{release}/"
24
25 html_filters.push 'marionette/entries_v3'
26 end
27
21 version '3' do28 version '3' do
22 self.release = '3.4.3'29 self.release = '3.5.1'
23 self.base_url = "https://marionettejs.com/docs/v#{release}/"30 self.base_url = "https://marionettejs.com/docs/v#{release}/"
24 31
25 html_filters.push 'marionette/entries_v3'32 html_filters.push 'marionette/entries_v3'
@@ -31,5 +38,9 @@ module Docs · 5 unmodified lines
31 38
32 html_filters.push 'marionette/entries_v2'39 html_filters.push 'marionette/entries_v2'
33 end40 end
41
42 def get_latest_version(opts)
43 get_npm_version('backbone.marionette', opts)
44 end
34 end45 end
35end46end
lib/docs/scrapers/markdown.rb+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Markdown < UrlScraper2 class Markdown < UrlScraper
3 self.name = 'Markdown'3 self.name = 'Markdown'
4 self.type = 'markdown'4 self.type = 'simple'
5 self.base_url = 'http://daringfireball.net/projects/markdown/syntax'5 self.base_url = 'http://daringfireball.net/projects/markdown/syntax'
6 6
7 html_filters.push 'markdown/clean_html', 'markdown/entries'7 html_filters.push 'markdown/clean_html', 'markdown/entries'
@@ -13,5 +13,9 @@ module Docs · 5 unmodified lines
13 &copy; 2004 John Gruber<br>13 &copy; 2004 John Gruber<br>
14 Licensed under the BSD License.14 Licensed under the BSD License.
15 HTML15 HTML
16
17 def get_latest_version(opts)
18 '1.0.0'
19 end
16 end20 end
17end21end
lib/docs/scrapers/matplotlib.rb+48−8
@@ -6,7 +6,7 @@ module Docs · 5 unmodified lines
6 self.type = 'sphinx'6 self.type = 'sphinx'
7 self.root_path = 'index.html'7 self.root_path = 'index.html'
8 self.links = {8 self.links = {
9 home: 'http://matplotlib.org/',9 home: 'https://matplotlib.org/',
10 code: 'https://github.com/matplotlib/matplotlib'10 code: 'https://github.com/matplotlib/matplotlib'
11 }11 }
12 12
@@ -16,26 +16,66 @@ module Docs · 3 unmodified lines
16 options[:skip] = %w(api_changes.html tutorial.html faq.html)16 options[:skip] = %w(api_changes.html tutorial.html faq.html)
17 17
18 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
19 &copy; 2012&ndash;2017 Matplotlib Development Team. All rights reserved.<br>19 &copy; 2012&ndash;2018 Matplotlib Development Team. All rights reserved.<br>
20 Licensed under the Matplotlib License Agreement.20 Licensed under the Matplotlib License Agreement.
21 HTML21 HTML
22 22
23 version '3.1' do
24 self.release = '3.1.1'
25 self.base_urls = [
26 "https://matplotlib.org/#{release}/api/",
27 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
28 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
29 ]
30 end
31
32 version '3.0' do
33 self.release = '3.0.0'
34 self.base_urls = [
35 "https://matplotlib.org/#{release}/api/",
36 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
37 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
38 ]
39 end
40
41 version '2.2' do
42 self.release = '2.2.3'
43 self.base_urls = [
44 "https://matplotlib.org/#{release}/api/",
45 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
46 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
47 ]
48 end
49
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
23 version '2.0' do59 version '2.0' do
24 self.release = '2.0.2'60 self.release = '2.0.2'
25 self.base_urls = [61 self.base_urls = [
26 "http://matplotlib.org/#{release}/api/",62 "https://matplotlib.org/#{release}/api/",
27 "http://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",63 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
28 "http://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"64 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
29 ]65 ]
30 end66 end
31 67
32 version '1.5' do68 version '1.5' do
33 self.release = '1.5.3'69 self.release = '1.5.3'
34 self.base_urls = [70 self.base_urls = [
35 "http://matplotlib.org/#{release}/api/",71 "https://matplotlib.org/#{release}/api/",
36 "http://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",72 "https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/",
37 "http://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"73 "https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
38 ]74 ]
39 end75 end
76
77 def get_latest_version(opts)
78 get_latest_github_release('matplotlib', 'matplotlib', opts)
79 end
40 end80 end
41end81end
lib/docs/scrapers/mdn/css.rb+2−0
@@ -6,6 +6,8 @@ module Docs · 5 unmodified lines
6 6
7 html_filters.push 'css/clean_html', 'css/entries', 'title'7 html_filters.push 'css/clean_html', 'css/entries', 'title'
8 8
9 options[:mdn_tag] = 'CSS'
10
9 options[:root_title] = 'CSS'11 options[:root_title] = 'CSS'
10 12
11 options[:skip] = %w(/CSS3 /Media/Visual /paged_media /Media/TV /Media/Tactile)13 options[:skip] = %w(/CSS3 /Media/Visual /paged_media /Media/TV /Media/Tactile)
lib/docs/scrapers/mdn/dom.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
8 8
9 html_filters.push 'dom/clean_html', 'dom/entries', 'title'9 html_filters.push 'dom/clean_html', 'dom/entries', 'title'
10 10
11 options[:mdn_tag] = 'XSLT_Reference'
12
11 options[:root_title] = 'DOM'13 options[:root_title] = 'DOM'
12 14
13 options[:skip] = %w(15 options[:skip] = %w(
lib/docs/scrapers/mdn/dom_events.rb+4−0
@@ -1,5 +1,7 @@
1module Docs1module Docs
2 class DomEvents < Mdn2 class DomEvents < Mdn
3 prepend FixInternalUrlsBehavior
4
3 self.name = 'DOM Events'5 self.name = 'DOM Events'
4 self.slug = 'dom_events'6 self.slug = 'dom_events'
5 self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/Events'7 self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/Events'
@@ -7,6 +9,8 @@ module Docs · 1 unmodified line
7 html_filters.insert_after 'clean_html', 'dom_events/clean_html'9 html_filters.insert_after 'clean_html', 'dom_events/clean_html'
8 html_filters.push 'dom_events/entries', 'title'10 html_filters.push 'dom_events/entries', 'title'
9 11
12 options[:mdn_tag] = 'events'
13
10 options[:root_title] = 'DOM Events'14 options[:root_title] = 'DOM Events'
11 15
12 options[:skip] = %w(/MozOrientation)16 options[:skip] = %w(/MozOrientation)
lib/docs/scrapers/mdn/html.rb+2−0
@@ -7,6 +7,8 @@ module Docs · 6 unmodified lines
7 7
8 html_filters.push 'html/clean_html', 'html/entries', 'title'8 html_filters.push 'html/clean_html', 'html/entries', 'title'
9 9
10 options[:mdn_tag] = 'HTML'
11
10 options[:root_title] = 'HTML'12 options[:root_title] = 'HTML'
11 13
12 options[:title] = ->(filter) do14 options[:title] = ->(filter) do
lib/docs/scrapers/mdn/javascript.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
8 8
9 html_filters.push 'javascript/clean_html', 'javascript/entries', 'title'9 html_filters.push 'javascript/clean_html', 'javascript/entries', 'title'
10 10
11 options[:mdn_tag] = 'JavaScript'
12
11 options[:root_title] = 'JavaScript'13 options[:root_title] = 'JavaScript'
12 14
13 # Don't want15 # Don't want
lib/docs/scrapers/mdn/mdn.rb+7−1
@@ -9,6 +9,7 @@ module Docs · 8 unmodified lines
9 html_filters.push 'mdn/clean_html'9 html_filters.push 'mdn/clean_html'
10 text_filters.insert_before 'attribution', 'mdn/contribute_link'10 text_filters.insert_before 'attribution', 'mdn/contribute_link'
11 11
12 options[:rate_limit] = 200
12 options[:trailing_slash] = false13 options[:trailing_slash] = false
13 14
14 options[:skip_link] = ->(link) {15 options[:skip_link] = ->(link) {
@@ -16,10 +17,15 @@ module Docs · 1 unmodified line
16 }17 }
17 18
18 options[:attribution] = <<-HTML19 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>
20 Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.21 Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
21 HTML22 HTML
22 23
24 def get_latest_version(opts)
25 json = fetch_json("https://developer.mozilla.org/en-US/docs/feeds/json/tag/#{options[:mdn_tag]}", opts)
26 DateTime.parse(json[0]['pubdate']).to_time.to_i
27 end
28
23 private29 private
24 30
25 def process_response?(response)31 def process_response?(response)
lib/docs/scrapers/mdn/svg.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
8 8
9 html_filters.push 'svg/clean_html', 'svg/entries', 'title'9 html_filters.push 'svg/clean_html', 'svg/entries', 'title'
10 10
11 options[:mdn_tag] = 'XSLT_Reference'
12
11 options[:root_title] = 'SVG'13 options[:root_title] = 'SVG'
12 14
13 options[:title] = ->(filter) do15 options[:title] = ->(filter) do
lib/docs/scrapers/mdn/xslt_xpath.rb+2−0
@@ -8,6 +8,8 @@ module Docs · 7 unmodified lines
8 8
9 html_filters.push 'xslt_xpath/clean_html', 'xslt_xpath/entries', 'title'9 html_filters.push 'xslt_xpath/clean_html', 'xslt_xpath/entries', 'title'
10 10
11 options[:mdn_tag] = 'XSLT_Reference'
12
11 options[:root_title] = 'XSLT'13 options[:root_title] = 'XSLT'
12 14
13 options[:only_patterns] = [/\A\/XSLT/, /\A\/XPath/]15 options[:only_patterns] = [/\A\/XSLT/, /\A\/XPath/]
lib/docs/scrapers/meteor.rb+6−1
@@ -31,7 +31,7 @@ module Docs · 30 unmodified lines
31 HTML31 HTML
32 32
33 version '1.5' do33 version '1.5' do
34 self.release = '1.5.1'34 self.release = '1.5.2'
35 self.base_urls = ['https://docs.meteor.com/', 'https://guide.meteor.com/', 'http://blazejs.org/']35 self.base_urls = ['https://docs.meteor.com/', 'https://guide.meteor.com/', 'http://blazejs.org/']
36 end36 end
37 37
@@ -45,5 +45,10 @@ module Docs · 7 unmodified lines
45 self.base_urls = ['https://guide.meteor.com/v1.3/', "https://docs.meteor.com/v#{self.release}/"]45 self.base_urls = ['https://guide.meteor.com/v1.3/', "https://docs.meteor.com/v#{self.release}/"]
46 options[:fix_urls] = nil46 options[:fix_urls] = nil
47 end47 end
48
49 def get_latest_version(opts)
50 doc = fetch_doc('https://docs.meteor.com/#/full/', opts)
51 doc.at_css('select.version-select > option').content
52 end
48 end53 end
49end54end
lib/docs/scrapers/mocha.rb+7−3
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Mocha < UrlScraper2 class Mocha < UrlScraper
3 self.type = 'mocha'3 self.type = 'simple'
4 self.release = '3.5.0'4 self.release = '5.2.0'
5 self.base_url = 'https://mochajs.org/'5 self.base_url = 'https://mochajs.org/'
6 self.links = {6 self.links = {
7 home: 'https://mochajs.org/',7 home: 'https://mochajs.org/',
@@ -15,8 +15,12 @@ module Docs · 7 unmodified lines
15 options[:skip_links] = true15 options[:skip_links] = true
16 16
17 options[:attribution] = <<-HTML17 options[:attribution] = <<-HTML
18 &copy; 2016 JS Foundation and contributors<br>18 &copy; 2011&ndash;2018 JS Foundation and contributors<br>
19 Licensed under the Creative Commons Attribution 4.0 International License.19 Licensed under the Creative Commons Attribution 4.0 International License.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 get_npm_version('mocha', opts)
24 end
21 end25 end
22end26end
lib/docs/scrapers/modernizr.rb+4−0
@@ -15,5 +15,9 @@ module Docs · 14 unmodified lines
15 &copy; 2009&ndash;2017 The Modernizr team<br>15 &copy; 2009&ndash;2017 The Modernizr team<br>
16 Licensed under the MIT License.16 Licensed under the MIT License.
17 HTML17 HTML
18
19 def get_latest_version(opts)
20 get_npm_version('modernizr', opts)
21 end
18 end22 end
19end23end
lib/docs/scrapers/moment.rb+6−1
@@ -3,7 +3,7 @@ module Docs · 2 unmodified lines
3 self.name = 'Moment.js'3 self.name = 'Moment.js'
4 self.slug = 'moment'4 self.slug = 'moment'
5 self.type = 'moment'5 self.type = 'moment'
6 self.release = '2.18.1'6 self.release = '2.22.1'
7 self.base_url = 'http://momentjs.com'7 self.base_url = 'http://momentjs.com'
8 self.root_path = '/docs/'8 self.root_path = '/docs/'
9 self.initial_paths = %w(/guides/)9 self.initial_paths = %w(/guides/)
@@ -22,5 +22,10 @@ module Docs · 12 unmodified lines
22 &copy; JS Foundation and other contributors<br>22 &copy; JS Foundation and other contributors<br>
23 Licensed under the MIT License.23 Licensed under the MIT License.
24 HTML24 HTML
25
26 def get_latest_version(opts)
27 doc = fetch_doc('http://momentjs.com/', opts)
28 doc.at_css('.hero-title > h1 > span').content
29 end
25 end30 end
26end31end
lib/docs/scrapers/mongoose.rb+9−2
@@ -1,11 +1,12 @@
1module Docs1module Docs
2 class Mongoose < UrlScraper2 class Mongoose < UrlScraper
3 self.name = 'Mongoose'3 self.name = 'Mongoose'
4 self.type = 'mongoose'4 self.type = 'simple'
5 self.release = '4.12.0'5 self.release = '4.13.1'
6 self.base_url = 'http://mongoosejs.com/docs/'6 self.base_url = 'http://mongoosejs.com/docs/'
7 self.root_path = 'index.html'7 self.root_path = 'index.html'
8 self.initial_paths = %w(guide.html api.html)8 self.initial_paths = %w(guide.html api.html)
9 self.force_gzip = true
9 self.links = {10 self.links = {
10 home: 'http://mongoosejs.com/',11 home: 'http://mongoosejs.com/',
11 code: 'https://github.com/Automattic/mongoose'12 code: 'https://github.com/Automattic/mongoose'
@@ -25,5 +26,11 @@ module Docs · 13 unmodified lines
25 &copy; 2010 LearnBoost<br>26 &copy; 2010 LearnBoost<br>
26 Licensed under the MIT License.27 Licensed under the MIT License.
27 HTML28 HTML
29
30 def get_latest_version(opts)
31 doc = fetch_doc('https://mongoosejs.com/docs/', opts)
32 label = doc.at_css('.pure-menu-link').content.strip
33 label.sub(/Version /, '')
34 end
28 end35 end
29end36end
lib/docs/scrapers/nginx.rb+10−4
@@ -2,11 +2,11 @@ module Docs · 1 unmodified line
2 class Nginx < UrlScraper2 class Nginx < UrlScraper
3 self.name = 'nginx'3 self.name = 'nginx'
4 self.type = 'nginx'4 self.type = 'nginx'
5 self.release = '1.13.4'5 self.release = '1.17.2'
6 self.base_url = 'https://nginx.org/en/docs/'6 self.base_url = 'https://nginx.org/en/docs/'
7 self.links = {7 self.links = {
8 home: 'https://nginx.org/',8 home: 'https://nginx.org/',
9 code: 'http://hg.nginx.org/nginx'9 code: 'https://hg.nginx.org/nginx'
10 }10 }
11 11
12 html_filters.push 'nginx/clean_html', 'nginx/entries'12 html_filters.push 'nginx/clean_html', 'nginx/entries'
@@ -21,9 +21,15 @@ module Docs · 8 unmodified lines
21 options[:skip_patterns] = [/\/faq\//]21 options[:skip_patterns] = [/\/faq\//]
22 22
23 options[:attribution] = <<-HTML23 options[:attribution] = <<-HTML
24 &copy; 2002-2017 Igor Sysoev<br>24 &copy; 2002-2019 Igor Sysoev<br>
25 &copy; 2011-2017 Nginx, Inc.<br>25 &copy; 2011-2019 Nginx, Inc.<br>
26 Licensed under the BSD License.26 Licensed under the BSD License.
27 HTML27 HTML
28
29 def get_latest_version(opts)
30 doc = fetch_doc('https://nginx.org/en/download.html', opts)
31 table = doc.at_css('#content > table').inner_html
32 table.scan(/nginx-([0-9.]+)</)[0][0]
33 end
28 end34 end
29end35end
lib/docs/scrapers/nginx_lua_module.rb+12−3
@@ -2,8 +2,11 @@ module Docs · 1 unmodified line
2 class NginxLuaModule < Github2 class NginxLuaModule < Github
3 self.name = 'nginx / Lua Module'3 self.name = 'nginx / Lua Module'
4 self.slug = 'nginx_lua_module'4 self.slug = 'nginx_lua_module'
5 self.release = '0.10.8'5 self.release = '0.10.13'
6 self.base_url = "https://github.com/openresty/lua-nginx-module/tree/v#{self.release}/"6 self.base_url = "https://github.com/openresty/lua-nginx-module/tree/v#{self.release}/"
7 self.links = {
8 code: 'https://github.com/openresty/lua-nginx-module'
9 }
7 10
8 html_filters.push 'nginx_lua_module/clean_html', 'nginx_lua_module/entries', 'title'11 html_filters.push 'nginx_lua_module/clean_html', 'nginx_lua_module/entries', 'title'
9 12
@@ -11,9 +14,15 @@ module Docs · 1 unmodified line
11 options[:container] = '#readme > article'14 options[:container] = '#readme > article'
12 15
13 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
14 &copy; 2009&ndash;2016 Xiaozhe Wang (chaoslawful)<br>17 &copy; 2009&ndash;2017 Xiaozhe Wang (chaoslawful)<br>
15 &copy; 2009&ndash;2017 Yichun "agentzh" Zhang (章亦春), OpenResty Inc.<br>18 &copy; 2009&ndash;2018 Yichun "agentzh" Zhang (章亦春), OpenResty Inc.<br>
16 Licensed under the BSD License.19 Licensed under the BSD License.
17 HTML20 HTML
21
22 def get_latest_version(opts)
23 tags = get_github_tags('openresty', 'lua-nginx-module', opts)
24 tag = tags.find {|tag| !tag['name'].include?('rc')}
25 tag['name'][1..-1]
26 end
18 end27 end
19end28end
lib/docs/scrapers/nim.rb+8−3
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Nim < UrlScraper2 class Nim < UrlScraper
3 self.type = 'nim'3 self.type = 'simple'
4 self.release = '0.17.0'4 self.release = '0.19.0'
5 self.base_url = 'https://nim-lang.org/docs/'5 self.base_url = 'https://nim-lang.org/docs/'
6 self.root_path = 'overview.html'6 self.root_path = 'overview.html'
7 self.links = {7 self.links = {
@@ -14,8 +14,13 @@ module Docs · 6 unmodified lines
14 options[:skip] = %w(theindex.html docgen.txt)14 options[:skip] = %w(theindex.html docgen.txt)
15 15
16 options[:attribution] = <<-HTML16 options[:attribution] = <<-HTML
17 &copy; 2006&ndash;2017 Andreas Rumpf<br>17 &copy; 2006&ndash;2018 Andreas Rumpf<br>
18 Licensed under the MIT License.18 Licensed under the MIT License.
19 HTML19 HTML
20
21 def get_latest_version(opts)
22 doc = fetch_doc('https://nim-lang.org/docs/overview.html', opts)
23 doc.at_css('.container > .docinfo > tbody > tr:last-child > td').content.strip
24 end
20 end25 end
21end26end
lib/docs/scrapers/node.rb+18−3
@@ -23,18 +23,33 @@ module Docs · 22 unmodified lines
23 HTML23 HTML
24 24
25 version do25 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'
27 self.base_url = 'https://nodejs.org/dist/latest-v8.x/docs/api/'37 self.base_url = 'https://nodejs.org/dist/latest-v8.x/docs/api/'
28 end38 end
29 39
30 version '6 LTS' do40 version '6 LTS' do
31 self.release = '6.11.4'41 self.release = '6.14.4'
32 self.base_url = 'https://nodejs.org/dist/latest-v6.x/docs/api/'42 self.base_url = 'https://nodejs.org/dist/latest-v6.x/docs/api/'
33 end43 end
34 44
35 version '4 LTS' do45 version '4 LTS' do
36 self.release = '4.8.4'46 self.release = '4.9.1'
37 self.base_url = 'https://nodejs.org/dist/latest-v4.x/docs/api/'47 self.base_url = 'https://nodejs.org/dist/latest-v4.x/docs/api/'
38 end48 end
49
50 def get_latest_version(opts)
51 doc = fetch_doc('https://nodejs.org/en/', opts)
52 doc.at_css('#home-intro > .home-downloadblock:last-of-type > a')['data-version'][1..-1]
53 end
39 end54 end
40end55end
lib/docs/scrapers/nokogiri2.rb+12−4
@@ -1,9 +1,13 @@
1module Docs1module Docs
2 class Nokogiri2 < Rdoc2 class Nokogiri2 < Rdoc
3 # Instructions:
4 # 1. Download the latest release at https://github.com/sparklemotion/nokogiri/releases
5 # 2. Run "bundle install && bundle exec rake docs" (in the Nokogiri directory)
6 # 4. Copy the "doc" directory to "docs/nokgiri"
7
3 self.name = 'Nokogiri'8 self.name = 'Nokogiri'
4 self.slug = 'nokogiri'9 self.slug = 'nokogiri'
5 self.release = '1.8.1'10 self.release = '1.9.0'
6 self.dir = '/Users/Thibaut/DevDocs/Docs/RDoc/Nokogiri'
7 11
8 html_filters.replace 'rdoc/entries', 'nokogiri2/entries'12 html_filters.replace 'rdoc/entries', 'nokogiri2/entries'
9 13
@@ -11,9 +15,13 @@ module Docs · 1 unmodified line
11 options[:only_patterns] = [/\ANokogiri/, /\AXSD/]15 options[:only_patterns] = [/\ANokogiri/, /\AXSD/]
12 16
13 options[:attribution] = <<-HTML17 options[:attribution] = <<-HTML
14 &copy; 2008&ndash;2017 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo<br>18 &copy; 2008&ndash;2018 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo,<br>
15 Patrick Mahoney, Yoko Harada, Akinori Musha, John Shahid<br>19 Patrick Mahoney, Yoko Harada, Akinori Musha, John Shahid, Lars Kanis<br>
16 Licensed under the MIT License.20 Licensed under the MIT License.
17 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_latest_github_release('sparklemotion', 'nokogiri', opts)
25 end
18 end26 end
19end27end
lib/docs/scrapers/npm.rb+5−1
@@ -2,7 +2,7 @@ module Docs · 1 unmodified line
2 class Npm < UrlScraper2 class Npm < UrlScraper
3 self.name = 'npm'3 self.name = 'npm'
4 self.type = 'npm'4 self.type = 'npm'
5 self.release = '5.4.1'5 self.release = '6.4.0'
6 self.base_url = 'https://docs.npmjs.com/'6 self.base_url = 'https://docs.npmjs.com/'
7 self.force_gzip = true7 self.force_gzip = true
8 self.links = {8 self.links = {
@@ -29,5 +29,9 @@ module Docs · 20 unmodified lines
29 Licensed under the npm License.<br>29 Licensed under the npm License.<br>
30 npm is a trademark of npm, Inc.30 npm is a trademark of npm, Inc.
31 HTML31 HTML
32
33 def get_latest_version(opts)
34 get_latest_github_release('npm', 'cli', opts)
35 end
32 end36 end
33end37end
lib/docs/scrapers/numpy.rb+9−1
@@ -2,7 +2,6 @@ module Docs · 1 unmodified line
2 class Numpy < FileScraper2 class Numpy < FileScraper
3 self.name = 'NumPy'3 self.name = 'NumPy'
4 self.type = 'sphinx'4 self.type = 'sphinx'
5 self.dir = '/Users/Thibaut/DevDocs/Docs/numpy/reference/'
6 self.root_path = 'index.html'5 self.root_path = 'index.html'
7 self.links = {6 self.links = {
8 home: 'http://www.numpy.org/',7 home: 'http://www.numpy.org/',
@@ -26,6 +25,11 @@ module Docs · 17 unmodified lines
26 Licensed under the NumPy License.25 Licensed under the NumPy License.
27 HTML26 HTML
28 27
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
29 version '1.13' do33 version '1.13' do
30 self.release = '1.13.0'34 self.release = '1.13.0'
31 self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"35 self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"
@@ -45,5 +49,9 @@ module Docs · 13 unmodified lines
45 self.release = '1.10.4'49 self.release = '1.10.4'
46 self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"50 self.base_url = "https://docs.scipy.org/doc/numpy-#{self.release}/reference/"
47 end51 end
52
53 def get_latest_version(opts)
54 get_latest_github_release('numpy', 'numpy', opts)
55 end
48 end56 end
49end57end
lib/docs/scrapers/openjdk.rb+22−3
@@ -1,11 +1,10 @@
1module Docs1module Docs
2 class Openjdk < FileScraper2 class Openjdk < FileScraper
3 # Downloaded from packages.debian.org/sid/openjdk-8-doc
4 # Extracting subdirectory /usr/share/doc/openjdk-8-jre-headless/api
3 self.name = 'OpenJDK'5 self.name = 'OpenJDK'
4 self.type = 'openjdk'6 self.type = 'openjdk'
5 self.root_path = 'overview-summary.html'7 self.root_path = 'overview-summary.html'
6 # Downloaded from packages.debian.org/sid/openjdk-8-doc
7 # Extracting subdirectory /usr/share/doc/openjdk-8-jre-headless/api
8 self.dir = '/Users/Thibaut/DevDocs/Docs/OpenJDK'
9 8
10 html_filters.insert_after 'internal_urls', 'openjdk/clean_urls'9 html_filters.insert_after 'internal_urls', 'openjdk/clean_urls'
11 html_filters.push 'openjdk/entries', 'openjdk/clean_html'10 html_filters.push 'openjdk/entries', 'openjdk/clean_html'
@@ -87,5 +86,25 @@ module Docs · 75 unmodified lines
87 def read_file(path)86 def read_file(path)
88 File.read(path).force_encoding('iso-8859-1').encode('utf-8') rescue nil87 File.read(path).force_encoding('iso-8859-1').encode('utf-8') rescue nil
89 end88 end
89
90 def get_latest_version(opts)
91 latest_version = 8
92 current_attempt = latest_version
93 attempts = 0
94
95 while attempts < 3
96 current_attempt += 1
97
98 doc = fetch_doc("https://packages.debian.org/sid/openjdk-#{current_attempt}-doc", opts)
99 if doc.at_css('.perror').nil?
100 latest_version = current_attempt
101 attempts = 0
102 else
103 attempts += 1
104 end
105 end
106
107 latest_version
108 end
90 end109 end
91end110end
lib/docs/scrapers/opentsdb.rb+4−0
@@ -18,5 +18,9 @@ module Docs · 17 unmodified lines
18 &copy; 2010&ndash;2016 The OpenTSDB Authors<br>18 &copy; 2010&ndash;2016 The OpenTSDB Authors<br>
19 Licensed under the GNU LGPLv2.1+ and GPLv3+ licenses.19 Licensed under the GNU LGPLv2.1+ and GPLv3+ licenses.
20 HTML20 HTML
21
22 def get_latest_version(opts)
23 get_latest_github_release('OpenTSDB', 'opentsdb', opts)
24 end
21 end25 end
22end26end
lib/docs/scrapers/padrino.rb+7−3
@@ -2,8 +2,8 @@ module Docs · 1 unmodified line
2 class Padrino < UrlScraper2 class Padrino < UrlScraper
3 self.slug = 'padrino'3 self.slug = 'padrino'
4 self.type = 'rubydoc'4 self.type = 'rubydoc'
5 self.release = '0.13.2'5 self.release = '0.14.4'
6 self.base_url = 'http://www.rubydoc.info/github/padrino/padrino-framework/'6 self.base_url = 'https://www.rubydoc.info/github/padrino/padrino-framework/'
7 self.root_path = 'file/README.rdoc'7 self.root_path = 'file/README.rdoc'
8 self.initial_paths = %w(index2)8 self.initial_paths = %w(index2)
9 self.links = {9 self.links = {
@@ -16,12 +16,16 @@ module Docs · 6 unmodified lines
16 options[:container] = ->(filter) { filter.root_page? ? '#filecontents' : '#content' }16 options[:container] = ->(filter) { filter.root_page? ? '#filecontents' : '#content' }
17 17
18 options[:attribution] = <<-HTML18 options[:attribution] = <<-HTML
19 &copy; 2010&ndash;2016 Padrino<br>19 &copy; 2010&ndash;2019 Padrino<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22 22
23 stub 'index2' do23 stub 'index2' do
24 request_one(url_for('index')).body24 request_one(url_for('index')).body
25 end25 end
26
27 def get_latest_version(opts)
28 get_github_tags('padrino', 'padrino-framework', opts)[0]['name']
29 end
26 end30 end
27end31end
lib/docs/scrapers/pandas.rb+36−4
@@ -14,25 +14,57 @@ module Docs · 13 unmodified lines
14 options[:container] = '.document'14 options[:container] = '.document'
15 15
16 options[:skip] = %w(internals.html release.html contributing.html whatsnew.html)16 options[:skip] = %w(internals.html release.html contributing.html whatsnew.html)
17 options[:skip_patterns] = [/whatsnew\//]
17 18
18 options[:attribution] = <<-HTML19 options[:attribution] = <<-HTML
19 &copy; 2008&ndash;2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team<br>20 &copy; 2008&ndash;2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team<br>
20 Licensed under the 3-clause BSD License.21 Licensed under the 3-clause BSD License.
21 HTML22 HTML
22 23
24 version '0.25' do
25 self.release = '0.25.0'
26 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
27 end
28
29 version '0.24' do
30 self.release = '0.24.2'
31 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
32 end
33
34 version '0.23' do
35 self.release = '0.23.4'
36 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
37 end
38
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
23 version '0.20' do49 version '0.20' do
24 self.release = '0.20.2'50 self.release = '0.20.3'
25 self.base_url = "http://pandas.pydata.org/pandas-docs/version/#{self.release}/"51 self.base_url = "https://pandas.pydata.org/pandas-docs/version/#{self.release}/"
26 end52 end
27 53
28 version '0.19' do54 version '0.19' do
29 self.release = '0.19.2'55 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}/"
31 end57 end
32 58
33 version '0.18' do59 version '0.18' do
34 self.release = '0.18.1'60 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]
36 end68 end
37 end69 end
38end70end
lib/docs/scrapers/perl.rb+6−1
@@ -2,7 +2,6 @@ module Docs · 1 unmodified line
2 class Perl < FileScraper2 class Perl < FileScraper
3 self.name = 'Perl'3 self.name = 'Perl'
4 self.type = 'perl'4 self.type = 'perl'
5 self.dir = '/Users/Thibaut/DevDocs/Docs/Perl'
6 self.root_path = 'index.html'5 self.root_path = 'index.html'
7 self.links = {6 self.links = {
8 home: 'https://www.perl.org/'7 home: 'https://www.perl.org/'
@@ -44,5 +43,11 @@ module Docs · 35 unmodified lines
44 self.release = '5.20.2'43 self.release = '5.20.2'
45 self.base_url = "https://perldoc.perl.org/#{self.release}/"44 self.base_url = "https://perldoc.perl.org/#{self.release}/"
46 end45 end
46
47 def get_latest_version(opts)
48 doc = fetch_doc('https://perldoc.perl.org/', opts)
49 header = doc.at_css('h2.h1').content
50 header.scan(/Perl ([0-9.]+)/)[0][0]
51 end
47 end52 end
48end53end
lib/docs/scrapers/phalcon.rb+5−0
@@ -29,5 +29,10 @@ module Docs · 28 unmodified lines
29 self.release = '2.0.13'29 self.release = '2.0.13'
30 self.base_url = 'https://docs.phalconphp.com/en/2.0.0/'30 self.base_url = 'https://docs.phalconphp.com/en/2.0.0/'
31 end31 end
32
33 def get_latest_version(opts)
34 doc = fetch_doc('https://docs.phalconphp.com/', opts)
35 doc.at_css('.header__lang.expand > div > ul > li > a').content
36 end
32 end37 end
33end38end
lib/docs/scrapers/phaser.rb+4−0
@@ -25,5 +25,9 @@ module Docs · 24 unmodified lines
25 &copy; 2016 Richard Davey, Photon Storm Ltd.<br>25 &copy; 2016 Richard Davey, Photon Storm Ltd.<br>
26 Licensed under the MIT License.26 Licensed under the MIT License.
27 HTML27 HTML
28
29 def get_latest_version(opts)
30 get_latest_github_release('photonstorm', 'phaser', opts)
31 end
28 end32 end
29end33end
lib/docs/scrapers/phoenix.rb+6−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Phoenix < UrlScraper2 class Phoenix < UrlScraper
3 self.type = 'elixir'3 self.type = 'elixir'
4 self.release = '1.3.0'4 self.release = '1.3.4'
5 self.base_url = 'https://hexdocs.pm/'5 self.base_url = 'https://hexdocs.pm/'
6 self.root_path = 'phoenix/Phoenix.html'6 self.root_path = 'phoenix/Phoenix.html'
7 self.initial_paths = %w(7 self.initial_paths = %w(
@@ -46,5 +46,10 @@ module Docs · 38 unmodified lines
46 HTML46 HTML
47 end47 end
48 }48 }
49
50 def get_latest_version(opts)
51 doc = fetch_doc('https://hexdocs.pm/phoenix/Phoenix.html', opts)
52 doc.at_css('.sidebar-projectVersion').content.strip[1..-1]
53 end
49 end54 end
50end55end
lib/docs/scrapers/php.rb+13−8
@@ -1,10 +1,12 @@
1module Docs1module Docs
2 class Php < FileScraper2 class Php < FileScraper
3 # Downloaded from php.net/download-docs.php
4
3 include FixInternalUrlsBehavior5 include FixInternalUrlsBehavior
4 6
5 self.name = 'PHP'7 self.name = 'PHP'
6 self.type = 'php'8 self.type = 'php'
7 self.release = 'up to 7.1.5'9 self.release = '7.2.9'
8 self.base_url = 'https://secure.php.net/manual/en/'10 self.base_url = 'https://secure.php.net/manual/en/'
9 self.root_path = 'index.html'11 self.root_path = 'index.html'
10 self.initial_paths = %w(12 self.initial_paths = %w(
@@ -20,12 +22,9 @@ module Docs · 9 unmodified lines
20 22
21 self.links = {23 self.links = {
22 home: 'https://secure.php.net/',24 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'
24 }26 }
25 27
26 # Downloaded from php.net/download-docs.php
27 self.dir = '/Users/Thibaut/DevDocs/Docs/PHP'
28
29 html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title'28 html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title'
30 text_filters.push 'php/fix_urls'29 text_filters.push 'php/fix_urls'
31 30
@@ -45,7 +44,7 @@ module Docs · 13 unmodified lines
45 44
46 BOOKS = %w(apache apc apcu array bc bzip2 calendar csprng classobj ctype curl45 BOOKS = %w(apache apc apcu array bc bzip2 calendar csprng classobj ctype curl
47 datetime dba dir dom ds eio errorfunc ev event exec exif fileinfo filesystem filter46 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 image47 ftp funchand gearman geoip gettext gmagick gmp hash ibase iconv iisfunc image
49 imagick imap info inotify intl json judy ldap libevent libxml lua mail mailparse48 imagick imap info inotify intl json judy ldap libevent libxml lua mail mailparse
50 math mbstring mcrypt memcached misc mysqli network oauth openssl49 math mbstring mcrypt memcached misc mysqli network oauth openssl
51 outcontrol password pcntl pcre pdo pgsql phar posix proctitle pthreads quickhash regex runkit50 outcontrol password pcntl pcre pdo pgsql phar posix proctitle pthreads quickhash regex runkit
@@ -61,11 +60,17 @@ module Docs · 9 unmodified lines
61 function.mssql-select-db.html60 function.mssql-select-db.html
62 pthreads.modifiers.html)61 pthreads.modifiers.html)
63 62
64 options[:skip_patterns] = [/mysqlnd/]63 options[:skip_patterns] = [/mysqlnd/, /xdevapi/i]
65 64
66 options[:attribution] = <<-HTML65 options[:attribution] = <<-HTML
67 &copy; 1997&ndash;2017 The PHP Documentation Group<br>66 &copy; 1997&ndash;2018 The PHP Documentation Group<br>
68 Licensed under the Creative Commons Attribution License v3.0 or later.67 Licensed under the Creative Commons Attribution License v3.0 or later.
69 HTML68 HTML
69
70 def get_latest_version(opts)
71 doc = fetch_doc('https://secure.php.net/manual/en/doc.changelog.php', opts)
72 label = doc.at_css('tbody.gen-changelog > tr > td').content
73 label.split(',').last.strip
74 end
70 end75 end
71end76end
lib/docs/scrapers/phpunit.rb+7−1
@@ -24,7 +24,7 @@ module Docs · 23 unmodified lines
24 HTML24 HTML
25 25
26 version '6' do26 version '6' do
27 self.release = '6.3'27 self.release = '6.5'
28 self.base_url = "https://phpunit.de/manual/#{release}/en/"28 self.base_url = "https://phpunit.de/manual/#{release}/en/"
29 end29 end
30 30
@@ -37,5 +37,11 @@ module Docs · 6 unmodified lines
37 self.release = '4.8'37 self.release = '4.8'
38 self.base_url = "https://phpunit.de/manual/#{release}/en/"38 self.base_url = "https://phpunit.de/manual/#{release}/en/"
39 end39 end
40
41 def get_latest_version(opts)
42 doc = fetch_doc('https://phpunit.readthedocs.io/', opts)
43 label = doc.at_css('.rst-current-version').content.strip
44 label.scan(/v: ([0-9.]+)/)[0][0]
45 end
40 end46 end
41end47end
lib/docs/scrapers/postgresql.rb+21−4
@@ -51,23 +51,40 @@ module Docs · 50 unmodified lines
51 /\Aunsupported-features/ ]51 /\Aunsupported-features/ ]
52 52
53 options[:attribution] = <<-HTML53 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>
55 Licensed under the PostgreSQL License.55 Licensed under the PostgreSQL License.
56 HTML56 HTML
57 57
58 version '10' do
59 self.release = '10.3'
60 self.base_url = 'https://www.postgresql.org/docs/10/static/'
61 end
62
58 version '9.6' do63 version '9.6' do
59 self.release = '9.6.5'64 self.release = '9.6.6'
60 self.base_url = 'https://www.postgresql.org/docs/9.6/static/'65 self.base_url = 'https://www.postgresql.org/docs/9.6/static/'
66
67 html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'
61 end68 end
62 69
63 version '9.5' do70 version '9.5' do
64 self.release = '9.5.9'71 self.release = '9.5.10'
65 self.base_url = 'https://www.postgresql.org/docs/9.5/static/'72 self.base_url = 'https://www.postgresql.org/docs/9.5/static/'
73
74 html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'
66 end75 end
67 76
68 version '9.4' do77 version '9.4' do
69 self.release = '9.4.14'78 self.release = '9.4.15'
70 self.base_url = 'https://www.postgresql.org/docs/9.4/static/'79 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]
71 end88 end
72 end89 end
73end90end
lib/docs/scrapers/pug.rb+5−1
@@ -3,7 +3,7 @@ module Docs · 2 unmodified lines
3 self.type = 'pug'3 self.type = 'pug'
4 self.base_url = 'https://pugjs.org/'4 self.base_url = 'https://pugjs.org/'
5 self.root_path = 'api/getting-started.html'5 self.root_path = 'api/getting-started.html'
6 self.release = '2.0.0-rc.2'6 self.release = '2.0.3'
7 self.links = {7 self.links = {
8 home: 'https://pugjs.org/',8 home: 'https://pugjs.org/',
9 code: 'https://github.com/pugjs/pug'9 code: 'https://github.com/pugjs/pug'
@@ -18,6 +18,10 @@ module Docs · 8 unmodified lines
18 Licensed under the MIT license.18 Licensed under the MIT license.
19 HTML19 HTML
20 20
21 def get_latest_version(opts)
22 get_npm_version('pug', opts)
23 end
24
21 private25 private
22 26
23 def parse(response) # Hook here because Nokogori removes whitespace from textareas27 def parse(response) # Hook here because Nokogori removes whitespace from textareas
lib/docs/scrapers/puppeteer.rb+23−0
@@ -0,0 +1,23 @@
1module Docs
2 class Puppeteer < Github
3 self.release = '1.10.0'
4 self.base_url = 'https://github.com/GoogleChrome/puppeteer/blob/v1.8.0/docs/api.md'
5 self.links = {
6 code: 'https://github.com/GoogleChrome/puppeteer'
7 }
8
9 html_filters.push 'puppeteer/entries', 'puppeteer/clean_html'
10
11 options[:container] = '.markdown-body'
12
13 options[:attribution] = <<-HTML
14 &copy; 2017 Google Inc<br>
15 Licensed under the Apache License 2.0.
16 HTML
17
18 def get_latest_version(opts)
19 contents = get_github_file_contents('GoogleChrome', 'puppeteer', 'README.md', opts)
20 contents.scan(/\/v([0-9.]+)\/docs\/api\.md/)[0][0]
21 end
22 end
23end
lib/docs/scrapers/pygame.rb+25−0
@@ -0,0 +1,25 @@
1module Docs
2 class Pygame < UrlScraper
3 self.type = 'simple'
4 self.release = '1.9.6'
5 self.base_url = 'https://www.pygame.org/docs/'
6 self.root_path = 'py-modindex.html'
7 self.links = {
8 home: 'https://www.pygame.org/',
9 code: 'https://github.com/pygame/pygame'
10 }
11
12 html_filters.push 'pygame/clean_html', 'pygame/entries'
13
14 options[:only_patterns] = [/ref\//]
15
16 options[:attribution] = <<-HTML
17 &copy; Pygame Developers.<br>
18 Licensed under the GNU LGPL License version 2.1.
19 HTML
20
21 def get_latest_version(opts)
22 get_latest_github_release('pygame', 'pygame', opts)
23 end
24 end
25end
lib/docs/scrapers/python.rb+20−11
@@ -1,6 +1,6 @@
1module Docs1module Docs
2 class Python < FileScraper2 class Python < FileScraper
3 self.type = 'sphinx'3 self.type = 'python'
4 self.root_path = 'library/index.html'4 self.root_path = 'library/index.html'
5 self.links = {5 self.links = {
6 home: 'https://www.python.org/',6 home: 'https://www.python.org/',
@@ -19,32 +19,41 @@ module Docs · 12 unmodified lines
19 library/sunau.html)19 library/sunau.html)
20 20
21 options[:attribution] = <<-HTML21 options[:attribution] = <<-HTML
22 &copy; 2001&ndash;2017 Python Software Foundation<br>22 &copy; 2001&ndash;2018 Python Software Foundation<br>
23 Licensed under the PSF License.23 Licensed under the PSF License.
24 HTML24 HTML
25 25
26 version '3.6' do26 version '3.7' do # docs.python.org/3.7/download.html
27 self.release = '3.6.1'27 self.release = '3.7.4'
28 self.dir = '/Users/Thibaut/DevDocs/Docs/Python36' # docs.python.org/3.6/download.html28 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'
29 self.base_url = 'https://docs.python.org/3.6/'35 self.base_url = 'https://docs.python.org/3.6/'
30 36
31 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'37 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'
32 end38 end
33 39
34 version '3.5' do40 version '3.5' do # docs.python.org/3.5/download.html
35 self.release = '3.5.3'41 self.release = '3.5.7'
36 self.dir = '/Users/Thibaut/DevDocs/Docs/Python35' # docs.python.org/3.5/download.html
37 self.base_url = 'https://docs.python.org/3.5/'42 self.base_url = 'https://docs.python.org/3.5/'
38 43
39 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'44 html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'
40 end45 end
41 46
42 version '2.7' do47 version '2.7' do # docs.python.org/2.7/download.html
43 self.release = '2.7.13'48 self.release = '2.7.16'
44 self.dir = '/Users/Thibaut/DevDocs/Docs/Python27' # docs.python.org/2.7/download.html
45 self.base_url = 'https://docs.python.org/2.7/'49 self.base_url = 'https://docs.python.org/2.7/'
46 50
47 html_filters.push 'python/entries_v2', 'sphinx/clean_html', 'python/clean_html'51 html_filters.push 'python/entries_v2', 'sphinx/clean_html', 'python/clean_html'
48 end52 end
53
54 def get_latest_version(opts)
55 doc = fetch_doc('https://docs.python.org/', opts)
56 doc.at_css('.version_switcher_placeholder').content
57 end
49 end58 end
50end59end
lib/docs/scrapers/q.rb+5−1
@@ -1,7 +1,7 @@
1module Docs1module Docs
2 class Q < Github2 class Q < Github
3 self.name = 'Q'3 self.name = 'Q'
4 self.release = '1.5.0'4 self.release = '1.5.1'
5 self.base_url = 'https://github.com/kriskowal/q/wiki/'5 self.base_url = 'https://github.com/kriskowal/q/wiki/'
6 self.root_path = 'API-Reference'6 self.root_path = 'API-Reference'
7 self.links = {7 self.links = {
@@ -19,5 +19,9 @@ module Docs · 11 unmodified lines
19 &copy; 2009&ndash;2017 Kristopher Michael Kowal<br>19 &copy; 2009&ndash;2017 Kristopher Michael Kowal<br>
20 Licensed under the MIT License.20 Licensed under the MIT License.
21 HTML21 HTML
22
23 def get_latest_version(opts)
24 get_npm_version('q', opts)
25 end
22 end26 end
23end27end
lib/docs/scrapers/qt.rb+136−0
@@ -0,0 +1,136 @@
1module Docs
2 class Qt < UrlScraper
3 self.name = 'Qt'
4 self.type = 'qt'
5 self.initial_paths = %w(classes.html qmltypes.html)
6 self.root_path = 'index.html'
7 self.links = {
8 home: 'https://www.qt.io',
9 code: 'https://code.qt.io/cgit/'
10 }
11
12 html_filters.push 'qt/entries', 'qt/clean_html'
13
14 options[:container] = '.main'
15 options[:max_image_size] = 156_000
16 options[:skip_patterns] = [
17 # License, copyright attributions
18 /3rdparty/,
19 /attribution/,
20 /license/,
21 /licensing/,
22
23 # Examples, guides, tutorials
24 /example/,
25 /guide$/,
26 /tutorial/,
27 /porting/,
28 /usecase/,
29 /topic/,
30 /^modelview/,
31 /deploy(ing|ment)/,
32 /building/,
33
34 # Old versions, changelog
35 /obsolete/,
36 /compatibility/,
37 /^whatsnew/,
38 /^newclasses/,
39
40 # Deprecated modules
41 /(qtopengl|qgl)/,
42 /qt?script/,
43
44 # Indexes
45 /members/,
46 /module/,
47 /overview/,
48 /^qopenglfunctions/,
49
50 # Tooling
51 /^(qt)?(linguist|assistant|qdbusviewer)/,
52 ]
53
54 options[:skip] = [
55 "qt5-intro.html",
56 "compatmap.html",
57
58 # Indexes
59 "classes.html",
60 "qtmodules.html",
61 "modules-qml.html",
62 "modules-cpp.html",
63 "functions.html",
64 "namespaces.html",
65 "qmltypes.html",
66 "qt3d-qml.html",
67 "qmlbasictypes.html",
68 "guibooks.html",
69 "annotated.html",
70 "overviews-main.html",
71 "reference-overview.html",
72
73 # Tutorials
74 "qtvirtualkeyboard-build.html",
75
76 # Copyright
77 "trademarks.html",
78 "lgpl.html",
79 "bughowto.html",
80
81 # Changelogs
82 "changes.html",
83 "qtlocation-changes.html",
84 "sourcebreaks.html",
85
86 # Best practice guides
87 "accessible.html",
88 "accessible-qtquick.html",
89 "sharedlibrary.html",
90 "exceptionsafety.html",
91 "scalability.html",
92 "session.html",
93 "appicon.html",
94 "accelerators.html",
95
96 # Other
97 "ecmascript.html",
98 "qtremoteobjects-interaction.html",
99 ]
100
101 options[:attribution] = <<-HTML
102 &copy; The Qt Company Ltd<br>
103 Licensed under the GNU Free Documentation License, Version 1.3.
104 HTML
105
106 version '5.13' do
107 self.release = '5.13'
108 self.base_url = 'https://doc.qt.io/qt-5.13/'
109 end
110

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