History

Prettier.io

Simon Legner committed 2 years ago

Commit a905bdf1 parent 6b335fb80 files · +13159 −10611Browse files

Unified · Split

80 files
  1. assets/javascripts/app/app.js +191 −90
  2. assets/javascripts/app/db.js +214 −110
  3. assets/javascripts/app/router.js +101 −61
  4. assets/javascripts/app/searcher.js +190 −118
  5. assets/javascripts/app/serviceworker.js +26 −12
  6. assets/javascripts/app/settings.js +111 −80
  7. assets/javascripts/app/shortcuts.js +126 −72
  8. assets/javascripts/app/update_checker.js +18 −10
  9. assets/javascripts/application.js +3 −3
  10. assets/javascripts/collections/collection.js +23 −7
  11. assets/javascripts/collections/docs.js +43 −20
  12. assets/javascripts/collections/entries.js +1 −1
  13. assets/javascripts/collections/types.js +9 −6
  14. assets/javascripts/debug.js +58 −31
  15. assets/javascripts/lib/ajax.js +73 −52
  16. assets/javascripts/lib/cookies_store.js +21 −10
  17. assets/javascripts/lib/events.js +38 −14
  18. assets/javascripts/lib/favicon.js +59 −41
  19. assets/javascripts/lib/license.js +1 −1
  20. assets/javascripts/lib/page.js +153 −82
  21. assets/javascripts/lib/util.js +308 −157
  22. assets/javascripts/models/doc.js +63 −29
  23. assets/javascripts/models/entry.js +51 −48
  24. assets/javascripts/models/model.js +4 −1
  25. assets/javascripts/models/type.js +2 −2
  26. assets/javascripts/templates/base.js +6 −4
  27. assets/javascripts/templates/error_tmpl.js +53 −36
  28. assets/javascripts/templates/notice_tmpl.js +7 −7
  29. assets/javascripts/templates/notif_tmpl.js +69 −34
  30. assets/javascripts/templates/pages/about_tmpl.js +24 −13
  31. assets/javascripts/templates/pages/help_tmpl.js +35 −29
  32. assets/javascripts/templates/pages/offline_tmpl.js +20 −16
  33. assets/javascripts/templates/pages/settings_tmpl.js +50 −19
  34. assets/javascripts/templates/pages/type_tmpl.js +9 −3
  35. assets/javascripts/templates/path_tmpl.js +13 −5
  36. assets/javascripts/templates/sidebar_tmpl.js +75 −37
  37. assets/javascripts/templates/tip_tmpl.js +8 −2
  38. assets/javascripts/tracking.js +43 −20
  39. assets/javascripts/vendor/cookies.js +191 −158
  40. assets/javascripts/vendor/mathml.js +9 −4
  41. assets/javascripts/vendor/prism.js +5777 −5324
  42. assets/javascripts/vendor/raven.js +3290 −2911
  43. assets/javascripts/views/content/content.js +81 −56
  44. assets/javascripts/views/content/entry_page.js +94 −47
  45. assets/javascripts/views/content/offline_page.js +70 −37
  46. assets/javascripts/views/content/root_page.js +14 −14
  47. assets/javascripts/views/content/settings_page.js +51 −41
  48. assets/javascripts/views/content/static_page.js +7 −7
  49. assets/javascripts/views/content/type_page.js +2 −2
  50. assets/javascripts/views/layout/document.js +69 −40
  51. assets/javascripts/views/layout/menu.js +16 −9
  52. assets/javascripts/views/layout/mobile.js +77 −63
  53. assets/javascripts/views/layout/path.js +16 −13
  54. assets/javascripts/views/layout/resizer.js +32 −20
  55. assets/javascripts/views/layout/settings.js +41 −33
  56. assets/javascripts/views/list/list_focus.js +71 −44
  57. assets/javascripts/views/list/list_fold.js +44 −20
  58. assets/javascripts/views/list/list_select.js +25 −14
  59. assets/javascripts/views/list/paginated_list.js +57 −22
  60. assets/javascripts/views/misc/news.js +14 −9
  61. assets/javascripts/views/misc/notice.js +13 −6
  62. assets/javascripts/views/misc/notif.js +22 −16
  63. assets/javascripts/views/misc/tip.js +3 −4
  64. assets/javascripts/views/misc/updates.js +30 −12
  65. assets/javascripts/views/pages/base.js +39 −15
  66. assets/javascripts/views/pages/hidden.js +9 −5
  67. assets/javascripts/views/pages/jquery.js +30 −17
  68. assets/javascripts/views/pages/rdoc.js +11 −7
  69. assets/javascripts/views/pages/sqlite.js +15 −10
  70. assets/javascripts/views/pages/support_tables.js +11 −6
  71. assets/javascripts/views/search/search.js +113 −62
  72. assets/javascripts/views/search/search_scope.js +91 −53
  73. assets/javascripts/views/sidebar/doc_list.js +90 −55
  74. assets/javascripts/views/sidebar/doc_picker.js +62 −32
  75. assets/javascripts/views/sidebar/entry_list.js +10 −5
  76. assets/javascripts/views/sidebar/results.js +49 −28
  77. assets/javascripts/views/sidebar/sidebar.js +79 −43
  78. assets/javascripts/views/sidebar/sidebar_hover.js +35 −26
  79. assets/javascripts/views/sidebar/type_list.js +36 −15
  80. assets/javascripts/views/view.js +64 −23
assets/javascripts/app/app.js+191−90
@@ -14,33 +14,41 @@ this.app = { · 13 unmodified lines
14 _$$: $$,14 _$$: $$,
15 _page: page,15 _page: page,
16 collections: {},16 collections: {},
17 models: {},17 models: {},
18 templates: {},18 templates: {},
19 views: {},19 views: {},
20 20
21 init() {21 init() {
22 try { this.initErrorTracking(); } catch (error) {}22 try {
23 if (!this.browserCheck()) { return; }23 this.initErrorTracking();
24 } catch (error) {}
25 if (!this.browserCheck()) {
26 return;
27 }
24 28
25 this.el = $('._app');29 this.el = $("._app");
26 this.localStorage = new LocalStorageStore;30 this.localStorage = new LocalStorageStore();
27 if (app.ServiceWorker.isEnabled()) { this.serviceWorker = new app.ServiceWorker; }31 if (app.ServiceWorker.isEnabled()) {
28 this.settings = new app.Settings;32 this.serviceWorker = new app.ServiceWorker();
33 }
34 this.settings = new app.Settings();
29 this.db = new app.DB();35 this.db = new app.DB();
30 36
31 this.settings.initLayout();37 this.settings.initLayout();
32 38
33 this.docs = new app.collections.Docs;39 this.docs = new app.collections.Docs();
34 this.disabledDocs = new app.collections.Docs;40 this.disabledDocs = new app.collections.Docs();
35 this.entries = new app.collections.Entries;41 this.entries = new app.collections.Entries();
36 42
37 this.router = new app.Router;43 this.router = new app.Router();
38 this.shortcuts = new app.Shortcuts;44 this.shortcuts = new app.Shortcuts();
39 this.document = new app.views.Document;45 this.document = new app.views.Document();
40 if (this.isMobile()) { this.mobile = new app.views.Mobile; }46 if (this.isMobile()) {
47 this.mobile = new app.views.Mobile();
48 }
41 49
42 if (document.body.hasAttribute('data-doc')) {50 if (document.body.hasAttribute("data-doc")) {
43 this.DOC = JSON.parse(document.body.getAttribute('data-doc'));51 this.DOC = JSON.parse(document.body.getAttribute("data-doc"));
44 this.bootOne();52 this.bootOne();
45 } else if (this.DOCS) {53 } else if (this.DOCS) {
46 this.bootAll();54 this.bootAll();
@@ -50,7 +58,9 @@ this.app = { · 3 unmodified lines
50 },58 },
51 59
52 browserCheck() {60 browserCheck() {
53 if (this.isSupportedBrowser()) { return true; }61 if (this.isSupportedBrowser()) {
62 return true;
63 }
54 document.body.innerHTML = app.templates.unsupportedBrowser;64 document.body.innerHTML = app.templates.unsupportedBrowser;
55 this.hideLoadingScreen();65 this.hideLoadingScreen();
56 return false;66 return false;
@@ -61,7 +71,7 @@ this.app = { · 4 unmodified lines
61 // from a domain other than our own, because things are likely to break.71 // from a domain other than our own, because things are likely to break.
62 // (e.g. cross-domain requests)72 // (e.g. cross-domain requests)
63 if (this.isInvalidLocation()) {73 if (this.isInvalidLocation()) {
64 new app.views.Notif('InvalidLocation');74 new app.views.Notif("InvalidLocation");
65 } else {75 } else {
66 if (this.config.sentry_dsn) {76 if (this.config.sentry_dsn) {
67 Raven.config(this.config.sentry_dsn, {77 Raven.config(this.config.sentry_dsn, {
@@ -70,9 +80,12 @@ this.app = { · 2 unmodified lines
70 includePaths: [/devdocs/],80 includePaths: [/devdocs/],
71 ignoreErrors: [/NPObject/, /NS_ERROR/, /^null$/, /EvalError/],81 ignoreErrors: [/NPObject/, /NS_ERROR/, /^null$/, /EvalError/],
72 tags: {82 tags: {
73 mode: this.isSingleDoc() ? 'single' : 'full',83 mode: this.isSingleDoc() ? "single" : "full",
74 iframe: (window.top !== window).toString(),84 iframe: (window.top !== window).toString(),
75 electron: (!!__guard__(window.process != null ? window.process.versions : undefined, x => x.electron)).toString()85 electron: (!!__guard__(
86 window.process != null ? window.process.versions : undefined,
87 (x) => x.electron,
88 )).toString(),
76 },89 },
77 shouldSendCallback: () => {90 shouldSendCallback: () => {
78 try {91 try {
@@ -89,12 +102,16 @@ this.app = { · 10 unmodified lines
89 dataCallback(data) {102 dataCallback(data) {
90 try {103 try {
91 $.extend(data.user || (data.user = {}), app.settings.dump());104 $.extend(data.user || (data.user = {}), app.settings.dump());
92 if (data.user.docs) { data.user.docs = data.user.docs.split('/'); }105 if (data.user.docs) {
93 if (app.lastIDBTransaction) { data.user.lastIDBTransaction = app.lastIDBTransaction; }106 data.user.docs = data.user.docs.split("/");
107 }
108 if (app.lastIDBTransaction) {
109 data.user.lastIDBTransaction = app.lastIDBTransaction;
110 }
94 data.tags.scriptCount = document.scripts.length;111 data.tags.scriptCount = document.scripts.length;
95 } catch (error) {}112 } catch (error) {}
96 return data;113 return data;
97 }114 },
98 }).install();115 }).install();
99 }116 }
100 this.previousErrorHandler = onerror;117 this.previousErrorHandler = onerror;
@@ -106,8 +123,10 @@ this.app = { · 5 unmodified lines
106 bootOne() {123 bootOne() {
107 this.doc = new app.models.Doc(this.DOC);124 this.doc = new app.models.Doc(this.DOC);
108 this.docs.reset([this.doc]);125 this.docs.reset([this.doc]);
109 this.doc.load(this.start.bind(this), this.onBootError.bind(this), {readCache: true});126 this.doc.load(this.start.bind(this), this.onBootError.bind(this), {
110 new app.views.Notice('singleDoc', this.doc);127 readCache: true,
128 });
129 new app.views.Notice("singleDoc", this.doc);
111 delete this.DOC;130 delete this.DOC;
112 },131 },
113 132
@@ -117,40 +136,61 @@ this.app = { · 3 unmodified lines
117 (docs.indexOf(doc.slug) >= 0 ? this.docs : this.disabledDocs).add(doc);136 (docs.indexOf(doc.slug) >= 0 ? this.docs : this.disabledDocs).add(doc);
118 }137 }
119 this.migrateDocs();138 this.migrateDocs();
120 this.docs.load(this.start.bind(this), this.onBootError.bind(this), {readCache: true, writeCache: true});139 this.docs.load(this.start.bind(this), this.onBootError.bind(this), {
140 readCache: true,
141 writeCache: true,
142 });
121 delete this.DOCS;143 delete this.DOCS;
122 },144 },
123 145
124 start() {146 start() {
125 let doc;147 let doc;
126 for (doc of Array.from(this.docs.all())) { this.entries.add(doc.toEntry()); }148 for (doc of Array.from(this.docs.all())) {
127 for (doc of Array.from(this.disabledDocs.all())) { this.entries.add(doc.toEntry()); }149 this.entries.add(doc.toEntry());
128 for (doc of Array.from(this.docs.all())) { this.initDoc(doc); }150 }
129 this.trigger('ready');151 for (doc of Array.from(this.disabledDocs.all())) {
152 this.entries.add(doc.toEntry());
153 }
154 for (doc of Array.from(this.docs.all())) {
155 this.initDoc(doc);
156 }
157 this.trigger("ready");
130 this.router.start();158 this.router.start();
131 this.hideLoadingScreen();159 this.hideLoadingScreen();
132 setTimeout(() => {160 setTimeout(() => {
133 if (!this.doc) { this.welcomeBack(); }161 if (!this.doc) {
134 return this.removeEvent('ready bootError');162 this.welcomeBack();
135 }163 }
136 , 50);164 return this.removeEvent("ready bootError");
165 }, 50);
137 },166 },
138 167
139 initDoc(doc) {168 initDoc(doc) {
140 for (var type of Array.from(doc.types.all())) { doc.entries.add(type.toEntry()); }169 for (var type of Array.from(doc.types.all())) {
170 doc.entries.add(type.toEntry());
171 }
141 this.entries.add(doc.entries.all());172 this.entries.add(doc.entries.all());
142 },173 },
143 174
144 migrateDocs() {175 migrateDocs() {
145 let needsSaving;176 let needsSaving;
146 for (var slug of Array.from(this.settings.getDocs())) {177 for (var slug of Array.from(this.settings.getDocs())) {
147 if (!this.docs.findBy('slug', slug)) {var doc;178 if (!this.docs.findBy("slug", slug)) {
148 179 var doc;
180
149 needsSaving = true;181 needsSaving = true;
150 if (slug === 'webpack~2') { doc = this.disabledDocs.findBy('slug', 'webpack'); }182 if (slug === "webpack~2") {
151 if (slug === 'angular~4_typescript') { doc = this.disabledDocs.findBy('slug', 'angular'); }183 doc = this.disabledDocs.findBy("slug", "webpack");
152 if (slug === 'angular~2_typescript') { doc = this.disabledDocs.findBy('slug', 'angular~2'); }184 }
153 if (!doc) { doc = this.disabledDocs.findBy('slug_without_version', slug); }185 if (slug === "angular~4_typescript") {
186 doc = this.disabledDocs.findBy("slug", "angular");
187 }
188 if (slug === "angular~2_typescript") {
189 doc = this.disabledDocs.findBy("slug", "angular~2");
190 }
191 if (!doc) {
192 doc = this.disabledDocs.findBy("slug_without_version", slug);
193 }
154 if (doc) {194 if (doc) {
155 this.disabledDocs.remove(doc);195 this.disabledDocs.remove(doc);
156 this.docs.add(doc);196 this.docs.add(doc);
@@ -158,56 +198,70 @@ this.app = { · 1 unmodified line
158 }198 }
159 }199 }
160 200
161 if (needsSaving) { this.saveDocs(); }201 if (needsSaving) {
202 this.saveDocs();
203 }
162 },204 },
163 205
164 enableDoc(doc, _onSuccess, onError) {206 enableDoc(doc, _onSuccess, onError) {
165 if (this.docs.contains(doc)) { return; }207 if (this.docs.contains(doc)) {
208 return;
209 }
166 210
167 const onSuccess = () => {211 const onSuccess = () => {
168 if (this.docs.contains(doc)) { return; }212 if (this.docs.contains(doc)) {
213 return;
214 }
169 this.disabledDocs.remove(doc);215 this.disabledDocs.remove(doc);
170 this.docs.add(doc);216 this.docs.add(doc);
171 this.docs.sort();217 this.docs.sort();
172 this.initDoc(doc);218 this.initDoc(doc);
173 this.saveDocs();219 this.saveDocs();
174 if (app.settings.get('autoInstall')) {220 if (app.settings.get("autoInstall")) {
175 doc.install(_onSuccess, onError);221 doc.install(_onSuccess, onError);
176 } else {222 } else {
177 _onSuccess();223 _onSuccess();
178 }224 }
179 };225 };
180 226
181 doc.load(onSuccess, onError, {writeCache: true});227 doc.load(onSuccess, onError, { writeCache: true });
182 },228 },
183 229
184 saveDocs() {230 saveDocs() {
185 this.settings.setDocs(Array.from(this.docs.all()).map((doc) => doc.slug));231 this.settings.setDocs(Array.from(this.docs.all()).map((doc) => doc.slug));
186 this.db.migrate();232 this.db.migrate();
187 return (this.serviceWorker != null ? this.serviceWorker.updateInBackground() : undefined);233 return this.serviceWorker != null
234 ? this.serviceWorker.updateInBackground()
235 : undefined;
188 },236 },
189 237
190 welcomeBack() {238 welcomeBack() {
191 let visitCount = this.settings.get('count');239 let visitCount = this.settings.get("count");
192 this.settings.set('count', ++visitCount);240 this.settings.set("count", ++visitCount);
193 if (visitCount === 5) { new app.views.Notif('Share', {autoHide: null}); }241 if (visitCount === 5) {
242 new app.views.Notif("Share", { autoHide: null });
243 }
194 new app.views.News();244 new app.views.News();
195 new app.views.Updates();245 new app.views.Updates();
196 return this.updateChecker = new app.UpdateChecker();246 return (this.updateChecker = new app.UpdateChecker());
197 },247 },
198 248
199 reboot() {249 reboot() {
200 if ((location.pathname !== '/') && (location.pathname !== '/settings')) {250 if (location.pathname !== "/" && location.pathname !== "/settings") {
201 window.location = `/#${location.pathname}`;251 window.location = `/#${location.pathname}`;
202 } else {252 } else {
203 window.location = '/';253 window.location = "/";
204 }254 }
205 },255 },
206 256
207 reload() {257 reload() {
208 this.docs.clearCache();258 this.docs.clearCache();
209 this.disabledDocs.clearCache();259 this.disabledDocs.clearCache();
210 if (this.serviceWorker) { this.serviceWorker.reload(); } else { this.reboot(); }260 if (this.serviceWorker) {
261 this.serviceWorker.reload();
262 } else {
263 this.reboot();
264 }
211 },265 },
212 266
213 reset() {267 reset() {
@@ -219,11 +273,13 @@ this.app = { · 5 unmodified lines
219 if (this.serviceWorker != null) {273 if (this.serviceWorker != null) {
220 this.serviceWorker.update();274 this.serviceWorker.update();
221 }275 }
222 window.location = '/';276 window.location = "/";
223 },277 },
224 278
225 showTip(tip) {279 showTip(tip) {
226 if (this.isSingleDoc()) { return; }280 if (this.isSingleDoc()) {
281 return;
282 }
227 const tips = this.settings.getTips();283 const tips = this.settings.getTips();
228 if (tips.indexOf(tip) === -1) {284 if (tips.indexOf(tip) === -1) {
229 tips.push(tip);285 tips.push(tip);
@@ -233,44 +289,61 @@ this.app = { · 3 unmodified lines
233 },289 },
234 290
235 hideLoadingScreen() {291 hideLoadingScreen() {
236 if ($.overlayScrollbarsEnabled()) { document.body.classList.add('_overlay-scrollbars'); }292 if ($.overlayScrollbarsEnabled()) {
237 document.documentElement.classList.remove('_booting');293 document.body.classList.add("_overlay-scrollbars");
294 }
295 document.documentElement.classList.remove("_booting");
238 },296 },
239 297
240 indexHost() {298 indexHost() {
241 // Can't load the index files from the host/CDN when service worker is299 // Can't load the index files from the host/CDN when service worker is
242 // enabled because it doesn't support caching URLs that use CORS.300 // enabled because it doesn't support caching URLs that use CORS.
243 return this.config[this.serviceWorker && this.settings.hasDocs() ? 'index_path' : 'docs_origin'];301 return this.config[
302 this.serviceWorker && this.settings.hasDocs()
303 ? "index_path"
304 : "docs_origin"
305 ];
244 },306 },
245 307
246 onBootError(...args) {308 onBootError(...args) {
247 this.trigger('bootError');309 this.trigger("bootError");
248 this.hideLoadingScreen();310 this.hideLoadingScreen();
249 },311 },
250 312
251 onQuotaExceeded() {313 onQuotaExceeded() {
252 if (this.quotaExceeded) { return; }314 if (this.quotaExceeded) {
315 return;
316 }
253 this.quotaExceeded = true;317 this.quotaExceeded = true;
254 new app.views.Notif('QuotaExceeded', {autoHide: null});318 new app.views.Notif("QuotaExceeded", { autoHide: null });
255 },319 },
256 320
257 onCookieBlocked(key, value, actual) {321 onCookieBlocked(key, value, actual) {
258 if (this.cookieBlocked) { return; }322 if (this.cookieBlocked) {
323 return;
324 }
259 this.cookieBlocked = true;325 this.cookieBlocked = true;
260 new app.views.Notif('CookieBlocked', {autoHide: null});326 new app.views.Notif("CookieBlocked", { autoHide: null });
261 Raven.captureMessage(`CookieBlocked/${key}`, {level: 'warning', extra: {value, actual}});327 Raven.captureMessage(`CookieBlocked/${key}`, {
328 level: "warning",
329 extra: { value, actual },
330 });
262 },331 },
263 332
264 onWindowError(...args) {333 onWindowError(...args) {
265 if (this.cookieBlocked) { return; }334 if (this.cookieBlocked) {
335 return;
336 }
266 if (this.isInjectionError(...Array.from(args || []))) {337 if (this.isInjectionError(...Array.from(args || []))) {
267 this.onInjectionError();338 this.onInjectionError();
268 } else if (this.isAppError(...Array.from(args || []))) {339 } else if (this.isAppError(...Array.from(args || []))) {
269 if (typeof this.previousErrorHandler === 'function') {340 if (typeof this.previousErrorHandler === "function") {
270 this.previousErrorHandler(...Array.from(args || []));341 this.previousErrorHandler(...Array.from(args || []));
271 }342 }
272 this.hideLoadingScreen();343 this.hideLoadingScreen();
273 if (!this.errorNotif) { this.errorNotif = new app.views.Notif('Error'); }344 if (!this.errorNotif) {
345 this.errorNotif = new app.views.Notif("Error");
346 }
274 this.errorNotif.show();347 this.errorNotif.show();
275 }348 }
276 },349 },
@@ -280,75 +353,103 @@ this.app = { · 3 unmodified lines
280 this.injectionError = true;353 this.injectionError = true;
281 alert(`\354 alert(`\
282JavaScript code has been injected in the page which prevents DevDocs from running correctly.355JavaScript code has been injected in the page which prevents DevDocs from running correctly.
283Please check your browser extensions/addons. `356Please check your browser extensions/addons. `);
284 );357 Raven.captureMessage("injection error", { level: "info" });
285 Raven.captureMessage('injection error', {level: 'info'});
286 }358 }
287 },359 },
288 360
289 isInjectionError() {361 isInjectionError() {
290 // Some browser extensions expect the entire web to use jQuery.362 // Some browser extensions expect the entire web to use jQuery.
291 // I gave up trying to fight back.363 // I gave up trying to fight back.
292 return (window.$ !== app._$) || (window.$$ !== app._$$) || (window.page !== app._page) || (typeof $.empty !== 'function') || (typeof page.show !== 'function');364 return (
365 window.$ !== app._$ ||
366 window.$$ !== app._$$ ||
367 window.page !== app._page ||
368 typeof $.empty !== "function" ||
369 typeof page.show !== "function"
370 );
293 },371 },
294 372
295 isAppError(error, file) {373 isAppError(error, file) {
296 // Ignore errors from external scripts.374 // Ignore errors from external scripts.
297 return file && (file.indexOf('devdocs') !== -1) && (file.indexOf('.js') === (file.length - 3));375 return (
376 file &&
377 file.indexOf("devdocs") !== -1 &&
378 file.indexOf(".js") === file.length - 3
379 );
298 },380 },
299 381
300 isSupportedBrowser() {382 isSupportedBrowser() {
301 try {383 try {
302 const features = {384 const features = {
303 bind: !!Function.prototype.bind,385 bind: !!Function.prototype.bind,
304 pushState: !!history.pushState,386 pushState: !!history.pushState,
305 matchMedia: !!window.matchMedia,387 matchMedia: !!window.matchMedia,
306 insertAdjacentHTML: !!document.body.insertAdjacentHTML,388 insertAdjacentHTML: !!document.body.insertAdjacentHTML,
307 defaultPrevented: document.createEvent('CustomEvent').defaultPrevented === false,389 defaultPrevented:
308 cssVariables: !!__guardMethod__(CSS, 'supports', o => o.supports('(--t: 0)'))390 document.createEvent("CustomEvent").defaultPrevented === false,
391 cssVariables: !!__guardMethod__(CSS, "supports", (o) =>
392 o.supports("(--t: 0)"),
393 ),
309 };394 };
310 395
311 for (var key in features) {396 for (var key in features) {
312 var value = features[key];397 var value = features[key];
313 if (!value) {398 if (!value) {
314 Raven.captureMessage(`unsupported/${key}`, {level: 'info'});399 Raven.captureMessage(`unsupported/${key}`, { level: "info" });
315 return false;400 return false;
316 }401 }
317 }402 }
318 403
319 return true;404 return true;
320 } catch (error) {405 } catch (error) {
321 Raven.captureMessage('unsupported/exception', {level: 'info', extra: { error }});406 Raven.captureMessage("unsupported/exception", {
407 level: "info",
408 extra: { error },
409 });
322 return false;410 return false;
323 }411 }
324 },412 },
325 413
326 isSingleDoc() {414 isSingleDoc() {
327 return document.body.hasAttribute('data-doc');415 return document.body.hasAttribute("data-doc");
328 },416 },
329 417
330 isMobile() {418 isMobile() {
331 return this._isMobile != null ? this._isMobile : (this._isMobile = app.views.Mobile.detect());419 return this._isMobile != null
420 ? this._isMobile
421 : (this._isMobile = app.views.Mobile.detect());
332 },422 },
333 423
334 isAndroidWebview() {424 isAndroidWebview() {
335 return this._isAndroidWebview != null ? this._isAndroidWebview : (this._isAndroidWebview = app.views.Mobile.detectAndroidWebview());425 return this._isAndroidWebview != null
426 ? this._isAndroidWebview
427 : (this._isAndroidWebview = app.views.Mobile.detectAndroidWebview());
336 },428 },
337 429
338 isInvalidLocation() {430 isInvalidLocation() {
339 return (this.config.env === 'production') && (location.host.indexOf(app.config.production_host) !== 0);431 return (
340 }432 this.config.env === "production" &&
433 location.host.indexOf(app.config.production_host) !== 0
434 );
435 },
341};436};
342 437
343$.extend(app, Events);438$.extend(app, Events);
344 439
345function __guard__(value, transform) {440function __guard__(value, transform) {
346 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;441 return typeof value !== "undefined" && value !== null
442 ? transform(value)
443 : undefined;
347}444}
348function __guardMethod__(obj, methodName, transform) {445function __guardMethod__(obj, methodName, transform) {
349 if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') {446 if (
447 typeof obj !== "undefined" &&
448 obj !== null &&
449 typeof obj[methodName] === "function"
450 ) {
350 return transform(obj, methodName);451 return transform(obj, methodName);
351 } else {452 } else {
352 return undefined;453 return undefined;
353 }454 }
354}455}
assets/javascripts/app/db.js+214−110
@@ -9,12 +9,12 @@ · 8 unmodified lines
9 * DS207: Consider shorter variations of null checks9 * DS207: Consider shorter variations of null checks
10 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md10 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
11 */11 */
12(function() {12(function () {
13 let NAME = undefined;13 let NAME = undefined;
14 let VERSION = undefined;14 let VERSION = undefined;
15 const Cls = (app.DB = class DB {15 const Cls = (app.DB = class DB {
16 static initClass() {16 static initClass() {
17 NAME = 'docs';17 NAME = "docs";
18 VERSION = 15;18 VERSION = 15;
19 }19 }
20 20
@@ -29,18 +29,27 @@ · 8 unmodified lines
29 }29 }
30 30
31 db(fn) {31 db(fn) {
32 if (!this.useIndexedDB) { return fn(); }32 if (!this.useIndexedDB) {
33 if (fn) { this.callbacks.push(fn); }33 return fn();
34 if (this.open) { return; }34 }
35 if (fn) {
36 this.callbacks.push(fn);
37 }
38 if (this.open) {
39 return;
40 }
35 41
36 try {42 try {
37 this.open = true;43 this.open = true;
38 const req = indexedDB.open(NAME, (VERSION * this.versionMultipler) + this.userVersion());44 const req = indexedDB.open(
45 NAME,
46 VERSION * this.versionMultipler + this.userVersion(),
47 );
39 req.onsuccess = this.onOpenSuccess;48 req.onsuccess = this.onOpenSuccess;
40 req.onerror = this.onOpenError;49 req.onerror = this.onOpenError;
41 req.onupgradeneeded = this.onUpgradeNeeded;50 req.onupgradeneeded = this.onUpgradeNeeded;
42 } catch (error) {51 } catch (error) {
43 this.fail('exception', error);52 this.fail("exception", error);
44 }53 }
45 }54 }
46 55
@@ -49,13 +58,17 @@ · 2 unmodified lines
49 const db = event.target.result;58 const db = event.target.result;
50 59
51 if (db.objectStoreNames.length === 0) {60 if (db.objectStoreNames.length === 0) {
52 try { db.close(); } catch (error1) {}61 try {
62 db.close();
63 } catch (error1) {}
53 this.open = false;64 this.open = false;
54 this.fail('empty');65 this.fail("empty");
55 } else if (error = this.buggyIDB(db)) {66 } else if ((error = this.buggyIDB(db))) {
56 try { db.close(); } catch (error2) {}67 try {
68 db.close();
69 } catch (error2) {}
57 this.open = false;70 this.open = false;
58 this.fail('buggy', error);71 this.fail("buggy", error);
59 } else {72 } else {
60 this.runCallbacks(db);73 this.runCallbacks(db);
61 this.open = false;74 this.open = false;
@@ -66,36 +79,43 @@ · 4 unmodified lines
66 onOpenError(event) {79 onOpenError(event) {
67 event.preventDefault();80 event.preventDefault();
68 this.open = false;81 this.open = false;
69 const {82 const { error } = event.target;
70 error
71 } = event.target;
72 83
73 switch (error.name) {84 switch (error.name) {
74 case 'QuotaExceededError':85 case "QuotaExceededError":
75 this.onQuotaExceededError();86 this.onQuotaExceededError();
76 break;87 break;
77 case 'VersionError':88 case "VersionError":
78 this.onVersionError();89 this.onVersionError();
79 break;90 break;
80 case 'InvalidStateError':91 case "InvalidStateError":
81 this.fail('private_mode');92 this.fail("private_mode");
82 break;93 break;
83 default:94 default:
84 this.fail('cant_open', error);95 this.fail("cant_open", error);
85 }96 }
86 }97 }
87 98
88 fail(reason, error) {99 fail(reason, error) {
89 this.cachedDocs = null;100 this.cachedDocs = null;
90 this.useIndexedDB = false;101 this.useIndexedDB = false;
91 if (!this.reason) { this.reason = reason; }102 if (!this.reason) {
92 if (!this.error) { this.error = error; }103 this.reason = reason;
93 if (error) { if (typeof console.error === 'function') {104 }
94 console.error('IDB error', error);105 if (!this.error) {
95 } }106 this.error = error;
107 }
108 if (error) {
109 if (typeof console.error === "function") {
110 console.error("IDB error", error);
111 }
112 }
96 this.runCallbacks();113 this.runCallbacks();
97 if (error && (reason === 'cant_open')) {114 if (error && reason === "cant_open") {
98 Raven.captureMessage(`${error.name}: ${error.message}`, {level: 'warning', fingerprint: [error.name]});115 Raven.captureMessage(`${error.name}: ${error.message}`, {
116 level: "warning",
117 fingerprint: [error.name],
118 });
99 }119 }
100 }120 }
101 121
@@ -103,34 +123,39 @@ · 1 unmodified line
103 this.reset();123 this.reset();
104 this.db();124 this.db();
105 app.onQuotaExceeded();125 app.onQuotaExceeded();
106 Raven.captureMessage('QuotaExceededError', {level: 'warning'});126 Raven.captureMessage("QuotaExceededError", { level: "warning" });
107 }127 }
108 128
109 onVersionError() {129 onVersionError() {
110 const req = indexedDB.open(NAME);130 const req = indexedDB.open(NAME);
111 req.onsuccess = event => {131 req.onsuccess = (event) => {
112 return this.handleVersionMismatch(event.target.result.version);132 return this.handleVersionMismatch(event.target.result.version);
113 };133 };
114 req.onerror = function(event) {134 req.onerror = function (event) {
115 event.preventDefault();135 event.preventDefault();
116 return this.fail('cant_open', error);136 return this.fail("cant_open", error);
117 };137 };
118 }138 }
119 139
120 handleVersionMismatch(actualVersion) {140 handleVersionMismatch(actualVersion) {
121 if (Math.floor(actualVersion / this.versionMultipler) !== VERSION) {141 if (Math.floor(actualVersion / this.versionMultipler) !== VERSION) {
122 this.fail('version');142 this.fail("version");
123 } else {143 } else {
124 this.setUserVersion(actualVersion - (VERSION * this.versionMultipler));144 this.setUserVersion(actualVersion - VERSION * this.versionMultipler);
125 this.db();145 this.db();
126 }146 }
127 }147 }
128 148
129 buggyIDB(db) {149 buggyIDB(db) {
130 if (this.checkedBuggyIDB) { return; }150 if (this.checkedBuggyIDB) {
151 return;
152 }
131 this.checkedBuggyIDB = true;153 this.checkedBuggyIDB = true;
132 try {154 try {
133 this.idbTransaction(db, {stores: $.makeArray(db.objectStoreNames).slice(0, 2), mode: 'readwrite'}).abort(); // https://bugs.webkit.org/show_bug.cgi?id=136937155 this.idbTransaction(db, {
156 stores: $.makeArray(db.objectStoreNames).slice(0, 2),
157 mode: "readwrite",
158 }).abort(); // https://bugs.webkit.org/show_bug.cgi?id=136937
134 return;159 return;
135 } catch (error) {160 } catch (error) {
136 return error;161 return error;
@@ -139,53 +164,72 @@ · 2 unmodified lines
139 164
140 runCallbacks(db) {165 runCallbacks(db) {
141 let fn;166 let fn;
142 while ((fn = this.callbacks.shift())) { fn(db); }167 while ((fn = this.callbacks.shift())) {
168 fn(db);
169 }
143 }170 }
144 171
145 onUpgradeNeeded(event) {172 onUpgradeNeeded(event) {
146 let db;173 let db;
147 if (!(db = event.target.result)) { return; }174 if (!(db = event.target.result)) {
175 return;
176 }
148 177
149 const objectStoreNames = $.makeArray(db.objectStoreNames);178 const objectStoreNames = $.makeArray(db.objectStoreNames);
150 179
151 if (!$.arrayDelete(objectStoreNames, 'docs')) {180 if (!$.arrayDelete(objectStoreNames, "docs")) {
152 try { db.createObjectStore('docs'); } catch (error) {}181 try {
182 db.createObjectStore("docs");
183 } catch (error) {}
153 }184 }
154 185
155 for (var doc of Array.from(app.docs.all())) {186 for (var doc of Array.from(app.docs.all())) {
156 if (!$.arrayDelete(objectStoreNames, doc.slug)) {187 if (!$.arrayDelete(objectStoreNames, doc.slug)) {
157 try { db.createObjectStore(doc.slug); } catch (error1) {}188 try {
189 db.createObjectStore(doc.slug);
190 } catch (error1) {}
158 }191 }
159 }192 }
160 193
161 for (var name of Array.from(objectStoreNames)) {194 for (var name of Array.from(objectStoreNames)) {
162 try { db.deleteObjectStore(name); } catch (error2) {}195 try {
196 db.deleteObjectStore(name);
197 } catch (error2) {}
163 }198 }
164 }199 }
165 200
166 store(doc, data, onSuccess, onError, _retry) {201 store(doc, data, onSuccess, onError, _retry) {
167 if (_retry == null) { _retry = true; }202 if (_retry == null) {
168 this.db(db => {203 _retry = true;
204 }
205 this.db((db) => {
169 if (!db) {206 if (!db) {
170 onError();207 onError();
171 return;208 return;
172 }209 }
173 210
174 const txn = this.idbTransaction(db, {stores: ['docs', doc.slug], mode: 'readwrite', ignoreError: false});211 const txn = this.idbTransaction(db, {
212 stores: ["docs", doc.slug],
213 mode: "readwrite",
214 ignoreError: false,
215 });
175 txn.oncomplete = () => {216 txn.oncomplete = () => {
176 if (this.cachedDocs != null) {217 if (this.cachedDocs != null) {
177 this.cachedDocs[doc.slug] = doc.mtime;218 this.cachedDocs[doc.slug] = doc.mtime;
178 }219 }
179 onSuccess();220 onSuccess();
180 };221 };
181 txn.onerror = event => {222 txn.onerror = (event) => {
182 event.preventDefault();223 event.preventDefault();
183 if (((txn.error != null ? txn.error.name : undefined) === 'NotFoundError') && _retry) {224 if (
225 (txn.error != null ? txn.error.name : undefined) ===
226 "NotFoundError" &&
227 _retry
228 ) {
184 this.migrate();229 this.migrate();
185 setTimeout(() => {230 setTimeout(() => {
186 return this.store(doc, data, onSuccess, onError, false);231 return this.store(doc, data, onSuccess, onError, false);
187 }232 }, 0);
188 , 0);
189 } else {233 } else {
190 onError(event);234 onError(event);
191 }235 }
@@ -193,42 +237,54 @@ · 1 unmodified line
193 237
194 let store = txn.objectStore(doc.slug);238 let store = txn.objectStore(doc.slug);
195 store.clear();239 store.clear();
196 for (var path in data) { var content = data[path]; store.add(content, path); }240 for (var path in data) {
241 var content = data[path];
242 store.add(content, path);
243 }
197 244
198 store = txn.objectStore('docs');245 store = txn.objectStore("docs");
199 store.put(doc.mtime, doc.slug);246 store.put(doc.mtime, doc.slug);
200 });247 });
201 }248 }
202 249
203 unstore(doc, onSuccess, onError, _retry) {250 unstore(doc, onSuccess, onError, _retry) {
204 if (_retry == null) { _retry = true; }251 if (_retry == null) {
205 this.db(db => {252 _retry = true;
253 }
254 this.db((db) => {
206 if (!db) {255 if (!db) {
207 onError();256 onError();
208 return;257 return;
209 }258 }
210 259
211 const txn = this.idbTransaction(db, {stores: ['docs', doc.slug], mode: 'readwrite', ignoreError: false});260 const txn = this.idbTransaction(db, {
261 stores: ["docs", doc.slug],
262 mode: "readwrite",
263 ignoreError: false,
264 });
212 txn.oncomplete = () => {265 txn.oncomplete = () => {
213 if (this.cachedDocs != null) {266 if (this.cachedDocs != null) {
214 delete this.cachedDocs[doc.slug];267 delete this.cachedDocs[doc.slug];
215 }268 }
216 onSuccess();269 onSuccess();
217 };270 };
218 txn.onerror = function(event) {271 txn.onerror = function (event) {
219 event.preventDefault();272 event.preventDefault();
220 if (((txn.error != null ? txn.error.name : undefined) === 'NotFoundError') && _retry) {273 if (
274 (txn.error != null ? txn.error.name : undefined) ===
275 "NotFoundError" &&
276 _retry
277 ) {
221 this.migrate();278 this.migrate();
222 setTimeout(() => {279 setTimeout(() => {
223 return this.unstore(doc, onSuccess, onError, false);280 return this.unstore(doc, onSuccess, onError, false);
224 }281 }, 0);
225 , 0);
226 } else {282 } else {
227 onError(event);283 onError(event);
228 }284 }
229 };285 };
230 286
231 let store = txn.objectStore('docs');287 let store = txn.objectStore("docs");
232 store.delete(doc.slug);288 store.delete(doc.slug);
233 289
234 store = txn.objectStore(doc.slug);290 store = txn.objectStore(doc.slug);
@@ -243,20 +299,23 @@ · 8 unmodified lines
243 return;299 return;
244 }300 }
245 301
246 this.db(db => {302 this.db((db) => {
247 if (!db) {303 if (!db) {
248 fn(false);304 fn(false);
249 return;305 return;
250 }306 }
251 307
252 const txn = this.idbTransaction(db, {stores: ['docs'], mode: 'readonly'});308 const txn = this.idbTransaction(db, {
253 const store = txn.objectStore('docs');309 stores: ["docs"],
310 mode: "readonly",
311 });
312 const store = txn.objectStore("docs");
254 313
255 const req = store.get(doc.slug);314 const req = store.get(doc.slug);
256 req.onsuccess = function() {315 req.onsuccess = function () {
257 fn(req.result);316 fn(req.result);
258 };317 };
259 req.onerror = function(event) {318 req.onerror = function (event) {
260 event.preventDefault();319 event.preventDefault();
261 fn(false);320 fn(false);
262 };321 };
@@ -264,36 +323,41 @@ · 1 unmodified line
264 }323 }
265 324
266 cachedVersion(doc) {325 cachedVersion(doc) {
267 if (!this.cachedDocs) { return; }326 if (!this.cachedDocs) {
327 return;
328 }
268 return this.cachedDocs[doc.slug] || false;329 return this.cachedDocs[doc.slug] || false;
269 }330 }
270 331
271 versions(docs, fn) {332 versions(docs, fn) {
272 let versions;333 let versions;
273 if (versions = this.cachedVersions(docs)) {334 if ((versions = this.cachedVersions(docs))) {
274 fn(versions);335 fn(versions);
275 return;336 return;
276 }337 }
277 338
278 return this.db(db => {339 return this.db((db) => {
279 if (!db) {340 if (!db) {
280 fn(false);341 fn(false);
281 return;342 return;
282 }343 }
283 344
284 const txn = this.idbTransaction(db, {stores: ['docs'], mode: 'readonly'});345 const txn = this.idbTransaction(db, {
285 txn.oncomplete = function() {346 stores: ["docs"],
347 mode: "readonly",
348 });
349 txn.oncomplete = function () {
286 fn(result);350 fn(result);
287 };351 };
288 const store = txn.objectStore('docs');352 const store = txn.objectStore("docs");
289 var result = {};353 var result = {};
290 354
291 docs.forEach(function(doc) {355 docs.forEach(function (doc) {
292 const req = store.get(doc.slug);356 const req = store.get(doc.slug);
293 req.onsuccess = function() {357 req.onsuccess = function () {
294 result[doc.slug] = req.result;358 result[doc.slug] = req.result;
295 };359 };
296 req.onerror = function(event) {360 req.onerror = function (event) {
297 event.preventDefault();361 event.preventDefault();
298 result[doc.slug] = false;362 result[doc.slug] = false;
299 };363 };
@@ -302,9 +366,13 @@ · 2 unmodified lines
302 }366 }
303 367
304 cachedVersions(docs) {368 cachedVersions(docs) {
305 if (!this.cachedDocs) { return; }369 if (!this.cachedDocs) {
370 return;
371 }
306 const result = {};372 const result = {};
307 for (var doc of Array.from(docs)) { result[doc.slug] = this.cachedVersion(doc); }373 for (var doc of Array.from(docs)) {
374 result[doc.slug] = this.cachedVersion(doc);
375 }
308 return result;376 return result;
309 }377 }
310 378
@@ -320,14 +388,14 @@ · 9 unmodified lines
320 loadWithXHR(entry, onSuccess, onError) {388 loadWithXHR(entry, onSuccess, onError) {
321 return ajax({389 return ajax({
322 url: entry.fileUrl(),390 url: entry.fileUrl(),
323 dataType: 'html',391 dataType: "html",
324 success: onSuccess,392 success: onSuccess,
325 error: onError393 error: onError,
326 });394 });
327 }395 }
328 396
329 loadWithIDB(entry, onSuccess, onError) {397 loadWithIDB(entry, onSuccess, onError) {
330 return this.db(db => {398 return this.db((db) => {
331 if (!db) {399 if (!db) {
332 onError();400 onError();
333 return;401 return;
@@ -339,14 +407,21 @@ · 5 unmodified lines
339 return;407 return;
340 }408 }
341 409
342 const txn = this.idbTransaction(db, {stores: [entry.doc.slug], mode: 'readonly'});410 const txn = this.idbTransaction(db, {
411 stores: [entry.doc.slug],
412 mode: "readonly",
413 });
343 const store = txn.objectStore(entry.doc.slug);414 const store = txn.objectStore(entry.doc.slug);
344 415
345 const req = store.get(entry.dbPath());416 const req = store.get(entry.dbPath());
346 req.onsuccess = function() {417 req.onsuccess = function () {
347 if (req.result) { onSuccess(req.result); } else { onError(); }418 if (req.result) {
419 onSuccess(req.result);
420 } else {
421 onError();
422 }
348 };423 };
349 req.onerror = function(event) {424 req.onerror = function (event) {
350 event.preventDefault();425 event.preventDefault();
351 onError();426 onError();
352 };427 };
@@ -355,31 +430,38 @@ · 2 unmodified lines
355 }430 }
356 431
357 loadDocsCache(db) {432 loadDocsCache(db) {
358 if (this.cachedDocs) { return; }433 if (this.cachedDocs) {
434 return;
435 }
359 this.cachedDocs = {};436 this.cachedDocs = {};
360 437
361 const txn = this.idbTransaction(db, {stores: ['docs'], mode: 'readonly'});438 const txn = this.idbTransaction(db, {
439 stores: ["docs"],
440 mode: "readonly",
441 });
362 txn.oncomplete = () => {442 txn.oncomplete = () => {
363 setTimeout(this.checkForCorruptedDocs, 50);443 setTimeout(this.checkForCorruptedDocs, 50);
364 };444 };
365 445
366 const req = txn.objectStore('docs').openCursor();446 const req = txn.objectStore("docs").openCursor();
367 req.onsuccess = event => {447 req.onsuccess = (event) => {
368 let cursor;448 let cursor;
369 if (!(cursor = event.target.result)) { return; }449 if (!(cursor = event.target.result)) {
450 return;
451 }
370 this.cachedDocs[cursor.key] = cursor.value;452 this.cachedDocs[cursor.key] = cursor.value;
371 cursor.continue();453 cursor.continue();
372 };454 };
373 req.onerror = function(event) {455 req.onerror = function (event) {
374 event.preventDefault();456 event.preventDefault();
375 };457 };
376 }458 }
377 459
378 checkForCorruptedDocs() {460 checkForCorruptedDocs() {
379 this.db(db => {461 this.db((db) => {
380 let slug;462 let slug;
381 this.corruptedDocs = [];463 this.corruptedDocs = [];
382 const docs = ((() => {464 const docs = (() => {
383 const result = [];465 const result = [];
384 for (var key in this.cachedDocs) {466 for (var key in this.cachedDocs) {
385 var value = this.cachedDocs[key];467 var value = this.cachedDocs[key];
@@ -388,11 +470,13 @@ · 2 unmodified lines
388 }470 }
389 }471 }
390 return result;472 return result;
391 })());473 })();
392 if (docs.length === 0) { return; }474 if (docs.length === 0) {
475 return;
476 }
393 477
394 for (slug of Array.from(docs)) {478 for (slug of Array.from(docs)) {
395 if (!app.docs.findBy('slug', slug)) {479 if (!app.docs.findBy("slug", slug)) {
396 this.corruptedDocs.push(slug);480 this.corruptedDocs.push(slug);
397 }481 }
398 }482 }
@@ -406,46 +490,64 @@ · 7 unmodified lines
406 return;490 return;
407 }491 }
408 492
409 const txn = this.idbTransaction(db, {stores: docs, mode: 'readonly', ignoreError: false});493 const txn = this.idbTransaction(db, {
494 stores: docs,
495 mode: "readonly",
496 ignoreError: false,
497 });
410 txn.oncomplete = () => {498 txn.oncomplete = () => {
411 if (this.corruptedDocs.length > 0) { setTimeout(this.deleteCorruptedDocs, 0); }499 if (this.corruptedDocs.length > 0) {
500 setTimeout(this.deleteCorruptedDocs, 0);
501 }
412 };502 };
413 503
414 for (var doc of Array.from(docs)) {504 for (var doc of Array.from(docs)) {
415 txn.objectStore(doc).get('index').onsuccess = event => {505 txn.objectStore(doc).get("index").onsuccess = (event) => {
416 if (!event.target.result) { this.corruptedDocs.push(event.target.source.name); }506 if (!event.target.result) {
507 this.corruptedDocs.push(event.target.source.name);
508 }
417 };509 };
418 }510 }
419 });511 });
420 }512 }
421 513
422 deleteCorruptedDocs() {514 deleteCorruptedDocs() {
423 this.db(db => {515 this.db((db) => {
424 let doc;516 let doc;
425 const txn = this.idbTransaction(db, {stores: ['docs'], mode: 'readwrite', ignoreError: false});517 const txn = this.idbTransaction(db, {
426 const store = txn.objectStore('docs');518 stores: ["docs"],
519 mode: "readwrite",
520 ignoreError: false,
521 });
522 const store = txn.objectStore("docs");
427 while ((doc = this.corruptedDocs.pop())) {523 while ((doc = this.corruptedDocs.pop())) {
428 this.cachedDocs[doc] = false;524 this.cachedDocs[doc] = false;
429 store.delete(doc);525 store.delete(doc);
430 }526 }
431 });527 });
432 Raven.captureMessage('corruptedDocs', {level: 'info', extra: { docs: this.corruptedDocs.join(',') }});528 Raven.captureMessage("corruptedDocs", {
529 level: "info",
530 extra: { docs: this.corruptedDocs.join(",") },
531 });
433 }532 }
434 533
435 shouldLoadWithIDB(entry) {534 shouldLoadWithIDB(entry) {
436 return this.useIndexedDB && (!this.cachedDocs || this.cachedDocs[entry.doc.slug]);535 return (
536 this.useIndexedDB &&
537 (!this.cachedDocs || this.cachedDocs[entry.doc.slug])
538 );
437 }539 }
438 540
439 idbTransaction(db, options) {541 idbTransaction(db, options) {
440 app.lastIDBTransaction = [options.stores, options.mode];542 app.lastIDBTransaction = [options.stores, options.mode];
441 const txn = db.transaction(options.stores, options.mode);543 const txn = db.transaction(options.stores, options.mode);
442 if (options.ignoreError !== false) {544 if (options.ignoreError !== false) {
443 txn.onerror = function(event) {545 txn.onerror = function (event) {
444 event.preventDefault();546 event.preventDefault();
445 };547 };
446 }548 }
447 if (options.ignoreAbort !== false) {549 if (options.ignoreAbort !== false) {
448 txn.onabort = function(event) {550 txn.onabort = function (event) {
449 event.preventDefault();551 event.preventDefault();
450 };552 };
451 }553 }
@@ -453,9 +555,11 @@ · 1 unmodified line
453 }555 }
454 556
455 reset() {557 reset() {
456 try { if (typeof indexedDB !== 'undefined' && indexedDB !== null) {558 try {
457 indexedDB.deleteDatabase(NAME);559 if (typeof indexedDB !== "undefined" && indexedDB !== null) {
458 } } catch (error) {}560 indexedDB.deleteDatabase(NAME);
561 }
562 } catch (error) {}
459 }563 }
460 564
461 useIndexedDB() {565 useIndexedDB() {
@@ -463,7 +567,7 @@ · 1 unmodified line
463 if (!app.isSingleDoc() && window.indexedDB) {567 if (!app.isSingleDoc() && window.indexedDB) {
464 return true;568 return true;
465 } else {569 } else {
466 this.reason = 'not_supported';570 this.reason = "not_supported";
467 return false;571 return false;
468 }572 }
469 } catch (error) {573 } catch (error) {
@@ -472,15 +576,15 @@ · 2 unmodified lines
472 }576 }
473 577
474 migrate() {578 migrate() {
475 app.settings.set('schema', this.userVersion() + 1);579 app.settings.set("schema", this.userVersion() + 1);
476 }580 }
477 581
478 setUserVersion(version) {582 setUserVersion(version) {
479 app.settings.set('schema', version);583 app.settings.set("schema", version);
480 }584 }
481 585
482 userVersion() {586 userVersion() {
483 return app.settings.get('schema');587 return app.settings.get("schema");
484 }588 }
485 });589 });
486 Cls.initClass();590 Cls.initClass();
assets/javascripts/app/router.js+101−61
@@ -12,19 +12,19 @@ · 11 unmodified lines
12const Cls = (app.Router = class Router {12const Cls = (app.Router = class Router {
13 static initClass() {13 static initClass() {
14 $.extend(this.prototype, Events);14 $.extend(this.prototype, Events);
15 15
16 this.routes = [16 this.routes = [
17 ['*', 'before' ],17 ["*", "before"],
18 ['/', 'root' ],18 ["/", "root"],
19 ['/settings', 'settings' ],19 ["/settings", "settings"],
20 ['/offline', 'offline' ],20 ["/offline", "offline"],
21 ['/about', 'about' ],21 ["/about", "about"],
22 ['/news', 'news' ],22 ["/news", "news"],
23 ['/help', 'help' ],23 ["/help", "help"],
24 ['/:doc-:type/', 'type' ],24 ["/:doc-:type/", "type"],
25 ['/:doc/', 'doc' ],25 ["/:doc/", "doc"],
26 ['/:doc/:path(*)', 'entry' ],26 ["/:doc/:path(*)", "entry"],
27 ['*', 'notFound' ]27 ["*", "notFound"],
28 ];28 ];
29 }29 }
30 30
@@ -45,16 +45,16 @@ const Cls = (app.Router = class Router { · 14 unmodified lines
45 45
46 triggerRoute(name) {46 triggerRoute(name) {
47 this.trigger(name, this.context);47 this.trigger(name, this.context);
48 this.trigger('after', name, this.context);48 this.trigger("after", name, this.context);
49 }49 }
50 50
51 before(context, next) {51 before(context, next) {
52 let res;52 let res;
53 const previousContext = this.context;53 const previousContext = this.context;
54 this.context = context;54 this.context = context;
55 this.trigger('before', context);55 this.trigger("before", context);
56 56
57 if (res = next()) {57 if ((res = next())) {
58 this.context = previousContext;58 this.context = previousContext;
59 return res;59 return res;
60 } else {60 } else {
@@ -64,10 +64,14 @@ const Cls = (app.Router = class Router { · 3 unmodified lines
64 64
65 doc(context, next) {65 doc(context, next) {
66 let doc;66 let doc;
67 if (doc = app.docs.findBySlug(context.params.doc) || app.disabledDocs.findBySlug(context.params.doc)) {67 if (
68 (doc =
69 app.docs.findBySlug(context.params.doc) ||
70 app.disabledDocs.findBySlug(context.params.doc))
71 ) {
68 context.doc = doc;72 context.doc = doc;
69 context.entry = doc.toEntry();73 context.entry = doc.toEntry();
70 this.triggerRoute('entry');74 this.triggerRoute("entry");
71 return;75 return;
72 } else {76 } else {
73 return next();77 return next();
@@ -78,10 +82,13 @@ const Cls = (app.Router = class Router { · 4 unmodified lines
78 let type;82 let type;
79 const doc = app.docs.findBySlug(context.params.doc);83 const doc = app.docs.findBySlug(context.params.doc);
80 84
81 if (type = doc != null ? doc.types.findBy('slug', context.params.type) : undefined) {85 if (
86 (type =
87 doc != null ? doc.types.findBy("slug", context.params.type) : undefined)
88 ) {
82 context.doc = doc;89 context.doc = doc;
83 context.type = type;90 context.type = type;
84 this.triggerRoute('type');91 this.triggerRoute("type");
85 return;92 return;
86 } else {93 } else {
87 return next();94 return next();
@@ -91,86 +98,110 @@ const Cls = (app.Router = class Router { · 3 unmodified lines
91 entry(context, next) {98 entry(context, next) {
92 let entry;99 let entry;
93 const doc = app.docs.findBySlug(context.params.doc);100 const doc = app.docs.findBySlug(context.params.doc);
94 if (!doc) { return next(); }101 if (!doc) {
95 let {102 return next();
96 path103 }
97 } = context.params;104 let { path } = context.params;
98 const {105 const { hash } = context;
99 hash106
100 } = context;107 if ((entry = doc.findEntryByPathAndHash(path, hash))) {
101
102 if (entry = doc.findEntryByPathAndHash(path, hash)) {
103 context.doc = doc;108 context.doc = doc;
104 context.entry = entry;109 context.entry = entry;
105 this.triggerRoute('entry');110 this.triggerRoute("entry");
106 return;111 return;
107 } else if (path.slice(-6) === '/index') {112 } else if (path.slice(-6) === "/index") {
108 path = path.substr(0, path.length - 6);113 path = path.substr(0, path.length - 6);
109 if (entry = doc.findEntryByPathAndHash(path, hash)) { return entry.fullPath(); }114 if ((entry = doc.findEntryByPathAndHash(path, hash))) {
115 return entry.fullPath();
116 }
110 } else {117 } else {
111 path = `${path}/index`;118 path = `${path}/index`;
112 if (entry = doc.findEntryByPathAndHash(path, hash)) { return entry.fullPath(); }119 if ((entry = doc.findEntryByPathAndHash(path, hash))) {
120 return entry.fullPath();
121 }
113 }122 }
114 123
115 return next();124 return next();
116 }125 }
117 126
118 root() {127 root() {
119 if (app.isSingleDoc()) { return '/'; }128 if (app.isSingleDoc()) {
120 this.triggerRoute('root');129 return "/";
130 }
131 this.triggerRoute("root");
121 }132 }
122 133
123 settings(context) {134 settings(context) {
124 if (app.isSingleDoc()) { return `/#/${context.path}`; }135 if (app.isSingleDoc()) {
125 this.triggerRoute('settings');136 return `/#/${context.path}`;
137 }
138 this.triggerRoute("settings");
126 }139 }
127 140
128 offline(context){141 offline(context) {
129 if (app.isSingleDoc()) { return `/#/${context.path}`; }142 if (app.isSingleDoc()) {
130 this.triggerRoute('offline');143 return `/#/${context.path}`;
144 }
145 this.triggerRoute("offline");
131 }146 }
132 147
133 about(context) {148 about(context) {
134 if (app.isSingleDoc()) { return `/#/${context.path}`; }149 if (app.isSingleDoc()) {
135 context.page = 'about';150 return `/#/${context.path}`;
136 this.triggerRoute('page');151 }
152 context.page = "about";
153 this.triggerRoute("page");
137 }154 }
138 155
139 news(context) {156 news(context) {
140 if (app.isSingleDoc()) { return `/#/${context.path}`; }157 if (app.isSingleDoc()) {
141 context.page = 'news';158 return `/#/${context.path}`;
142 this.triggerRoute('page');159 }
160 context.page = "news";
161 this.triggerRoute("page");
143 }162 }
144 163
145 help(context) {164 help(context) {
146 if (app.isSingleDoc()) { return `/#/${context.path}`; }165 if (app.isSingleDoc()) {
147 context.page = 'help';166 return `/#/${context.path}`;
148 this.triggerRoute('page');167 }
168 context.page = "help";
169 this.triggerRoute("page");
149 }170 }
150 171
151 notFound(context) {172 notFound(context) {
152 this.triggerRoute('notFound');173 this.triggerRoute("notFound");
153 }174 }
154 175
155 isIndex() {176 isIndex() {
156 return ((this.context != null ? this.context.path : undefined) === '/') || (app.isSingleDoc() && __guard__(this.context != null ? this.context.entry : undefined, x => x.isIndex()));177 return (
178 (this.context != null ? this.context.path : undefined) === "/" ||
179 (app.isSingleDoc() &&
180 __guard__(this.context != null ? this.context.entry : undefined, (x) =>
181 x.isIndex(),
182 ))
183 );
157 }184 }
158 185
159 isSettings() {186 isSettings() {
160 return (this.context != null ? this.context.path : undefined) === '/settings';187 return (
188 (this.context != null ? this.context.path : undefined) === "/settings"
189 );
161 }190 }
162 191
163 setInitialPath() {192 setInitialPath() {
164 // Remove superfluous forward slashes at the beginning of the path193 // Remove superfluous forward slashes at the beginning of the path
165 let path;194 let path;
166 if ((path = location.pathname.replace(/^\/{2,}/g, '/')) !== location.pathname) {195 if (
196 (path = location.pathname.replace(/^\/{2,}/g, "/")) !== location.pathname
197 ) {
167 page.replace(path + location.search + location.hash, null, true);198 page.replace(path + location.search + location.hash, null, true);
168 }199 }
169 200
170 if (location.pathname === '/') {201 if (location.pathname === "/") {
171 if (path = this.getInitialPathFromHash()) {202 if ((path = this.getInitialPathFromHash())) {
172 page.replace(path + location.search, null, true);203 page.replace(path + location.search, null, true);
173 } else if (path = this.getInitialPathFromCookie()) {204 } else if ((path = this.getInitialPathFromCookie())) {
174 page.replace(path + location.search + location.hash, null, true);205 page.replace(path + location.search + location.hash, null, true);
175 }206 }
176 }207 }
@@ -178,24 +209,33 @@ const Cls = (app.Router = class Router { · 1 unmodified line
178 209
179 getInitialPathFromHash() {210 getInitialPathFromHash() {
180 try {211 try {
181 return __guard__((new RegExp("#/(.+)")).exec(decodeURIComponent(location.hash)), x => x[1]);212 return __guard__(
213 new RegExp("#/(.+)").exec(decodeURIComponent(location.hash)),
214 (x) => x[1],
215 );
182 } catch (error) {}216 } catch (error) {}
183 }217 }
184 218
185 getInitialPathFromCookie() {219 getInitialPathFromCookie() {
186 let path;220 let path;
187 if (path = Cookies.get('initial_path')) {221 if ((path = Cookies.get("initial_path"))) {
188 Cookies.expire('initial_path');222 Cookies.expire("initial_path");
189 return path;223 return path;
190 }224 }
191 }225 }
192 226
193 replaceHash(hash) {227 replaceHash(hash) {
194 page.replace(location.pathname + location.search + (hash || ''), null, true);228 page.replace(
229 location.pathname + location.search + (hash || ""),
230 null,
231 true,
232 );
195 }233 }
196});234});
197Cls.initClass();235Cls.initClass();
198 236
199function __guard__(value, transform) {237function __guard__(value, transform) {
200 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;238 return typeof value !== "undefined" && value !== null
201}239 ? transform(value)
240 : undefined;
241}
assets/javascripts/app/searcher.js+190−118
@@ -16,116 +16,150 @@ · 15 unmodified lines
16// Match functions16// Match functions
17//17//
18 18
19let fuzzyRegexp, i, index, lastIndex, match, matcher, matchIndex, matchLength, queryLength, score, separators, value, valueLength;19let fuzzyRegexp,
20const SEPARATOR = '.';20 i,
21 index,
22 lastIndex,
23 match,
24 matcher,
25 matchIndex,
26 matchLength,
27 queryLength,
28 score,
29 separators,
30 value,
31 valueLength;
32const SEPARATOR = ".";
21 33
22let query =34let query =
23(queryLength =35 (queryLength =
24(value =36 value =
25(valueLength =37 valueLength =
26(matcher = // current match function38 matcher = // current match function
27(fuzzyRegexp = // query fuzzy regexp39 fuzzyRegexp = // query fuzzy regexp
28(index = // position of the query in the string being matched40 index = // position of the query in the string being matched
29(lastIndex = // last position of the query in the string being matched41 lastIndex = // last position of the query in the string being matched
30(match = // regexp match data42 match = // regexp match data
31(matchIndex =43 matchIndex =
32(matchLength =44 matchLength =
33(score = // score for the current match45 score = // score for the current match
34(separators = // counter46 separators = // counter
35(i = null))))))))))))); // cursor47 i =
48 null); // cursor
36 49
37function exactMatch() {50function exactMatch() {
38index = value.indexOf(query);51 index = value.indexOf(query);
39if (!(index >= 0)) { return; }52 if (!(index >= 0)) {
53 return;
54 }
40 55
41lastIndex = value.lastIndexOf(query);56 lastIndex = value.lastIndexOf(query);
42 57
43if (index !== lastIndex) {58 if (index !== lastIndex) {
44 return Math.max(scoreExactMatch(), ((index = lastIndex) && scoreExactMatch()) || 0);59 return Math.max(
45} else {60 scoreExactMatch(),
46 return scoreExactMatch();61 ((index = lastIndex) && scoreExactMatch()) || 0,
47}62 );
63 } else {
64 return scoreExactMatch();
65 }
48}66}
49 67
50function scoreExactMatch() {68function scoreExactMatch() {
51// Remove one point for each unmatched character.69 // Remove one point for each unmatched character.
52score = 100 - (valueLength - queryLength);70 score = 100 - (valueLength - queryLength);
53 71
54if (index > 0) {72 if (index > 0) {
55 // If the character preceding the query is a dot, assign the same score73 // If the character preceding the query is a dot, assign the same score
56 // as if the query was found at the beginning of the string, minus one.74 // as if the query was found at the beginning of the string, minus one.
57 if (value.charAt(index - 1) === SEPARATOR) {75 if (value.charAt(index - 1) === SEPARATOR) {
58 score += index - 1;76 score += index - 1;
59 // Don't match a single-character query unless it's found at the beginning77 // Don't match a single-character query unless it's found at the beginning
60 // of the string or is preceded by a dot.78 // of the string or is preceded by a dot.
61 } else if (queryLength === 1) {79 } else if (queryLength === 1) {
62 return;80 return;
63 // (1) Remove one point for each unmatched character up to the nearest81 // (1) Remove one point for each unmatched character up to the nearest
64 // preceding dot or the beginning of the string.82 // preceding dot or the beginning of the string.
65 // (2) Remove one point for each unmatched character following the query.83 // (2) Remove one point for each unmatched character following the query.
66 } else {84 } else {
85 i = index - 2;
86 while (i >= 0 && value.charAt(i) !== SEPARATOR) {
87 i--;
88 }
89 score -=
90 index -
91 i + // (1)
92 (valueLength - queryLength - index); // (2)
93 }
94
95 // Remove one point for each dot preceding the query, except for the one
96 // immediately before the query.
97 separators = 0;
67 i = index - 2;98 i = index - 2;
68 while ((i >= 0) && (value.charAt(i) !== SEPARATOR)) { i--; }99 while (i >= 0) {
69 score -= (index - i) + // (1)100 if (value.charAt(i) === SEPARATOR) {
70 (valueLength - queryLength - index); // (2)101 separators++;
102 }
103 i--;
104 }
105 score -= separators;
71 }106 }
72 107
73 // Remove one point for each dot preceding the query, except for the one108 // Remove five points for each dot following the query.
74 // immediately before the query.
75 separators = 0;109 separators = 0;
76 i = index - 2;110 i = valueLength - queryLength - index - 1;
77 while (i >= 0) {111 while (i >= 0) {
78 if (value.charAt(i) === SEPARATOR) { separators++; }112 if (value.charAt(index + queryLength + i) === SEPARATOR) {
113 separators++;
114 }
79 i--;115 i--;
80 }116 }
81 score -= separators;117 score -= separators * 5;
82}
83
84// Remove five points for each dot following the query.
85separators = 0;
86i = valueLength - queryLength - index - 1;
87while (i >= 0) {
88 if (value.charAt(index + queryLength + i) === SEPARATOR) { separators++; }
89 i--;
90}
91score -= separators * 5;
92 118
93return Math.max(1, score);119 return Math.max(1, score);
94}120}
95 121
96function fuzzyMatch() {122function fuzzyMatch() {
97if ((valueLength <= queryLength) || (value.indexOf(query) >= 0)) { return; }123 if (valueLength <= queryLength || value.indexOf(query) >= 0) {
98if (!(match = fuzzyRegexp.exec(value))) { return; }124 return;
99matchIndex = match.index;125 }
100matchLength = match[0].length;126 if (!(match = fuzzyRegexp.exec(value))) {
101score = scoreFuzzyMatch();127 return;
102if (match = fuzzyRegexp.exec(value.slice(i = value.lastIndexOf(SEPARATOR) + 1))) {128 }
103 matchIndex = i + match.index;129 matchIndex = match.index;
104 matchLength = match[0].length;130 matchLength = match[0].length;
105 return Math.max(score, scoreFuzzyMatch());131 score = scoreFuzzyMatch();
106} else {132 if (
107 return score;133 (match = fuzzyRegexp.exec(
108}134 value.slice((i = value.lastIndexOf(SEPARATOR) + 1)),
135 ))
136 ) {
137 matchIndex = i + match.index;
138 matchLength = match[0].length;
139 return Math.max(score, scoreFuzzyMatch());
140 } else {
141 return score;
142 }
109}143}
110 144
111function scoreFuzzyMatch() {145function scoreFuzzyMatch() {
112// When the match is at the beginning of the string or preceded by a dot.146 // When the match is at the beginning of the string or preceded by a dot.
113if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) {147 if (matchIndex === 0 || value.charAt(matchIndex - 1) === SEPARATOR) {
114 return Math.max(66, 100 - matchLength);148 return Math.max(66, 100 - matchLength);
115// When the match is at the end of the string.149 // When the match is at the end of the string.
116} else if ((matchIndex + matchLength) === valueLength) {150 } else if (matchIndex + matchLength === valueLength) {
117 return Math.max(33, 67 - matchLength);151 return Math.max(33, 67 - matchLength);
118// When the match is in the middle of the string.152 // When the match is in the middle of the string.
119} else {153 } else {
120 return Math.max(1, 34 - matchLength);154 return Math.max(1, 34 - matchLength);
121}155 }
122}156}
123 157
124//158//
125// Searchers159// Searchers
126//160//
127 161
128(function() {162(function () {
129 let CHUNK_SIZE = undefined;163 let CHUNK_SIZE = undefined;
130 let DEFAULTS = undefined;164 let DEFAULTS = undefined;
131 let SEPARATORS_REGEXP = undefined;165 let SEPARATORS_REGEXP = undefined;
@@ -141,25 +175,26 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 9 unmodified lines
141 const Cls = (app.Searcher = class Searcher {175 const Cls = (app.Searcher = class Searcher {
142 static initClass() {176 static initClass() {
143 $.extend(this.prototype, Events);177 $.extend(this.prototype, Events);
144 178
145 CHUNK_SIZE = 20000;179 CHUNK_SIZE = 20000;
146 180
147 DEFAULTS = {181 DEFAULTS = {
148 max_results: app.config.max_results,182 max_results: app.config.max_results,
149 fuzzy_min_length: 3183 fuzzy_min_length: 3,
150 };184 };
151 185
152 SEPARATORS_REGEXP = /#|::|:-|->|\$(?=\w)|\-(?=\w)|\:(?=\w)|\ [\/\-&]\ |:\ |\ /g;186 SEPARATORS_REGEXP =
187 /#|::|:-|->|\$(?=\w)|\-(?=\w)|\:(?=\w)|\ [\/\-&]\ |:\ |\ /g;
153 EOS_SEPARATORS_REGEXP = /(\w)[\-:]$/;188 EOS_SEPARATORS_REGEXP = /(\w)[\-:]$/;
154 INFO_PARANTHESES_REGEXP = /\ \(\w+?\)$/;189 INFO_PARANTHESES_REGEXP = /\ \(\w+?\)$/;
155 EMPTY_PARANTHESES_REGEXP = /\(\)/;190 EMPTY_PARANTHESES_REGEXP = /\(\)/;
156 EVENT_REGEXP = /\ event$/;191 EVENT_REGEXP = /\ event$/;
157 DOT_REGEXP = /\.+/g;192 DOT_REGEXP = /\.+/g;
158 WHITESPACE_REGEXP = /\s/g;193 WHITESPACE_REGEXP = /\s/g;
159 194
160 EMPTY_STRING = '';195 EMPTY_STRING = "";
161 ELLIPSIS = '...';196 ELLIPSIS = "...";
162 STRING = 'string';197 STRING = "string";
163 }198 }
164 199
165 static normalizeString(string) {200 static normalizeString(string) {
@@ -176,13 +211,15 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 10 unmodified lines
176 211
177 static normalizeQuery(string) {212 static normalizeQuery(string) {
178 string = this.normalizeString(string);213 string = this.normalizeString(string);
179 return string.replace(EOS_SEPARATORS_REGEXP, '$1.');214 return string.replace(EOS_SEPARATORS_REGEXP, "$1.");
180 }215 }
181 216
182 constructor(options) {217 constructor(options) {
183 this.match = this.match.bind(this);218 this.match = this.match.bind(this);
184 this.matchChunks = this.matchChunks.bind(this);219 this.matchChunks = this.matchChunks.bind(this);
185 if (options == null) { options = {}; }220 if (options == null) {
221 options = {};
222 }
186 this.options = $.extend({}, DEFAULTS, options);223 this.options = $.extend({}, DEFAULTS, options);
187 }224 }
188 225
@@ -194,11 +231,15 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 5 unmodified lines
194 this.query = q;231 this.query = q;
195 this.setup();232 this.setup();
196 233
197 if (this.isValid()) { this.match(); } else { this.end(); }234 if (this.isValid()) {
235 this.match();
236 } else {
237 this.end();
238 }
198 }239 }
199 240
200 setup() {241 setup() {
201 query = (this.query = this.constructor.normalizeQuery(this.query));242 query = this.query = this.constructor.normalizeQuery(this.query);
202 queryLength = query.length;243 queryLength = query.length;
203 this.dataLength = this.data.length;244 this.dataLength = this.data.length;
204 this.matchers = [exactMatch];245 this.matchers = [exactMatch];
@@ -216,12 +257,14 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 11 unmodified lines
216 }257 }
217 258
218 isValid() {259 isValid() {
219 return (queryLength > 0) && (query !== SEPARATOR);260 return queryLength > 0 && query !== SEPARATOR;
220 }261 }
221 262
222 end() {263 end() {
223 if (!this.totalResults) { this.triggerResults([]); }264 if (!this.totalResults) {
224 this.trigger('end');265 this.triggerResults([]);
266 }
267 this.trigger("end");
225 this.free();268 this.free();
226 }269 }
227 270
@@ -233,8 +276,17 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 5 unmodified lines
233 }276 }
234 277
235 free() {278 free() {
236 this.data = (this.attr = (this.dataLength = (this.matchers = (this.matcher = (this.query =279 this.data =
237 (this.totalResults = (this.scoreMap = (this.cursor = (this.timeout = null)))))))));280 this.attr =
281 this.dataLength =
282 this.matchers =
283 this.matcher =
284 this.query =
285 this.totalResults =
286 this.scoreMap =
287 this.cursor =
288 this.timeout =
289 null;
238 }290 }
239 291
240 match() {292 match() {
@@ -254,7 +306,7 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 13 unmodified lines
254 matchChunks() {306 matchChunks() {
255 this.matchChunk();307 this.matchChunk();
256 308
257 if ((this.cursor === this.dataLength) || this.scoredEnough()) {309 if (this.cursor === this.dataLength || this.scoredEnough()) {
258 this.delay(this.match);310 this.delay(this.match);
259 this.sendResults();311 this.sendResults();
260 } else {312 } else {
@@ -263,28 +315,36 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 2 unmodified lines
263 }315 }
264 316
265 matchChunk() {317 matchChunk() {
266 ({318 ({ matcher } = this);
267 matcher319 for (
268 } = this);320 let j = 0, end = this.chunkSize(), asc = 0 <= end;
269 for (let j = 0, end = this.chunkSize(), asc = 0 <= end; asc ? j < end : j > end; asc ? j++ : j--) {321 asc ? j < end : j > end;
322 asc ? j++ : j--
323 ) {
270 value = this.data[this.cursor][this.attr];324 value = this.data[this.cursor][this.attr];
271 if (value.split) { // string325 if (value.split) {
326 // string
272 valueLength = value.length;327 valueLength = value.length;
273 if (score = matcher()) { this.addResult(this.data[this.cursor], score); }328 if ((score = matcher())) {
274 } else { // array329 this.addResult(this.data[this.cursor], score);
330 }
331 } else {
332 // array
275 score = 0;333 score = 0;
276 for (value of Array.from(this.data[this.cursor][this.attr])) {334 for (value of Array.from(this.data[this.cursor][this.attr])) {
277 valueLength = value.length;335 valueLength = value.length;
278 score = Math.max(score, matcher() || 0);336 score = Math.max(score, matcher() || 0);
279 }337 }
280 if (score > 0) { this.addResult(this.data[this.cursor], score); }338 if (score > 0) {
339 this.addResult(this.data[this.cursor], score);
340 }
281 }341 }
282 this.cursor++;342 this.cursor++;
283 }343 }
284 }344 }
285 345
286 chunkSize() {346 chunkSize() {
287 if ((this.cursor + CHUNK_SIZE) > this.dataLength) {347 if (this.cursor + CHUNK_SIZE > this.dataLength) {
288 return this.dataLength % CHUNK_SIZE;348 return this.dataLength % CHUNK_SIZE;
289 } else {349 } else {
290 return CHUNK_SIZE;350 return CHUNK_SIZE;
@@ -292,8 +352,11 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 1 unmodified line
292 }352 }
293 353
294 scoredEnough() {354 scoredEnough() {
295 return (this.scoreMap[100] != null ? this.scoreMap[100].length : undefined) >= this.options.max_results;355 return (
296 }356 (this.scoreMap[100] != null ? this.scoreMap[100].length : undefined) >=
357 this.options.max_results
358 );
359 }
297 360
298 foundEnough() {361 foundEnough() {
299 return this.totalResults >= this.options.max_results;362 return this.totalResults >= this.options.max_results;
@@ -301,7 +364,9 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 1 unmodified line
301 364
302 addResult(object, score) {365 addResult(object, score) {
303 let name;366 let name;
304 (this.scoreMap[name = Math.round(score)] || (this.scoreMap[name] = [])).push(object);367 (
368 this.scoreMap[(name = Math.round(score))] || (this.scoreMap[name] = [])
369 ).push(object);
305 this.totalResults++;370 this.totalResults++;
306 }371 }
307 372
@@ -318,21 +383,26 @@ if ((matchIndex === 0) || (value.charAt(matchIndex - 1) === SEPARATOR)) { · 10 unmodified lines
318 383
319 sendResults() {384 sendResults() {
320 const results = this.getResults();385 const results = this.getResults();
321 if (results.length) { this.triggerResults(results); }386 if (results.length) {
387 this.triggerResults(results);
388 }
322 }389 }
323 390
324 triggerResults(results) {391 triggerResults(results) {
325 this.trigger('results', results);392 this.trigger("results", results);
326 }393 }
327 394
328 delay(fn) {395 delay(fn) {
329 return this.timeout = setTimeout(fn, 1);396 return (this.timeout = setTimeout(fn, 1));
330 }397 }
331 398
332 queryToFuzzyRegexp(string) {399 queryToFuzzyRegexp(string) {
333 const chars = string.split('');400 const chars = string.split("");
334 for (i = 0; i < chars.length; i++) { var char = chars[i]; chars[i] = $.escapeRegexp(char); }401 for (i = 0; i < chars.length; i++) {
335 return new RegExp(chars.join('.*?'));402 var char = chars[i];
403 chars[i] = $.escapeRegexp(char);
404 }
405 return new RegExp(chars.join(".*?"));
336 }406 }
337 });407 });
338 Cls.initClass();408 Cls.initClass();
@@ -347,7 +417,9 @@ app.SynchronousSearcher = class SynchronousSearcher extends app.Searcher { · 8 unmodified lines
347 417
348 match() {418 match() {
349 if (this.matcher) {419 if (this.matcher) {
350 if (!this.allResults) { this.allResults = []; }420 if (!this.allResults) {
421 this.allResults = [];
422 }
351 this.allResults.push.apply(this.allResults, this.getResults());423 this.allResults.push.apply(this.allResults, this.getResults());
352 }424 }
353 return super.match(...arguments);425 return super.match(...arguments);
assets/javascripts/app/serviceworker.js+26−12
@@ -21,22 +21,28 @@ const Cls = (app.ServiceWorker = class ServiceWorker { · 20 unmodified lines
21 this.registration = null;21 this.registration = null;
22 this.notifyUpdate = true;22 this.notifyUpdate = true;
23 23
24 navigator.serviceWorker.register(app.config.service_worker_path, {scope: '/'})24 navigator.serviceWorker
25 .register(app.config.service_worker_path, { scope: "/" })
25 .then(26 .then(
26 registration => this.updateRegistration(registration),27 (registration) => this.updateRegistration(registration),
27 error => console.error('Could not register service worker:', error));28 (error) => console.error("Could not register service worker:", error),
29 );
28 }30 }
29 31
30 update() {32 update() {
31 if (!this.registration) { return; }33 if (!this.registration) {
34 return;
35 }
32 this.notifyUpdate = true;36 this.notifyUpdate = true;
33 return this.registration.update().catch(function() {});37 return this.registration.update().catch(function () {});
34 }38 }
35 39
36 updateInBackground() {40 updateInBackground() {
37 if (!this.registration) { return; }41 if (!this.registration) {
42 return;
43 }
38 this.notifyUpdate = false;44 this.notifyUpdate = false;
39 return this.registration.update().catch(function() {});45 return this.registration.update().catch(function () {});
40 }46 }
41 47
42 reload() {48 reload() {
@@ -45,24 +51,32 @@ const Cls = (app.ServiceWorker = class ServiceWorker { · 2 unmodified lines
45 51
46 updateRegistration(registration) {52 updateRegistration(registration) {
47 this.registration = registration;53 this.registration = registration;
48 $.on(this.registration, 'updatefound', this.onUpdateFound);54 $.on(this.registration, "updatefound", this.onUpdateFound);
49 }55 }
50 56
51 onUpdateFound() {57 onUpdateFound() {
52 if (this.installingRegistration) { $.off(this.installingRegistration, 'statechange', this.onStateChange()); }58 if (this.installingRegistration) {
59 $.off(this.installingRegistration, "statechange", this.onStateChange());
60 }
53 this.installingRegistration = this.registration.installing;61 this.installingRegistration = this.registration.installing;
54 $.on(this.installingRegistration, 'statechange', this.onStateChange);62 $.on(this.installingRegistration, "statechange", this.onStateChange);
55 }63 }
56 64
57 onStateChange() {65 onStateChange() {
58 if (this.installingRegistration && (this.installingRegistration.state === 'installed') && navigator.serviceWorker.controller) {66 if (
67 this.installingRegistration &&
68 this.installingRegistration.state === "installed" &&
69 navigator.serviceWorker.controller
70 ) {
59 this.installingRegistration = null;71 this.installingRegistration = null;
60 this.onUpdateReady();72 this.onUpdateReady();
61 }73 }
62 }74 }
63 75
64 onUpdateReady() {76 onUpdateReady() {
65 if (this.notifyUpdate) { this.trigger('updateready'); }77 if (this.notifyUpdate) {
78 this.trigger("updateready");
79 }
66 }80 }
67});81});
68Cls.initClass();82Cls.initClass();
assets/javascripts/app/settings.js+111−80
@@ -10,44 +10,39 @@ · 9 unmodified lines
10 * DS207: Consider shorter variations of null checks10 * DS207: Consider shorter variations of null checks
11 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md11 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
12 */12 */
13(function() {13(function () {
14 let PREFERENCE_KEYS = undefined;14 let PREFERENCE_KEYS = undefined;
15 let INTERNAL_KEYS = undefined;15 let INTERNAL_KEYS = undefined;
16 const Cls = (app.Settings = class Settings {16 const Cls = (app.Settings = class Settings {
17 static initClass() {17 static initClass() {
18 PREFERENCE_KEYS = [18 PREFERENCE_KEYS = [
19 'hideDisabled',19 "hideDisabled",
20 'hideIntro',20 "hideIntro",
21 'manualUpdate',21 "manualUpdate",
22 'fastScroll',22 "fastScroll",
23 'arrowScroll',23 "arrowScroll",
24 'analyticsConsent',24 "analyticsConsent",
25 'docs',25 "docs",
26 'dark', // legacy26 "dark", // legacy
27 'theme',27 "theme",
28 'layout',28 "layout",
29 'size',29 "size",
30 'tips',30 "tips",
31 'noAutofocus',31 "noAutofocus",
32 'autoInstall',32 "autoInstall",
33 'spaceScroll',33 "spaceScroll",
34 'spaceTimeout'34 "spaceTimeout",
35 ];35 ];
36 36
37 INTERNAL_KEYS = [37 INTERNAL_KEYS = ["count", "schema", "version", "news"];
38 'count',38
39 'schema',
40 'version',
41 'news'
42 ];
43
44 this.prototype.LAYOUTS = [39 this.prototype.LAYOUTS = [
45 '_max-width',40 "_max-width",
46 '_sidebar-hidden',41 "_sidebar-hidden",
47 '_native-scrollbars',42 "_native-scrollbars",
48 '_text-justify-hyphenate'43 "_text-justify-hyphenate",
49 ];44 ];
50 45
51 this.defaults = {46 this.defaults = {
52 count: 0,47 count: 0,
53 hideDisabled: false,48 hideDisabled: false,
@@ -56,29 +51,38 @@ · 2 unmodified lines
56 manualUpdate: false,51 manualUpdate: false,
57 schema: 1,52 schema: 1,
58 analyticsConsent: false,53 analyticsConsent: false,
59 theme: 'auto',54 theme: "auto",
60 spaceScroll: 1,55 spaceScroll: 1,
61 spaceTimeout: 0.556 spaceTimeout: 0.5,
62 };57 };
63 }58 }
64 59
65 constructor() {60 constructor() {
66 this.store = new CookiesStore;61 this.store = new CookiesStore();
67 this.cache = {};62 this.cache = {};
68 this.autoSupported = window.matchMedia('(prefers-color-scheme)').media !== 'not all';63 this.autoSupported =
64 window.matchMedia("(prefers-color-scheme)").media !== "not all";
69 if (this.autoSupported) {65 if (this.autoSupported) {
70 this.darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');66 this.darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
71 this.darkModeQuery.addListener(() => this.setTheme(this.get('theme')));67 this.darkModeQuery.addListener(() => this.setTheme(this.get("theme")));
72 }68 }
73 }69 }
74 70
75
76 get(key) {71 get(key) {
77 let left;72 let left;
78 if (this.cache.hasOwnProperty(key)) { return this.cache[key]; }73 if (this.cache.hasOwnProperty(key)) {
79 this.cache[key] = (left = this.store.get(key)) != null ? left : this.constructor.defaults[key];74 return this.cache[key];
80 if ((key === 'theme') && (this.cache[key] === 'auto') && !this.darkModeQuery) {75 }
81 return this.cache[key] = 'default';76 this.cache[key] =
77 (left = this.store.get(key)) != null
78 ? left
79 : this.constructor.defaults[key];
80 if (
81 key === "theme" &&
82 this.cache[key] === "auto" &&
83 !this.darkModeQuery
84 ) {
85 return (this.cache[key] = "default");
82 } else {86 } else {
83 return this.cache[key];87 return this.cache[key];
84 }88 }
@@ -87,7 +91,9 @@ · 2 unmodified lines
87 set(key, value) {91 set(key, value) {
88 this.store.set(key, value);92 this.store.set(key, value);
89 delete this.cache[key];93 delete this.cache[key];
90 if (key === 'theme') { this.setTheme(value); }94 if (key === "theme") {
95 this.setTheme(value);
96 }
91 }97 }
92 98
93 del(key) {99 del(key) {
@@ -96,51 +102,58 @@ · 2 unmodified lines
96 }102 }
97 103
98 hasDocs() {104 hasDocs() {
99 try { return !!this.store.get('docs'); } catch (error) {}105 try {
106 return !!this.store.get("docs");
107 } catch (error) {}
100 }108 }
101 109
102 getDocs() {110 getDocs() {
103 return __guard__(this.store.get('docs'), x => x.split('/')) || app.config.default_docs;111 return (
112 __guard__(this.store.get("docs"), (x) => x.split("/")) ||
113 app.config.default_docs
114 );
104 }115 }
105 116
106 setDocs(docs) {117 setDocs(docs) {
107 this.set('docs', docs.join('/'));118 this.set("docs", docs.join("/"));
108 }119 }
109 120
110 getTips() {121 getTips() {
111 return __guard__(this.store.get('tips'), x => x.split('/')) || [];122 return __guard__(this.store.get("tips"), (x) => x.split("/")) || [];
112 }123 }
113 124
114 setTips(tips) {125 setTips(tips) {
115 this.set('tips', tips.join('/'));126 this.set("tips", tips.join("/"));
116 }127 }
117 128
118 setLayout(name, enable) {129 setLayout(name, enable) {
119 this.toggleLayout(name, enable);130 this.toggleLayout(name, enable);
120 131
121 const layout = (this.store.get('layout') || '').split(' ');132 const layout = (this.store.get("layout") || "").split(" ");
122 $.arrayDelete(layout, '');133 $.arrayDelete(layout, "");
123 134
124 if (enable) {135 if (enable) {
125 if (layout.indexOf(name) === -1) { layout.push(name); }136 if (layout.indexOf(name) === -1) {
137 layout.push(name);
138 }
126 } else {139 } else {
127 $.arrayDelete(layout, name);140 $.arrayDelete(layout, name);
128 }141 }
129 142
130 if (layout.length > 0) {143 if (layout.length > 0) {
131 this.set('layout', layout.join(' '));144 this.set("layout", layout.join(" "));
132 } else {145 } else {
133 this.del('layout');146 this.del("layout");
134 }147 }
135 }148 }
136 149
137 hasLayout(name) {150 hasLayout(name) {
138 const layout = (this.store.get('layout') || '').split(' ');151 const layout = (this.store.get("layout") || "").split(" ");
139 return layout.indexOf(name) !== -1;152 return layout.indexOf(name) !== -1;
140 }153 }
141 154
142 setSize(value) {155 setSize(value) {
143 this.set('size', value);156 this.set("size", value);
144 }157 }
145 158
146 dump() {159 dump() {
@@ -149,7 +162,9 @@ · 2 unmodified lines
149 162
150 export() {163 export() {
151 const data = this.dump();164 const data = this.dump();
152 for (var key of Array.from(INTERNAL_KEYS)) { delete data[key]; }165 for (var key of Array.from(INTERNAL_KEYS)) {
166 delete data[key];
167 }
153 return data;168 return data;
154 }169 }
155 170
@@ -158,11 +173,15 @@ · 2 unmodified lines
158 const object = this.export();173 const object = this.export();
159 for (key in object) {174 for (key in object) {
160 value = object[key];175 value = object[key];
161 if (!data.hasOwnProperty(key)) { this.del(key); }176 if (!data.hasOwnProperty(key)) {
177 this.del(key);
178 }
162 }179 }
163 for (key in data) {180 for (key in data) {
164 value = data[key];181 value = data[key];
165 if (PREFERENCE_KEYS.indexOf(key) !== -1) { this.set(key, value); }182 if (PREFERENCE_KEYS.indexOf(key) !== -1) {
183 this.set(key, value);
184 }
166 }185 }
167 }186 }
168 187
@@ -172,44 +191,54 @@ · 3 unmodified lines
172 }191 }
173 192
174 initLayout() {193 initLayout() {
175 if (this.get('dark') === 1) {194 if (this.get("dark") === 1) {
176 this.set('theme', 'dark');195 this.set("theme", "dark");
177 this.del('dark');196 this.del("dark");
197 }
198 this.setTheme(this.get("theme"));
199 for (var layout of Array.from(this.LAYOUTS)) {
200 this.toggleLayout(layout, this.hasLayout(layout));
178 }201 }
179 this.setTheme(this.get('theme'));
180 for (var layout of Array.from(this.LAYOUTS)) { this.toggleLayout(layout, this.hasLayout(layout)); }
181 this.initSidebarWidth();202 this.initSidebarWidth();
182 }203 }
183 204
184 setTheme(theme) {205 setTheme(theme) {
185 if (theme === 'auto') {206 if (theme === "auto") {
186 theme = this.darkModeQuery.matches ? 'dark' : 'default';207 theme = this.darkModeQuery.matches ? "dark" : "default";
187 }208 }
188 const {209 const { classList } = document.documentElement;
189 classList210 classList.remove("_theme-default", "_theme-dark");
190 } = document.documentElement;211 classList.add("_theme-" + theme);
191 classList.remove('_theme-default', '_theme-dark');
192 classList.add('_theme-' + theme);
193 this.updateColorMeta();212 this.updateColorMeta();
194 }213 }
195 214
196 updateColorMeta() {215 updateColorMeta() {
197 const color = getComputedStyle(document.documentElement).getPropertyValue('--headerBackground').trim();216 const color = getComputedStyle(document.documentElement)
198 $('meta[name=theme-color]').setAttribute('content', color);217 .getPropertyValue("--headerBackground")
218 .trim();
219 $("meta[name=theme-color]").setAttribute("content", color);
199 }220 }
200 221
201 toggleLayout(layout, enable) {222 toggleLayout(layout, enable) {
202 const {223 const { classList } = document.body;
203 classList
204 } = document.body;
205 // sidebar is always shown for settings; its state is updated in app.views.Settings224 // sidebar is always shown for settings; its state is updated in app.views.Settings
206 if ((layout !== '_sidebar-hidden') || !(app.router != null ? app.router.isSettings : undefined)) { classList.toggle(layout, enable); }225 if (
207 classList.toggle('_overlay-scrollbars', $.overlayScrollbarsEnabled());226 layout !== "_sidebar-hidden" ||
227 !(app.router != null ? app.router.isSettings : undefined)
228 ) {
229 classList.toggle(layout, enable);
230 }
231 classList.toggle("_overlay-scrollbars", $.overlayScrollbarsEnabled());
208 }232 }
209 233
210 initSidebarWidth() {234 initSidebarWidth() {
211 const size = this.get('size');235 const size = this.get("size");
212 if (size) { document.documentElement.style.setProperty('--sidebarWidth', size + 'px'); }236 if (size) {
237 document.documentElement.style.setProperty(
238 "--sidebarWidth",
239 size + "px",
240 );
241 }
213 }242 }
214 });243 });
215 Cls.initClass();244 Cls.initClass();
@@ -217,5 +246,7 @@ · 1 unmodified line
217})();246})();
218 247
219function __guard__(value, transform) {248function __guard__(value, transform) {
220 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;249 return typeof value !== "undefined" && value !== null
221}250 ? transform(value)
251 : undefined;
252}
assets/javascripts/app/shortcuts.js+126−72
@@ -21,111 +21,151 @@ const Cls = (app.Shortcuts = class Shortcuts { · 20 unmodified lines
21 }21 }
22 22
23 start() {23 start() {
24 $.on(document, 'keydown', this.onKeydown);24 $.on(document, "keydown", this.onKeydown);
25 $.on(document, 'keypress', this.onKeypress);25 $.on(document, "keypress", this.onKeypress);
26 }26 }
27 27
28 stop() {28 stop() {
29 $.off(document, 'keydown', this.onKeydown);29 $.off(document, "keydown", this.onKeydown);
30 $.off(document, 'keypress', this.onKeypress);30 $.off(document, "keypress", this.onKeypress);
31 }31 }
32 32
33 swapArrowKeysBehavior() {33 swapArrowKeysBehavior() {
34 return app.settings.get('arrowScroll');34 return app.settings.get("arrowScroll");
35 }35 }
36 36
37 spaceScroll() {37 spaceScroll() {
38 return app.settings.get('spaceScroll');38 return app.settings.get("spaceScroll");
39 }39 }
40 40
41 showTip() {41 showTip() {
42 app.showTip('KeyNav');42 app.showTip("KeyNav");
43 return this.showTip = null;43 return (this.showTip = null);
44 }44 }
45 45
46 spaceTimeout() {46 spaceTimeout() {
47 return app.settings.get('spaceTimeout');47 return app.settings.get("spaceTimeout");
48 }48 }
49 49
50 onKeydown(event) {50 onKeydown(event) {
51 if (this.buggyEvent(event)) { return; }51 if (this.buggyEvent(event)) {
52 return;
53 }
52 const result = (() => {54 const result = (() => {
53 if (event.ctrlKey || event.metaKey) {55 if (event.ctrlKey || event.metaKey) {
54 if (!event.altKey && !event.shiftKey) { return this.handleKeydownSuperEvent(event); }56 if (!event.altKey && !event.shiftKey) {
55 } else if (event.shiftKey) {57 return this.handleKeydownSuperEvent(event);
56 if (!event.altKey) { return this.handleKeydownShiftEvent(event); }58 }
57 } else if (event.altKey) {59 } else if (event.shiftKey) {
58 return this.handleKeydownAltEvent(event);60 if (!event.altKey) {
59 } else {61 return this.handleKeydownShiftEvent(event);
60 return this.handleKeydownEvent(event);62 }
61 }63 } else if (event.altKey) {
64 return this.handleKeydownAltEvent(event);
65 } else {
66 return this.handleKeydownEvent(event);
67 }
62 })();68 })();
63 69
64 if (result === false) { event.preventDefault(); }70 if (result === false) {
71 event.preventDefault();
72 }
65 }73 }
66 74
67 onKeypress(event) {75 onKeypress(event) {
68 if (this.buggyEvent(event) || ((event.charCode === 63) && (document.activeElement.tagName === 'INPUT'))) { return; }76 if (
77 this.buggyEvent(event) ||
78 (event.charCode === 63 && document.activeElement.tagName === "INPUT")
79 ) {
80 return;
81 }
69 if (!event.ctrlKey && !event.metaKey) {82 if (!event.ctrlKey && !event.metaKey) {
70 const result = this.handleKeypressEvent(event);83 const result = this.handleKeypressEvent(event);
71 if (result === false) { event.preventDefault(); }84 if (result === false) {
85 event.preventDefault();
86 }
72 }87 }
73 }88 }
74 89
75 handleKeydownEvent(event, _force) {90 handleKeydownEvent(event, _force) {
76 if (!_force && [37, 38, 39, 40].includes(event.which) && this.swapArrowKeysBehavior()) { return this.handleKeydownAltEvent(event, true); }91 if (
92 !_force &&
93 [37, 38, 39, 40].includes(event.which) &&
94 this.swapArrowKeysBehavior()
95 ) {
96 return this.handleKeydownAltEvent(event, true);
97 }
77 98
78 if (!event.target.form && ((48 <= event.which && event.which <= 57) || (65 <= event.which && event.which <= 90))) {99 if (
79 this.trigger('typing');100 !event.target.form &&
101 ((48 <= event.which && event.which <= 57) ||
102 (65 <= event.which && event.which <= 90))
103 ) {
104 this.trigger("typing");
80 return;105 return;
81 }106 }
82 107
83 switch (event.which) {108 switch (event.which) {
84 case 8:109 case 8:
85 if (!event.target.form) { return this.trigger('typing'); }110 if (!event.target.form) {
111 return this.trigger("typing");
112 }
86 break;113 break;
87 case 13:114 case 13:
88 return this.trigger('enter');115 return this.trigger("enter");
89 case 27:116 case 27:
90 this.trigger('escape');117 this.trigger("escape");
91 return false;118 return false;
92 case 32:119 case 32:
93 if ((event.target.type === 'search') && this.spaceScroll() && (!this.lastKeypress || (this.lastKeypress < (Date.now() - (this.spaceTimeout() * 1000))))) {120 if (
94 this.trigger('pageDown');121 event.target.type === "search" &&
122 this.spaceScroll() &&
123 (!this.lastKeypress ||
124 this.lastKeypress < Date.now() - this.spaceTimeout() * 1000)
125 ) {
126 this.trigger("pageDown");
95 return false;127 return false;
96 }128 }
97 break;129 break;
98 case 33:130 case 33:
99 return this.trigger('pageUp');131 return this.trigger("pageUp");
100 case 34:132 case 34:
101 return this.trigger('pageDown');133 return this.trigger("pageDown");
102 case 35:134 case 35:
103 if (!event.target.form) { return this.trigger('pageBottom'); }135 if (!event.target.form) {
136 return this.trigger("pageBottom");
137 }
104 break;138 break;
105 case 36:139 case 36:
106 if (!event.target.form) { return this.trigger('pageTop'); }140 if (!event.target.form) {
141 return this.trigger("pageTop");
142 }
107 break;143 break;
108 case 37:144 case 37:
109 if (!event.target.value) { return this.trigger('left'); }145 if (!event.target.value) {
146 return this.trigger("left");
147 }
110 break;148 break;
111 case 38:149 case 38:
112 this.trigger('up');150 this.trigger("up");
113 if (typeof this.showTip === 'function') {151 if (typeof this.showTip === "function") {
114 this.showTip();152 this.showTip();
115 }153 }
116 return false;154 return false;
117 case 39:155 case 39:
118 if (!event.target.value) { return this.trigger('right'); }156 if (!event.target.value) {
157 return this.trigger("right");
158 }
119 break;159 break;
120 case 40:160 case 40:
121 this.trigger('down');161 this.trigger("down");
122 if (typeof this.showTip === 'function') {162 if (typeof this.showTip === "function") {
123 this.showTip();163 this.showTip();
124 }164 }
125 return false;165 return false;
126 case 191:166 case 191:
127 if (!event.target.form) {167 if (!event.target.form) {
128 this.trigger('typing');168 this.trigger("typing");
129 return false;169 return false;
130 }170 }
131 break;171 break;
@@ -135,52 +175,58 @@ const Cls = (app.Shortcuts = class Shortcuts { · 3 unmodified lines
135 handleKeydownSuperEvent(event) {175 handleKeydownSuperEvent(event) {
136 switch (event.which) {176 switch (event.which) {
137 case 13:177 case 13:
138 return this.trigger('superEnter');178 return this.trigger("superEnter");
139 case 37:179 case 37:
140 if (this.isMac) {180 if (this.isMac) {
141 this.trigger('superLeft');181 this.trigger("superLeft");
142 return false;182 return false;
143 }183 }
144 break;184 break;
145 case 38:185 case 38:
146 this.trigger('pageTop');186 this.trigger("pageTop");
147 return false;187 return false;
148 case 39:188 case 39:
149 if (this.isMac) {189 if (this.isMac) {
150 this.trigger('superRight');190 this.trigger("superRight");
151 return false;191 return false;
152 }192 }
153 break;193 break;
154 case 40:194 case 40:
155 this.trigger('pageBottom');195 this.trigger("pageBottom");
156 return false;196 return false;
157 case 188:197 case 188:
158 this.trigger('preferences');198 this.trigger("preferences");
159 return false;199 return false;
160 }200 }
161 }201 }
162 202
163 handleKeydownShiftEvent(event, _force) {203 handleKeydownShiftEvent(event, _force) {
164 if (!_force && [37, 38, 39, 40].includes(event.which) && this.swapArrowKeysBehavior()) { return this.handleKeydownEvent(event, true); }204 if (
205 !_force &&
206 [37, 38, 39, 40].includes(event.which) &&
207 this.swapArrowKeysBehavior()
208 ) {
209 return this.handleKeydownEvent(event, true);
210 }
165 211
166 if (!event.target.form && (65 <= event.which && event.which <= 90)) {212 if (!event.target.form && 65 <= event.which && event.which <= 90) {
167 this.trigger('typing');213 this.trigger("typing");
168 return;214 return;
169 }215 }
170 216
171 switch (event.which) {217 switch (event.which) {
172 case 32:218 case 32:
173 this.trigger('pageUp');219 this.trigger("pageUp");
174 return false;220 return false;
175 case 38:221 case 38:
176 if (!__guard__(getSelection(), x => x.toString())) {222 if (!__guard__(getSelection(), (x) => x.toString())) {
177 this.trigger('altUp');223 this.trigger("altUp");
178 return false;224 return false;
179 }225 }
180 break;226 break;
181 case 40:227 case 40:
182 if (!__guard__(getSelection(), x1 => x1.toString())) {228 if (!__guard__(getSelection(), (x1) => x1.toString())) {
183 this.trigger('altDown');229 this.trigger("altDown");
184 return false;230 return false;
185 }231 }
186 break;232 break;
@@ -188,58 +234,64 @@ const Cls = (app.Shortcuts = class Shortcuts { · 1 unmodified line
188 }234 }
189 235
190 handleKeydownAltEvent(event, _force) {236 handleKeydownAltEvent(event, _force) {
191 if (!_force && [37, 38, 39, 40].includes(event.which) && this.swapArrowKeysBehavior()) { return this.handleKeydownEvent(event, true); }237 if (
238 !_force &&
239 [37, 38, 39, 40].includes(event.which) &&
240 this.swapArrowKeysBehavior()
241 ) {
242 return this.handleKeydownEvent(event, true);
243 }
192 244
193 switch (event.which) {245 switch (event.which) {
194 case 9:246 case 9:
195 return this.trigger('altRight', event);247 return this.trigger("altRight", event);
196 case 37:248 case 37:
197 if (!this.isMac) {249 if (!this.isMac) {
198 this.trigger('superLeft');250 this.trigger("superLeft");
199 return false;251 return false;
200 }252 }
201 break;253 break;
202 case 38:254 case 38:
203 this.trigger('altUp');255 this.trigger("altUp");
204 return false;256 return false;
205 case 39:257 case 39:
206 if (!this.isMac) {258 if (!this.isMac) {
207 this.trigger('superRight');259 this.trigger("superRight");
208 return false;260 return false;
209 }261 }
210 break;262 break;
211 case 40:263 case 40:
212 this.trigger('altDown');264 this.trigger("altDown");
213 return false;265 return false;
214 case 67:266 case 67:
215 this.trigger('altC');267 this.trigger("altC");
216 return false;268 return false;
217 case 68:269 case 68:
218 this.trigger('altD');270 this.trigger("altD");
219 return false;271 return false;
220 case 70:272 case 70:
221 return this.trigger('altF', event);273 return this.trigger("altF", event);
222 case 71:274 case 71:
223 this.trigger('altG');275 this.trigger("altG");
224 return false;276 return false;
225 case 79:277 case 79:
226 this.trigger('altO');278 this.trigger("altO");
227 return false;279 return false;
228 case 82:280 case 82:
229 this.trigger('altR');281 this.trigger("altR");
230 return false;282 return false;
231 case 83:283 case 83:
232 this.trigger('altS');284 this.trigger("altS");
233 return false;285 return false;
234 }286 }
235 }287 }
236 288
237 handleKeypressEvent(event) {289 handleKeypressEvent(event) {
238 if ((event.which === 63) && !event.target.value) {290 if (event.which === 63 && !event.target.value) {
239 this.trigger('help');291 this.trigger("help");
240 return false;292 return false;
241 } else {293 } else {
242 return this.lastKeypress = Date.now();294 return (this.lastKeypress = Date.now());
243 }295 }
244 }296 }
245 297
@@ -257,5 +309,7 @@ const Cls = (app.Shortcuts = class Shortcuts { · 11 unmodified lines
257Cls.initClass();309Cls.initClass();
258 310
259function __guard__(value, transform) {311function __guard__(value, transform) {
260 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;312 return typeof value !== "undefined" && value !== null
261}313 ? transform(value)
314 : undefined;
315}
assets/javascripts/app/update_checker.js+18−10
@@ -11,9 +11,9 @@ app.UpdateChecker = class UpdateChecker { · 10 unmodified lines
11 this.onFocus = this.onFocus.bind(this);11 this.onFocus = this.onFocus.bind(this);
12 this.lastCheck = Date.now();12 this.lastCheck = Date.now();
13 13
14 $.on(window, 'focus', this.onFocus);14 $.on(window, "focus", this.onFocus);
15 if (app.serviceWorker != null) {15 if (app.serviceWorker != null) {
16 app.serviceWorker.on('updateready', this.onUpdateReady);16 app.serviceWorker.on("updateready", this.onUpdateReady);
17 }17 }
18 18
19 setTimeout(this.checkDocs, 0);19 setTimeout(this.checkDocs, 0);
@@ -24,31 +24,39 @@ app.UpdateChecker = class UpdateChecker { · 4 unmodified lines
24 app.serviceWorker.update();24 app.serviceWorker.update();
25 } else {25 } else {
26 ajax({26 ajax({
27 url: $('script[src*="application"]').getAttribute('src'),27 url: $('script[src*="application"]').getAttribute("src"),
28 dataType: 'application/javascript',28 dataType: "application/javascript",
29 error: (_, xhr) => { if (xhr.status === 404) { return this.onUpdateReady(); } }29 error: (_, xhr) => {
30 if (xhr.status === 404) {
31 return this.onUpdateReady();
32 }
33 },
30 });34 });
31 }35 }
32 }36 }
33 37
34 onUpdateReady() {38 onUpdateReady() {
35 new app.views.Notif('UpdateReady', {autoHide: null});39 new app.views.Notif("UpdateReady", { autoHide: null });
36 }40 }
37 41
38 checkDocs() {42 checkDocs() {
39 if (!app.settings.get('manualUpdate')) {43 if (!app.settings.get("manualUpdate")) {
40 app.docs.updateInBackground();44 app.docs.updateInBackground();
41 } else {45 } else {
42 app.docs.checkForUpdates(i => { if (i > 0) { return this.onDocsUpdateReady(); } });46 app.docs.checkForUpdates((i) => {
47 if (i > 0) {
48 return this.onDocsUpdateReady();
49 }
50 });
43 }51 }
44 }52 }
45 53
46 onDocsUpdateReady() {54 onDocsUpdateReady() {
47 new app.views.Notif('UpdateDocs', {autoHide: null});55 new app.views.Notif("UpdateDocs", { autoHide: null });
48 }56 }
49 57
50 onFocus() {58 onFocus() {
51 if ((Date.now() - this.lastCheck) > 21600e3) {59 if (Date.now() - this.lastCheck > 21600e3) {
52 this.lastCheck = Date.now();60 this.lastCheck = Date.now();
53 this.check();61 this.check();
54 }62 }
assets/javascripts/application.js+3−3
@@ -27,8 +27,8 @@ · 26 unmodified lines
27 27
28//= require tracking28//= require tracking
29 29
30var init = function() {30var init = function () {
31 document.removeEventListener('DOMContentLoaded', init, false);31 document.removeEventListener("DOMContentLoaded", init, false);
32 32
33 if (document.body) {33 if (document.body) {
34 return app.init();34 return app.init();
@@ -37,4 +37,4 @@ var init = function() { · 2 unmodified lines
37 }37 }
38};38};
39 39
40document.addEventListener('DOMContentLoaded', init, false);40document.addEventListener("DOMContentLoaded", init, false);
assets/javascripts/collections/collection.js+23−7
@@ -9,7 +9,9 @@ · 8 unmodified lines
9 */9 */
10app.Collection = class Collection {10app.Collection = class Collection {
11 constructor(objects) {11 constructor(objects) {
12 if (objects == null) { objects = []; }12 if (objects == null) {
13 objects = [];
14 }
13 this.reset(objects);15 this.reset(objects);
14 }16 }
15 17
@@ -18,16 +20,22 @@ app.Collection = class Collection { · 2 unmodified lines
18 }20 }
19 21
20 reset(objects) {22 reset(objects) {
21 if (objects == null) { objects = []; }23 if (objects == null) {
24 objects = [];
25 }
22 this.models = [];26 this.models = [];
23 for (var object of Array.from(objects)) { this.add(object); }27 for (var object of Array.from(objects)) {
28 this.add(object);
29 }
24 }30 }
25 31
26 add(object) {32 add(object) {
27 if (object instanceof app.Model) {33 if (object instanceof app.Model) {
28 this.models.push(object);34 this.models.push(object);
29 } else if (object instanceof Array) {35 } else if (object instanceof Array) {
30 for (var obj of Array.from(object)) { this.add(obj); }36 for (var obj of Array.from(object)) {
37 this.add(obj);
38 }
31 } else if (object instanceof app.Collection) {39 } else if (object instanceof app.Collection) {
32 this.models.push(...Array.from(object.all() || []));40 this.models.push(...Array.from(object.all() || []));
33 } else {41 } else {
@@ -48,7 +56,9 @@ app.Collection = class Collection { · 14 unmodified lines
48 }56 }
49 57
50 each(fn) {58 each(fn) {
51 for (var model of Array.from(this.models)) { fn(model); }59 for (var model of Array.from(this.models)) {
60 fn(model);
61 }
52 }62 }
53 63
54 all() {64 all() {
@@ -61,7 +71,9 @@ app.Collection = class Collection { · 6 unmodified lines
61 71
62 findBy(attr, value) {72 findBy(attr, value) {
63 for (var model of Array.from(this.models)) {73 for (var model of Array.from(this.models)) {
64 if (model[attr] === value) { return model; }74 if (model[attr] === value) {
75 return model;
76 }
65 }77 }
66 }78 }
67 79
@@ -71,7 +83,11 @@ app.Collection = class Collection { · 3 unmodified lines
71 83
72 countAllBy(attr, value) {84 countAllBy(attr, value) {
73 let i = 0;85 let i = 0;
74 for (var model of Array.from(this.models)) { if (model[attr] === value) { i += 1; } }86 for (var model of Array.from(this.models)) {
87 if (model[attr] === value) {
88 i += 1;
89 }
90 }
75 return i;91 return i;
76 }92 }
77};93};
assets/javascripts/collections/docs.js+43−20
@@ -8,29 +8,35 @@ · 7 unmodified lines
8 * DS206: Consider reworking classes to avoid initClass8 * DS206: Consider reworking classes to avoid initClass
9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
10 */10 */
11(function() {11(function () {
12 let NORMALIZE_VERSION_RGX = undefined;12 let NORMALIZE_VERSION_RGX = undefined;
13 let NORMALIZE_VERSION_SUB = undefined;13 let NORMALIZE_VERSION_SUB = undefined;
14 let CONCURRENCY = undefined;14 let CONCURRENCY = undefined;
15 const Cls = (app.collections.Docs = class Docs extends app.Collection {15 const Cls = (app.collections.Docs = class Docs extends app.Collection {
16 static initClass() {16 static initClass() {
17 this.model = 'Doc';17 this.model = "Doc";
18 18
19 NORMALIZE_VERSION_RGX = /\.(\d)$/;19 NORMALIZE_VERSION_RGX = /\.(\d)$/;
20 NORMALIZE_VERSION_SUB = '.0$1';20 NORMALIZE_VERSION_SUB = ".0$1";
21 21
22 // Load models concurrently.22 // Load models concurrently.
23 // It's not pretty but I didn't want to import a promise library only for this.23 // It's not pretty but I didn't want to import a promise library only for this.
24 CONCURRENCY = 3;24 CONCURRENCY = 3;
25 }25 }
26 26
27 findBySlug(slug) {27 findBySlug(slug) {
28 return this.findBy('slug', slug) || this.findBy('slug_without_version', slug);28 return (
29 this.findBy("slug", slug) || this.findBy("slug_without_version", slug)
30 );
29 }31 }
30 sort() {32 sort() {
31 return this.models.sort(function(a, b) {33 return this.models.sort(function (a, b) {
32 if (a.name === b.name) {34 if (a.name === b.name) {
33 if (!a.version || (a.version.replace(NORMALIZE_VERSION_RGX, NORMALIZE_VERSION_SUB) > b.version.replace(NORMALIZE_VERSION_RGX, NORMALIZE_VERSION_SUB))) {35 if (
36 !a.version ||
37 a.version.replace(NORMALIZE_VERSION_RGX, NORMALIZE_VERSION_SUB) >
38 b.version.replace(NORMALIZE_VERSION_RGX, NORMALIZE_VERSION_SUB)
39 ) {
34 return -1;40 return -1;
35 } else {41 } else {
36 return 1;42 return 1;
@@ -48,13 +54,13 @@ · 11 unmodified lines
48 var next = () => {54 var next = () => {
49 if (i < this.models.length) {55 if (i < this.models.length) {
50 this.models[i].load(next, fail, options);56 this.models[i].load(next, fail, options);
51 } else if (i === ((this.models.length + CONCURRENCY) - 1)) {57 } else if (i === this.models.length + CONCURRENCY - 1) {
52 onComplete();58 onComplete();
53 }59 }
54 i++;60 i++;
55 };61 };
56 62
57 var fail = function(...args) {63 var fail = function (...args) {
58 if (onError) {64 if (onError) {
59 onError(...Array.from(args || []));65 onError(...Array.from(args || []));
60 onError = null;66 onError = null;
@@ -62,11 +68,19 @@ · 1 unmodified line
62 next();68 next();
63 };69 };
64 70
65 for (let j = 0, end = CONCURRENCY, asc = 0 <= end; asc ? j < end : j > end; asc ? j++ : j--) { next(); }71 for (
72 let j = 0, end = CONCURRENCY, asc = 0 <= end;
73 asc ? j < end : j > end;
74 asc ? j++ : j--
75 ) {
76 next();
77 }
66 }78 }
67 79
68 clearCache() {80 clearCache() {
69 for (var doc of Array.from(this.models)) { doc.clearCache(); }81 for (var doc of Array.from(this.models)) {
82 doc.clearCache();
83 }
70 }84 }
71 85
72 uninstall(callback) {86 uninstall(callback) {
@@ -82,11 +96,11 @@ · 9 unmodified lines
82 }96 }
83 97
84 getInstallStatuses(callback) {98 getInstallStatuses(callback) {
85 app.db.versions(this.models, function(statuses) {99 app.db.versions(this.models, function (statuses) {
86 if (statuses) {100 if (statuses) {
87 for (var key in statuses) {101 for (var key in statuses) {
88 var value = statuses[key];102 var value = statuses[key];
89 statuses[key] = {installed: !!value, mtime: value};103 statuses[key] = { installed: !!value, mtime: value };
90 }104 }
91 }105 }
92 callback(statuses);106 callback(statuses);
@@ -94,22 +108,31 @@ · 1 unmodified line
94 }108 }
95 109
96 checkForUpdates(callback) {110 checkForUpdates(callback) {
97 this.getInstallStatuses(statuses => {111 this.getInstallStatuses((statuses) => {
98 let i = 0;112 let i = 0;
99 if (statuses) {113 if (statuses) {
100 for (var slug in statuses) { var status = statuses[slug]; if (this.findBy('slug', slug).isOutdated(status)) { i += 1; } }114 for (var slug in statuses) {
115 var status = statuses[slug];
116 if (this.findBy("slug", slug).isOutdated(status)) {
117 i += 1;
118 }
119 }
101 }120 }
102 callback(i);121 callback(i);
103 });122 });
104 }123 }
105 124
106 updateInBackground() {125 updateInBackground() {
107 this.getInstallStatuses(statuses => {126 this.getInstallStatuses((statuses) => {
108 if (!statuses) { return; }127 if (!statuses) {
128 return;
129 }
109 for (var slug in statuses) {130 for (var slug in statuses) {
110 var status = statuses[slug];131 var status = statuses[slug];
111 var doc = this.findBy('slug', slug);132 var doc = this.findBy("slug", slug);
112 if (doc.isOutdated(status)) { doc.install($.noop, $.noop); }133 if (doc.isOutdated(status)) {
134 doc.install($.noop, $.noop);
135 }
113 }136 }
114 });137 });
115 }138 }
assets/javascripts/collections/entries.js+1−1
@@ -7,7 +7,7 @@ · 6 unmodified lines
7 */7 */
8const Cls = (app.collections.Entries = class Entries extends app.Collection {8const Cls = (app.collections.Entries = class Entries extends app.Collection {
9 static initClass() {9 static initClass() {
10 this.model = 'Entry';10 this.model = "Entry";
11 }11 }
12});12});
13Cls.initClass();13Cls.initClass();
assets/javascripts/collections/types.js+9−6
@@ -8,14 +8,15 @@ · 7 unmodified lines
8 * DS206: Consider reworking classes to avoid initClass8 * DS206: Consider reworking classes to avoid initClass
9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
10 */10 */
11(function() {11(function () {
12 let GUIDES_RGX = undefined;12 let GUIDES_RGX = undefined;
13 let APPENDIX_RGX = undefined;13 let APPENDIX_RGX = undefined;
14 const Cls = (app.collections.Types = class Types extends app.Collection {14 const Cls = (app.collections.Types = class Types extends app.Collection {
15 static initClass() {15 static initClass() {
16 this.model = 'Type';16 this.model = "Type";
17 17
18 GUIDES_RGX = /(^|\()(guides?|tutorials?|reference|book|getting\ started|manual|examples)($|[\):])/i;18 GUIDES_RGX =
19 /(^|\()(guides?|tutorials?|reference|book|getting\ started|manual|examples)($|[\):])/i;
19 APPENDIX_RGX = /appendix/i;20 APPENDIX_RGX = /appendix/i;
20 }21 }
21 22
@@ -23,9 +24,11 @@ · 1 unmodified line
23 const result = [];24 const result = [];
24 for (var type of Array.from(this.models)) {25 for (var type of Array.from(this.models)) {
25 var name;26 var name;
26 (result[name = this._groupFor(type)] || (result[name] = [])).push(type);27 (result[(name = this._groupFor(type))] || (result[name] = [])).push(
28 type,
29 );
27 }30 }
28 return result.filter(e => e.length > 0);31 return result.filter((e) => e.length > 0);
29 }32 }
30 33
31 _groupFor(type) {34 _groupFor(type) {
assets/javascripts/debug.js+58−31
@@ -9,26 +9,33 @@ · 8 unmodified lines
9 * DS209: Avoid top-level return9 * DS209: Avoid top-level return
10 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md10 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
11 */11 */
12if (!(typeof console !== 'undefined' && console !== null ? console.time : undefined) || !console.groupCollapsed) { return; }12if (
13 !(typeof console !== "undefined" && console !== null
14 ? console.time
15 : undefined) ||
16 !console.groupCollapsed
17) {
18 return;
19}
13 20
14//21//
15// App22// App
16//23//
17 24
18const _init = app.init;25const _init = app.init;
19app.init = function() {26app.init = function () {
20 console.time('Init');27 console.time("Init");
21 _init.call(app);28 _init.call(app);
22 console.timeEnd('Init');29 console.timeEnd("Init");
23 return console.time('Load');30 return console.time("Load");
24};31};
25 32
26const _start = app.start;33const _start = app.start;
27app.start = function() {34app.start = function () {
28 console.timeEnd('Load');35 console.timeEnd("Load");
29 console.time('Start');36 console.time("Start");
30 _start.call(app, ...arguments);37 _start.call(app, ...arguments);
31 return console.timeEnd('Start');38 return console.timeEnd("Start");
32};39};
33 40
34//41//
@@ -38,47 +45,50 @@ app.start = function() { · 3 unmodified lines
38const _super = app.Searcher;45const _super = app.Searcher;
39const _proto = app.Searcher.prototype;46const _proto = app.Searcher.prototype;
40 47
41app.Searcher = function() {48app.Searcher = function () {
42 _super.apply(this, arguments);49 _super.apply(this, arguments);
43 50
44 const _setup = this.setup.bind(this);51 const _setup = this.setup.bind(this);
45 this.setup = function() {52 this.setup = function () {
46 console.groupCollapsed(`Search: ${this.query}`);53 console.groupCollapsed(`Search: ${this.query}`);
47 console.time('Total');54 console.time("Total");
48 return _setup();55 return _setup();
49 };56 };
50 57
51 const _match = this.match.bind(this);58 const _match = this.match.bind(this);
52 this.match = () => {59 this.match = () => {
53 if (this.matcher) { console.timeEnd(this.matcher.name); }60 if (this.matcher) {
61 console.timeEnd(this.matcher.name);
62 }
54 return _match();63 return _match();
55 };64 };
56 65
57 const _setupMatcher = this.setupMatcher.bind(this);66 const _setupMatcher = this.setupMatcher.bind(this);
58 this.setupMatcher = function() {67 this.setupMatcher = function () {
59 console.time(this.matcher.name);68 console.time(this.matcher.name);
60 return _setupMatcher();69 return _setupMatcher();
61 };70 };
62 71
63 const _end = this.end.bind(this);72 const _end = this.end.bind(this);
64 this.end = function() {73 this.end = function () {
65 console.log(`Results: ${this.totalResults}`);74 console.log(`Results: ${this.totalResults}`);
66 console.timeEnd('Total');75 console.timeEnd("Total");
67 console.groupEnd();76 console.groupEnd();
68 return _end();77 return _end();
69 };78 };
70 79
71 const _kill = this.kill.bind(this);80 const _kill = this.kill.bind(this);
72 this.kill = function() {81 this.kill = function () {
73 if (this.timeout) {82 if (this.timeout) {
74 if (this.matcher) { console.timeEnd(this.matcher.name); }83 if (this.matcher) {
84 console.timeEnd(this.matcher.name);
85 }
75 console.groupEnd();86 console.groupEnd();
76 console.timeEnd('Total');87 console.timeEnd("Total");
77 console.warn('Killed');88 console.warn("Killed");
78 }89 }
79 return _kill();90 return _kill();
80 };91 };
81
82};92};
83 93
84$.extend(app.Searcher, _super);94$.extend(app.Searcher, _super);
@@ -89,23 +99,40 @@ app.Searcher.prototype = _proto; · 4 unmodified lines
89// View tree99// View tree
90//100//
91 101
92this.viewTree = function(view, level, visited) {102this.viewTree = function (view, level, visited) {
93 if (view == null) { view = app.document; }103 if (view == null) {
94 if (level == null) { level = 0; }104 view = app.document;
95 if (visited == null) { visited = []; }105 }
96 if (visited.indexOf(view) >= 0) { return; }106 if (level == null) {
107 level = 0;
108 }
109 if (visited == null) {
110 visited = [];
111 }
112 if (visited.indexOf(view) >= 0) {
113 return;
114 }
97 visited.push(view);115 visited.push(view);
98 116
99 console.log(`%c ${Array(level + 1).join(' ')}${view.constructor.name}: ${!!view.activated}`,117 console.log(
100 'color:' + ((view.activated && 'green') || 'red'));118 `%c ${Array(level + 1).join(" ")}${
119 view.constructor.name
120 }: ${!!view.activated}`,
121 "color:" + ((view.activated && "green") || "red"),
122 );
101 123
102 for (var key of Object.keys(view || {})) {124 for (var key of Object.keys(view || {})) {
103 var value = view[key];125 var value = view[key];
104 if ((key !== 'view') && value) {126 if (key !== "view" && value) {
105 if ((typeof value === 'object') && value.setupElement) {127 if (typeof value === "object" && value.setupElement) {
106 this.viewTree(value, level + 1, visited);128 this.viewTree(value, level + 1, visited);
107 } else if (value.constructor.toString().match(/Object\(\)/)) {129 } else if (value.constructor.toString().match(/Object\(\)/)) {
108 for (var k of Object.keys(value || {})) { var v = value[k]; if (v && (typeof v === 'object') && v.setupElement) { this.viewTree(v, level + 1, visited); } }130 for (var k of Object.keys(value || {})) {
131 var v = value[k];
132 if (v && typeof v === "object" && v.setupElement) {
133 this.viewTree(v, level + 1, visited);
134 }
135 }
109 }136 }
110 }137 }
111 }138 }
assets/javascripts/lib/ajax.js+73−52
@@ -9,11 +9,11 @@ · 8 unmodified lines
9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
10 */10 */
11const MIME_TYPES = {11const MIME_TYPES = {
12 json: 'application/json',12 json: "application/json",
13 html: 'text/html'13 html: "text/html",
14};14};
15 15
16this.ajax = function(options) {16this.ajax = function (options) {
17 applyDefaults(options);17 applyDefaults(options);
18 serializeData(options);18 serializeData(options);
19 19
@@ -26,7 +26,7 @@ this.ajax = function(options) { · 6 unmodified lines
26 xhr.send(options.data);26 xhr.send(options.data);
27 27
28 if (options.async) {28 if (options.async) {
29 return {abort: abort.bind(undefined, xhr)};29 return { abort: abort.bind(undefined, xhr) };
30 } else {30 } else {
31 return parseResponse(xhr, options);31 return parseResponse(xhr, options);
32 }32 }
@@ -34,51 +34,63 @@ this.ajax = function(options) { · 1 unmodified line
34 34
35ajax.defaults = {35ajax.defaults = {
36 async: true,36 async: true,
37 dataType: 'json',37 dataType: "json",
38 timeout: 30,38 timeout: 30,
39 type: 'GET'39 type: "GET",
40};40};
41 // contentType41// contentType
42 // context42// context
43 // data43// data
44 // error44// error
45 // headers45// headers
46 // progress46// progress
47 // success47// success
48 // url48// url
49 49
50var applyDefaults = function(options) {50var applyDefaults = function (options) {
51 for (var key in ajax.defaults) {51 for (var key in ajax.defaults) {
52 if (options[key] == null) { options[key] = ajax.defaults[key]; }52 if (options[key] == null) {
53 options[key] = ajax.defaults[key];
54 }
53 }55 }
54};56};
55 57
56var serializeData = function(options) {58var serializeData = function (options) {
57 if (!options.data) { return; }59 if (!options.data) {
60 return;
61 }
58 62
59 if (options.type === 'GET') {63 if (options.type === "GET") {
60 options.url += '?' + serializeParams(options.data);64 options.url += "?" + serializeParams(options.data);
61 options.data = null;65 options.data = null;
62 } else {66 } else {
63 options.data = serializeParams(options.data);67 options.data = serializeParams(options.data);
64 }68 }
65};69};
66 70
67var serializeParams = params => ((() => {71var serializeParams = (params) =>
68 const result = [];72 (() => {
69 for (var key in params) {73 const result = [];
70 var value = params[key];74 for (var key in params) {
71 result.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);75 var value = params[key];
72 }76 result.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
73 return result;77 }
74})()).join('&');78 return result;
79 })().join("&");
75 80
76var applyCallbacks = function(xhr, options) {81var applyCallbacks = function (xhr, options) {
77 if (!options.async) { return; }82 if (!options.async) {
83 return;
84 }
78 85
79 xhr.timer = setTimeout(onTimeout.bind(undefined, xhr, options), options.timeout * 1000);86 xhr.timer = setTimeout(
80 if (options.progress) { xhr.onprogress = options.progress; }87 onTimeout.bind(undefined, xhr, options),
81 xhr.onreadystatechange = function() {88 options.timeout * 1000,
89 );
90 if (options.progress) {
91 xhr.onprogress = options.progress;
92 }
93 xhr.onreadystatechange = function () {
82 if (xhr.readyState === 4) {94 if (xhr.readyState === 4) {
83 clearTimeout(xhr.timer);95 clearTimeout(xhr.timer);
84 onComplete(xhr, options);96 onComplete(xhr, options);
@@ -86,19 +98,26 @@ var applyCallbacks = function(xhr, options) { · 1 unmodified line
86 };98 };
87};99};
88 100
89var applyHeaders = function(xhr, options) {101var applyHeaders = function (xhr, options) {
90 if (!options.headers) { options.headers = {}; }102 if (!options.headers) {
103 options.headers = {};
104 }
91 105
92 if (options.contentType) {106 if (options.contentType) {
93 options.headers['Content-Type'] = options.contentType;107 options.headers["Content-Type"] = options.contentType;
94 }108 }
95 109
96 if (!options.headers['Content-Type'] && options.data && (options.type !== 'GET')) {110 if (
97 options.headers['Content-Type'] = 'application/x-www-form-urlencoded';111 !options.headers["Content-Type"] &&
112 options.data &&
113 options.type !== "GET"
114 ) {
115 options.headers["Content-Type"] = "application/x-www-form-urlencoded";
98 }116 }
99 117
100 if (options.dataType) {118 if (options.dataType) {
101 options.headers['Accept'] = MIME_TYPES[options.dataType] || options.dataType;119 options.headers["Accept"] =
120 MIME_TYPES[options.dataType] || options.dataType;
102 }121 }
103 122
104 for (var key in options.headers) {123 for (var key in options.headers) {
@@ -107,50 +126,52 @@ var applyHeaders = function(xhr, options) { · 2 unmodified lines
107 }126 }
108};127};
109 128
110var onComplete = function(xhr, options) {129var onComplete = function (xhr, options) {
111 if (200 <= xhr.status && xhr.status < 300) {130 if (200 <= xhr.status && xhr.status < 300) {
112 let response;131 let response;
113 if ((response = parseResponse(xhr, options)) != null) {132 if ((response = parseResponse(xhr, options)) != null) {
114 onSuccess(response, xhr, options);133 onSuccess(response, xhr, options);
115 } else {134 } else {
116 onError('invalid', xhr, options);135 onError("invalid", xhr, options);
117 }136 }
118 } else {137 } else {
119 onError('error', xhr, options);138 onError("error", xhr, options);
120 }139 }
121};140};
122 141
123var onSuccess = function(response, xhr, options) {142var onSuccess = function (response, xhr, options) {
124 if (options.success != null) {143 if (options.success != null) {
125 options.success.call(options.context, response, xhr, options);144 options.success.call(options.context, response, xhr, options);
126 }145 }
127};146};
128 147
129var onError = function(type, xhr, options) {148var onError = function (type, xhr, options) {
130 if (options.error != null) {149 if (options.error != null) {
131 options.error.call(options.context, type, xhr, options);150 options.error.call(options.context, type, xhr, options);
132 }151 }
133};152};
134 153
135var onTimeout = function(xhr, options) {154var onTimeout = function (xhr, options) {
136 xhr.abort();155 xhr.abort();
137 onError('timeout', xhr, options);156 onError("timeout", xhr, options);
138};157};
139 158
140var abort = function(xhr) {159var abort = function (xhr) {
141 clearTimeout(xhr.timer);160 clearTimeout(xhr.timer);
142 xhr.onreadystatechange = null;161 xhr.onreadystatechange = null;
143 xhr.abort();162 xhr.abort();
144};163};
145 164
146var parseResponse = function(xhr, options) {165var parseResponse = function (xhr, options) {
147 if (options.dataType === 'json') {166 if (options.dataType === "json") {
148 return parseJSON(xhr.responseText);167 return parseJSON(xhr.responseText);
149 } else {168 } else {
150 return xhr.responseText;169 return xhr.responseText;
151 }170 }
152};171};
153 172
154var parseJSON = function(json) {173var parseJSON = function (json) {
155 try { return JSON.parse(json); } catch (error) {}174 try {
175 return JSON.parse(json);
176 } catch (error) {}
156};177};
assets/javascripts/lib/cookies_store.js+21−10
@@ -8,14 +8,14 @@ · 7 unmodified lines
8 * DS207: Consider shorter variations of null checks8 * DS207: Consider shorter variations of null checks
9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
10 */10 */
11(function() {11(function () {
12 let INT = undefined;12 let INT = undefined;
13 const Cls = (this.CookiesStore = class CookiesStore {13 const Cls = (this.CookiesStore = class CookiesStore {
14 static initClass() {14 static initClass() {
15 // Intentionally called CookiesStore instead of CookieStore15 // Intentionally called CookiesStore instead of CookieStore
16 // Calling it CookieStore causes issues when the Experimental Web Platform features flag is enabled in Chrome16 // Calling it CookieStore causes issues when the Experimental Web Platform features flag is enabled in Chrome
17 // Related issue: https://github.com/freeCodeCamp/devdocs/issues/93217 // Related issue: https://github.com/freeCodeCamp/devdocs/issues/932
18 18
19 INT = /^\d+$/;19 INT = /^\d+$/;
20 }20 }
21 21
@@ -23,7 +23,9 @@ · 1 unmodified line
23 23
24 get(key) {24 get(key) {
25 let value = Cookies.get(key);25 let value = Cookies.get(key);
26 if ((value != null) && INT.test(value)) { value = parseInt(value, 10); }26 if (value != null && INT.test(value)) {
27 value = parseInt(value, 10);
28 }
27 return value;29 return value;
28 }30 }
29 31
@@ -33,10 +35,19 @@ · 3 unmodified lines
33 return;35 return;
34 }36 }
35 37
36 if (value === true) { value = 1; }38 if (value === true) {
37 if (value && (typeof INT.test === 'function' ? INT.test(value) : undefined)) { value = parseInt(value, 10); }39 value = 1;
38 Cookies.set(key, '' + value, {path: '/', expires: 1e8});40 }
39 if (this.get(key) !== value) { this.constructor.onBlocked(key, value, this.get(key)); }41 if (
42 value &&
43 (typeof INT.test === "function" ? INT.test(value) : undefined)
44 ) {
45 value = parseInt(value, 10);
46 }
47 Cookies.set(key, "" + value, { path: "/", expires: 1e8 });
48 if (this.get(key) !== value) {
49 this.constructor.onBlocked(key, value, this.get(key));
50 }
40 }51 }
41 52
42 del(key) {53 del(key) {
@@ -46,7 +57,7 @@ · 3 unmodified lines
46 reset() {57 reset() {
47 try {58 try {
48 for (var cookie of Array.from(document.cookie.split(/;\s?/))) {59 for (var cookie of Array.from(document.cookie.split(/;\s?/))) {
49 Cookies.expire(cookie.split('=')[0]);60 Cookies.expire(cookie.split("=")[0]);
50 }61 }
51 return;62 return;
52 } catch (error) {}63 } catch (error) {}
@@ -55,8 +66,8 @@ · 2 unmodified lines
55 dump() {66 dump() {
56 const result = {};67 const result = {};
57 for (var cookie of Array.from(document.cookie.split(/;\s?/))) {68 for (var cookie of Array.from(document.cookie.split(/;\s?/))) {
58 if (cookie[0] !== '_') {69 if (cookie[0] !== "_") {
59 cookie = cookie.split('=');70 cookie = cookie.split("=");
60 result[cookie[0]] = cookie[1];71 result[cookie[0]] = cookie[1];
61 }72 }
62 }73 }
assets/javascripts/lib/events.js+38−14
@@ -11,22 +11,38 @@ · 10 unmodified lines
11 */11 */
12this.Events = {12this.Events = {
13 on(event, callback) {13 on(event, callback) {
14 if (event.indexOf(' ') >= 0) {14 if (event.indexOf(" ") >= 0) {
15 for (var name of Array.from(event.split(' '))) { this.on(name, callback); }15 for (var name of Array.from(event.split(" "))) {
16 this.on(name, callback);
17 }
16 } else {18 } else {
17 let base;19 let base;
18 (((base = this._callbacks != null ? this._callbacks : (this._callbacks = {})))[event] != null ? base[event] : (base[event] = [])).push(callback);20 ((base =
21 this._callbacks != null ? this._callbacks : (this._callbacks = {}))[
22 event
23 ] != null
24 ? base[event]
25 : (base[event] = [])
26 ).push(callback);
19 }27 }
20 return this;28 return this;
21 },29 },
22 30
23 off(event, callback) {31 off(event, callback) {
24 let callbacks, index;32 let callbacks, index;
25 if (event.indexOf(' ') >= 0) {33 if (event.indexOf(" ") >= 0) {
26 for (var name of Array.from(event.split(' '))) { this.off(name, callback); }34 for (var name of Array.from(event.split(" "))) {
27 } else if ((callbacks = this._callbacks != null ? this._callbacks[event] : undefined) && ((index = callbacks.indexOf(callback)) >= 0)) {35 this.off(name, callback);
36 }
37 } else if (
38 (callbacks =
39 this._callbacks != null ? this._callbacks[event] : undefined) &&
40 (index = callbacks.indexOf(callback)) >= 0
41 ) {
28 callbacks.splice(index, 1);42 callbacks.splice(index, 1);
29 if (!callbacks.length) { delete this._callbacks[event]; }43 if (!callbacks.length) {
44 delete this._callbacks[event];
45 }
30 }46 }
31 return this;47 return this;
32 },48 },
@@ -34,20 +50,28 @@ this.Events = { · 1 unmodified line
34 trigger(event, ...args) {50 trigger(event, ...args) {
35 let callbacks;51 let callbacks;
36 this.eventInProgress = { name: event, args };52 this.eventInProgress = { name: event, args };
37 if (callbacks = this._callbacks != null ? this._callbacks[event] : undefined) {53 if (
38 for (var callback of Array.from(callbacks.slice(0))) { if (typeof callback === 'function') {54 (callbacks = this._callbacks != null ? this._callbacks[event] : undefined)
39 callback(...Array.from(args || []));55 ) {
40 } }56 for (var callback of Array.from(callbacks.slice(0))) {
57 if (typeof callback === "function") {
58 callback(...Array.from(args || []));
59 }
60 }
41 }61 }
42 this.eventInProgress = null;62 this.eventInProgress = null;
43 if (event !== 'all') { this.trigger('all', event, ...Array.from(args)); }63 if (event !== "all") {
64 this.trigger("all", event, ...Array.from(args));
65 }
44 return this;66 return this;
45 },67 },
46 68
47 removeEvent(event) {69 removeEvent(event) {
48 if (this._callbacks != null) {70 if (this._callbacks != null) {
49 for (var name of Array.from(event.split(' '))) { delete this._callbacks[name]; }71 for (var name of Array.from(event.split(" "))) {
72 delete this._callbacks[name];
73 }
50 }74 }
51 return this;75 return this;
52 }76 },
53};77};
assets/javascripts/lib/favicon.js+59−41
@@ -12,22 +12,24 @@ let currentSlug = null; · 11 unmodified lines
12const imageCache = {};12const imageCache = {};
13const urlCache = {};13const urlCache = {};
14 14
15const withImage = function(url, action) {15const withImage = function (url, action) {
16 if (imageCache[url]) {16 if (imageCache[url]) {
17 return action(imageCache[url]);17 return action(imageCache[url]);
18 } else {18 } else {
19 const img = new Image();19 const img = new Image();
20 img.crossOrigin = 'anonymous';20 img.crossOrigin = "anonymous";
21 img.src = url;21 img.src = url;
22 return img.onload = () => {22 return (img.onload = () => {
23 imageCache[url] = img;23 imageCache[url] = img;
24 return action(img);24 return action(img);
25 };25 });
26 }26 }
27};27};
28 28
29this.setFaviconForDoc = function(doc) {29this.setFaviconForDoc = function (doc) {
30 if (currentSlug === doc.slug) { return; }30 if (currentSlug === doc.slug) {
31 return;
32 }
31 33
32 const favicon = $('link[rel="icon"]');34 const favicon = $('link[rel="icon"]');
33 35
@@ -41,51 +43,67 @@ this.setFaviconForDoc = function(doc) { · 7 unmodified lines
41 return;43 return;
42 }44 }
43 45
44 const iconEl = $(`._icon-${doc.slug.split('~')[0]}`);46 const iconEl = $(`._icon-${doc.slug.split("~")[0]}`);
45 if (iconEl === null) { return; }47 if (iconEl === null) {
48 return;
49 }
46 50
47 const styles = window.getComputedStyle(iconEl, ':before');51 const styles = window.getComputedStyle(iconEl, ":before");
48 52
49 const backgroundPositionX = styles['background-position-x'];53 const backgroundPositionX = styles["background-position-x"];
50 const backgroundPositionY = styles['background-position-y'];54 const backgroundPositionY = styles["background-position-y"];
51 if ((backgroundPositionX === undefined) || (backgroundPositionY === undefined)) { return; }55 if (backgroundPositionX === undefined || backgroundPositionY === undefined) {
56 return;
57 }
52 58
53 const bgUrl = app.config.favicon_spritesheet;59 const bgUrl = app.config.favicon_spritesheet;
54 const sourceSize = 16;60 const sourceSize = 16;
55 const sourceX = Math.abs(parseInt(backgroundPositionX.slice(0, -2)));61 const sourceX = Math.abs(parseInt(backgroundPositionX.slice(0, -2)));
56 const sourceY = Math.abs(parseInt(backgroundPositionY.slice(0, -2)));62 const sourceY = Math.abs(parseInt(backgroundPositionY.slice(0, -2)));
57 63
58 return withImage(bgUrl, docImg => withImage(defaultUrl, function(defaultImg) {64 return withImage(bgUrl, (docImg) =>
59 const size = defaultImg.width;65 withImage(defaultUrl, function (defaultImg) {
60 66 const size = defaultImg.width;
61 const canvas = document.createElement('canvas');67
62 const ctx = canvas.getContext('2d');68 const canvas = document.createElement("canvas");
63 69 const ctx = canvas.getContext("2d");
64 canvas.width = size;70
65 canvas.height = size;71 canvas.width = size;
66 ctx.drawImage(defaultImg, 0, 0);72 canvas.height = size;
67 73 ctx.drawImage(defaultImg, 0, 0);
68 const docIconPercentage = 65;74
69 const destinationCoords = (size / 100) * (100 - docIconPercentage);75 const docIconPercentage = 65;
70 const destinationSize = (size / 100) * docIconPercentage;76 const destinationCoords = (size / 100) * (100 - docIconPercentage);
71 77 const destinationSize = (size / 100) * docIconPercentage;
72 ctx.drawImage(docImg, sourceX, sourceY, sourceSize, sourceSize, destinationCoords, destinationCoords, destinationSize, destinationSize);78
73 79 ctx.drawImage(
74 try {80 docImg,
75 urlCache[doc.slug] = canvas.toDataURL();81 sourceX,
76 favicon.href = urlCache[doc.slug];82 sourceY,
77 83 sourceSize,
78 return currentSlug = doc.slug;84 sourceSize,
79 } catch (error) {85 destinationCoords,
80 Raven.captureException(error, { level: 'info' });86 destinationCoords,
81 return this.resetFavicon();87 destinationSize,
82 }88 destinationSize,
83 }));89 );
90
91 try {
92 urlCache[doc.slug] = canvas.toDataURL();
93 favicon.href = urlCache[doc.slug];
94
95 return (currentSlug = doc.slug);
96 } catch (error) {
97 Raven.captureException(error, { level: "info" });
98 return this.resetFavicon();
99 }
100 }),
101 );
84};102};
85 103
86this.resetFavicon = function() {104this.resetFavicon = function () {
87 if ((defaultUrl !== null) && (currentSlug !== null)) {105 if (defaultUrl !== null && currentSlug !== null) {
88 $('link[rel="icon"]').href = defaultUrl;106 $('link[rel="icon"]').href = defaultUrl;
89 return currentSlug = null;107 return (currentSlug = null);
90 }108 }
91};109};
assets/javascripts/lib/license.js+1−1
@@ -6,4 +6,4 @@ · 5 unmodified lines
6 * This source code is licensed under the terms of the Mozilla6 * This source code is licensed under the terms of the Mozilla
7 * Public License, v. 2.0, a copy of which may be obtained at:7 * Public License, v. 2.0, a copy of which may be obtained at:
8 * http://mozilla.org/MPL/2.0/8 * http://mozilla.org/MPL/2.0/
9*/9 */
assets/javascripts/lib/page.js+153−82
@@ -13,50 +13,54 @@ · 12 unmodified lines
13 * Based on github.com/visionmedia/page.js13 * Based on github.com/visionmedia/page.js
14 * Licensed under the MIT license14 * Licensed under the MIT license
15 * Copyright 2012 TJ Holowaychuk <tj@vision-media.ca>15 * Copyright 2012 TJ Holowaychuk <tj@vision-media.ca>
16*/16 */
17 17
18let running = false;18let running = false;
19let currentState = null;19let currentState = null;
20const callbacks = [];20const callbacks = [];
21 21
22this.page = function(value, fn) {22this.page = function (value, fn) {
23 if (typeof value === 'function') {23 if (typeof value === "function") {
24 page('*', value);24 page("*", value);
25 } else if (typeof fn === 'function') {25 } else if (typeof fn === "function") {
26 const route = new Route(value);26 const route = new Route(value);
27 callbacks.push(route.middleware(fn));27 callbacks.push(route.middleware(fn));
28 } else if (typeof value === 'string') {28 } else if (typeof value === "string") {
29 page.show(value, fn);29 page.show(value, fn);
30 } else {30 } else {
31 page.start(value);31 page.start(value);
32 }32 }
33};33};
34 34
35page.start = function(options) {35page.start = function (options) {
36 if (options == null) { options = {}; }36 if (options == null) {
37 options = {};
38 }
37 if (!running) {39 if (!running) {
38 running = true;40 running = true;
39 addEventListener('popstate', onpopstate);41 addEventListener("popstate", onpopstate);
40 addEventListener('click', onclick);42 addEventListener("click", onclick);
41 page.replace(currentPath(), null, null, true);43 page.replace(currentPath(), null, null, true);
42 }44 }
43};45};
44 46
45page.stop = function() {47page.stop = function () {
46 if (running) {48 if (running) {
47 running = false;49 running = false;
48 removeEventListener('click', onclick);50 removeEventListener("click", onclick);
49 removeEventListener('popstate', onpopstate);51 removeEventListener("popstate", onpopstate);
50 }52 }
51};53};
52 54
53page.show = function(path, state) {55page.show = function (path, state) {
54 let res;56 let res;
55 if (path === (currentState != null ? currentState.path : undefined)) { return; }57 if (path === (currentState != null ? currentState.path : undefined)) {
58 return;
59 }
56 const context = new Context(path, state);60 const context = new Context(path, state);
57 const previousState = currentState;61 const previousState = currentState;
58 currentState = context.state;62 currentState = context.state;
59 if (res = page.dispatch(context)) {63 if ((res = page.dispatch(context))) {
60 currentState = previousState;64 currentState = previousState;
61 location.assign(res);65 location.assign(res);
62 } else {66 } else {
@@ -67,12 +71,14 @@ page.show = function(path, state) { · 4 unmodified lines
67 return context;71 return context;
68};72};
69 73
70page.replace = function(path, state, skipDispatch, init) {74page.replace = function (path, state, skipDispatch, init) {
71 let result;75 let result;
72 let context = new Context(path, state || currentState);76 let context = new Context(path, state || currentState);
73 context.init = init;77 context.init = init;
74 currentState = context.state;78 currentState = context.state;
75 if (!skipDispatch) { result = page.dispatch(context); }79 if (!skipDispatch) {
80 result = page.dispatch(context);
81 }
76 if (result) {82 if (result) {
77 context = new Context(result);83 context = new Context(result);
78 context.init = init;84 context.init = init;
@@ -81,15 +87,19 @@ page.replace = function(path, state, skipDispatch, init) { · 2 unmodified lines
81 }87 }
82 context.replaceState();88 context.replaceState();
83 updateCanonicalLink();89 updateCanonicalLink();
84 if (!skipDispatch) { track(); }90 if (!skipDispatch) {
91 track();
92 }
85 return context;93 return context;
86};94};
87 95
88page.dispatch = function(context) {96page.dispatch = function (context) {
89 let i = 0;97 let i = 0;
90 var next = function() {98 var next = function () {
91 let fn, res;99 let fn, res;
92 if (fn = callbacks[i++]) { res = fn(context, next); }100 if ((fn = callbacks[i++])) {
101 res = fn(context, next);
102 }
93 return res;103 return res;
94 };104 };
95 return next();105 return next();
@@ -113,11 +123,11 @@ class Context { · 17 unmodified lines
113 }123 }
114 124
115 static isLastState(state) {125 static isLastState(state) {
116 return state.id === (this.stateId - 1);126 return state.id === this.stateId - 1;
117 }127 }
118 128
119 static isInitialPopState(state) {129 static isInitialPopState(state) {
120 return (state.path === this.initialPath) && (this.stateId === 1);130 return state.path === this.initialPath && this.stateId === 1;
121 }131 }
122 132
123 static isSameSession(state) {133 static isSameSession(state) {
@@ -125,27 +135,40 @@ class Context { · 1 unmodified line
125 }135 }
126 136
127 constructor(path, state) {137 constructor(path, state) {
128 if (path == null) { path = '/'; }138 if (path == null) {
139 path = "/";
140 }
129 this.path = path;141 this.path = path;
130 if (state == null) { state = {}; }142 if (state == null) {
143 state = {};
144 }
131 this.state = state;145 this.state = state;
132 this.pathname = this.path.replace(/(?:\?([^#]*))?(?:#(.*))?$/, (_, query, hash) => {146 this.pathname = this.path.replace(
133 this.query = query;147 /(?:\?([^#]*))?(?:#(.*))?$/,
134 this.hash = hash;148 (_, query, hash) => {
135 return '';149 this.query = query;
136 });150 this.hash = hash;
137 151 return "";
138 if (this.state.id == null) { this.state.id = this.constructor.stateId++; }152 },
139 if (this.state.sessionId == null) { this.state.sessionId = this.constructor.sessionId; }153 );
154
155 if (this.state.id == null) {
156 this.state.id = this.constructor.stateId++;
157 }
158 if (this.state.sessionId == null) {
159 this.state.sessionId = this.constructor.sessionId;
160 }
140 this.state.path = this.path;161 this.state.path = this.path;
141 }162 }
142 163
143 pushState() {164 pushState() {
144 history.pushState(this.state, '', this.path);165 history.pushState(this.state, "", this.path);
145 }166 }
146 167
147 replaceState() {168 replaceState() {
148 try { history.replaceState(this.state, '', this.path); } catch (error) {} // NS_ERROR_FAILURE in Firefox169 try {
170 history.replaceState(this.state, "", this.path);
171 } catch (error) {} // NS_ERROR_FAILURE in Firefox
149 }172 }
150}173}
151Context.initClass();174Context.initClass();
@@ -153,7 +176,9 @@ Context.initClass(); · 1 unmodified line
153class Route {176class Route {
154 constructor(path, options) {177 constructor(path, options) {
155 this.path = path;178 this.path = path;
156 if (options == null) { options = {}; }179 if (options == null) {
180 options = {};
181 }
157 this.keys = [];182 this.keys = [];
158 this.regexp = pathtoRegexp(this.path, this.keys);183 this.regexp = pathtoRegexp(this.path, this.keys);
159 }184 }
@@ -172,13 +197,17 @@ class Route { · 12 unmodified lines
172 197
173 match(path, params) {198 match(path, params) {
174 let matchData;199 let matchData;
175 if (!(matchData = this.regexp.exec(path))) { return; }200 if (!(matchData = this.regexp.exec(path))) {
201 return;
202 }
176 203
177 const iterable = matchData.slice(1);204 const iterable = matchData.slice(1);
178 for (let i = 0; i < iterable.length; i++) {205 for (let i = 0; i < iterable.length; i++) {
179 var key;206 var key;
180 var value = iterable[i];207 var value = iterable[i];
181 if (typeof value === 'string') { value = decodeURIComponent(value); }208 if (typeof value === "string") {
209 value = decodeURIComponent(value);
210 }
182 if ((key = this.keys[i])) {211 if ((key = this.keys[i])) {
183 params[key.name] = value;212 params[key.name] = value;
184 } else {213 } else {
@@ -189,32 +218,50 @@ class Route { · 4 unmodified lines
189 }218 }
190}219}
191 220
192var pathtoRegexp = function(path, keys) {221var pathtoRegexp = function (path, keys) {
193 if (path instanceof RegExp) { return path; }222 if (path instanceof RegExp) {
223 return path;
224 }
194 225
195 if (path instanceof Array) { path = `(${path.join('|')})`; }226 if (path instanceof Array) {
227 path = `(${path.join("|")})`;
228 }
196 path = path229 path = path
197 .replace(/\/\(/g, '(?:/')230 .replace(/\/\(/g, "(?:/")
198 .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional) {231 .replace(
199 if (slash == null) { slash = ''; }232 /(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g,
200 if (format == null) { format = ''; }233 function (_, slash, format, key, capture, optional) {
201 keys.push({name: key, optional: !!optional});234 if (slash == null) {
202 let str = optional ? '' : slash;235 slash = "";
203 str += '(?:';236 }
204 if (optional) { str += slash; }237 if (format == null) {
205 str += format;238 format = "";
206 str += capture || (format ? '([^/.]+?)' : '([^/]+?)');239 }
207 str += ')';240 keys.push({ name: key, optional: !!optional });
208 if (optional) { str += optional; }241 let str = optional ? "" : slash;
209 return str;242 str += "(?:";
210 }).replace(/([\/.])/g, '\\$1')243 if (optional) {
211 .replace(/\*/g, '(.*)');244 str += slash;
245 }
246 str += format;
247 str += capture || (format ? "([^/.]+?)" : "([^/]+?)");
248 str += ")";
249 if (optional) {
250 str += optional;
251 }
252 return str;
253 },
254 )
255 .replace(/([\/.])/g, "\\$1")
256 .replace(/\*/g, "(.*)");
212 257
213 return new RegExp(`^${path}$`);258 return new RegExp(`^${path}$`);
214};259};
215 260
216var onpopstate = function(event) {261var onpopstate = function (event) {
217 if (!event.state || Context.isInitialPopState(event.state)) { return; }262 if (!event.state || Context.isInitialPopState(event.state)) {
263 return;
264 }
218 265
219 if (Context.isSameSession(event.state)) {266 if (Context.isSameSession(event.state)) {
220 page.replace(event.state.path, event.state);267 page.replace(event.state.path, event.state);
@@ -223,59 +270,83 @@ var onpopstate = function(event) { · 2 unmodified lines
223 }270 }
224};271};
225 272
226var onclick = function(event) {273var onclick = function (event) {
227 try {274 try {
228 if ((event.which !== 1) || event.metaKey || event.ctrlKey || event.shiftKey || event.defaultPrevented) { return; }275 if (
276 event.which !== 1 ||
277 event.metaKey ||
278 event.ctrlKey ||
279 event.shiftKey ||
280 event.defaultPrevented
281 ) {
282 return;
283 }
229 } catch (error) {284 } catch (error) {
230 return;285 return;
231 }286 }
232 287
233 let link = $.eventTarget(event);288 let link = $.eventTarget(event);
234 while (link && (link.tagName !== 'A')) { link = link.parentNode; }289 while (link && link.tagName !== "A") {
290 link = link.parentNode;
291 }
235 292
236 if (link && !link.target && isSameOrigin(link.href)) {293 if (link && !link.target && isSameOrigin(link.href)) {
237 event.preventDefault();294 event.preventDefault();
238 let path = link.pathname + link.search + link.hash;295 let path = link.pathname + link.search + link.hash;
239 path = path.replace(/^\/\/+/, '/'); // IE11 bug296 path = path.replace(/^\/\/+/, "/"); // IE11 bug
240 page.show(path);297 page.show(path);
241 }298 }
242};299};
243 300
244var isSameOrigin = url => url.indexOf(`${location.protocol}//${location.hostname}`) === 0;301var isSameOrigin = (url) =>
302 url.indexOf(`${location.protocol}//${location.hostname}`) === 0;
245 303
246var updateCanonicalLink = function() {304var updateCanonicalLink = function () {
247 if (!this.canonicalLink) { this.canonicalLink = document.head.querySelector('link[rel="canonical"]'); }305 if (!this.canonicalLink) {
248 return this.canonicalLink.setAttribute('href', `https://${location.host}${location.pathname}`);306 this.canonicalLink = document.head.querySelector('link[rel="canonical"]');
307 }
308 return this.canonicalLink.setAttribute(
309 "href",
310 `https://${location.host}${location.pathname}`,
311 );
249};312};
250 313
251const trackers = [];314const trackers = [];
252 315
253page.track = function(fn) {316page.track = function (fn) {
254 trackers.push(fn);317 trackers.push(fn);
255};318};
256 319
257var track = function() {320var track = function () {
258 if (app.config.env !== 'production') { return; }321 if (app.config.env !== "production") {
259 if (navigator.doNotTrack === '1') { return; }322 return;
260 if (navigator.globalPrivacyControl) { return; }323 }
324 if (navigator.doNotTrack === "1") {
325 return;
326 }
327 if (navigator.globalPrivacyControl) {
328 return;
329 }
261 330
262 const consentGiven = Cookies.get('analyticsConsent');331 const consentGiven = Cookies.get("analyticsConsent");
263 const consentAsked = Cookies.get('analyticsConsentAsked');332 const consentAsked = Cookies.get("analyticsConsentAsked");
264 333
265 if (consentGiven === '1') {334 if (consentGiven === "1") {
266 for (var tracker of Array.from(trackers)) { tracker.call(); }335 for (var tracker of Array.from(trackers)) {
267 } else if ((consentGiven === undefined) && (consentAsked === undefined)) {336 tracker.call();
337 }
338 } else if (consentGiven === undefined && consentAsked === undefined) {
268 // Only ask for consent once per browser session339 // Only ask for consent once per browser session
269 Cookies.set('analyticsConsentAsked', '1');340 Cookies.set("analyticsConsentAsked", "1");
270 341
271 new app.views.Notif('AnalyticsConsent', {autoHide: null});342 new app.views.Notif("AnalyticsConsent", { autoHide: null });
272 }343 }
273};344};
274 345
275this.resetAnalytics = function() {346this.resetAnalytics = function () {
276 for (var cookie of Array.from(document.cookie.split(/;\s?/))) {347 for (var cookie of Array.from(document.cookie.split(/;\s?/))) {
277 var name = cookie.split('=')[0];348 var name = cookie.split("=")[0];
278 if ((name[0] === '_') && (name[1] !== '_')) {349 if (name[0] === "_" && name[1] !== "_") {
279 Cookies.expire(name);350 Cookies.expire(name);
280 }351 }
281 }352 }
assets/javascripts/lib/util.js+308−157
@@ -16,32 +16,52 @@ · 15 unmodified lines
16//16//
17 17
18let smoothDistance, smoothDuration, smoothEnd, smoothStart;18let smoothDistance, smoothDuration, smoothEnd, smoothStart;
19this.$ = function(selector, el) {19this.$ = function (selector, el) {
20 if (el == null) { el = document; }20 if (el == null) {
21 try { return el.querySelector(selector); } catch (error) {}21 el = document;
22 }
23 try {
24 return el.querySelector(selector);
25 } catch (error) {}
22};26};
23 27
24this.$$ = function(selector, el) {28this.$$ = function (selector, el) {
25 if (el == null) { el = document; }29 if (el == null) {
26 try { return el.querySelectorAll(selector); } catch (error) {}30 el = document;
31 }
32 try {
33 return el.querySelectorAll(selector);
34 } catch (error) {}
27};35};
28 36
29$.id = id => document.getElementById(id);37$.id = (id) => document.getElementById(id);
30 38
31$.hasChild = function(parent, el) {39$.hasChild = function (parent, el) {
32 if (!parent) { return; }40 if (!parent) {
41 return;
42 }
33 while (el) {43 while (el) {
34 if (el === parent) { return true; }44 if (el === parent) {
35 if (el === document.body) { return; }45 return true;
46 }
47 if (el === document.body) {
48 return;
49 }
36 el = el.parentNode;50 el = el.parentNode;
37 }51 }
38};52};
39 53
40$.closestLink = function(el, parent) {54$.closestLink = function (el, parent) {
41 if (parent == null) { parent = document.body; }55 if (parent == null) {
56 parent = document.body;
57 }
42 while (el) {58 while (el) {
43 if (el.tagName === 'A') { return el; }59 if (el.tagName === "A") {
44 if (el === parent) { return; }60 return el;
61 }
62 if (el === parent) {
63 return;
64 }
45 el = el.parentNode;65 el = el.parentNode;
46 }66 }
47};67};
@@ -50,55 +70,85 @@ $.closestLink = function(el, parent) { · 2 unmodified lines
50// Events70// Events
51//71//
52 72
53$.on = function(el, event, callback, useCapture) {73$.on = function (el, event, callback, useCapture) {
54 if (useCapture == null) { useCapture = false; }74 if (useCapture == null) {
55 if (event.indexOf(' ') >= 0) {75 useCapture = false;
56 for (var name of Array.from(event.split(' '))) { $.on(el, name, callback); }76 }
77 if (event.indexOf(" ") >= 0) {
78 for (var name of Array.from(event.split(" "))) {
79 $.on(el, name, callback);
80 }
57 } else {81 } else {
58 el.addEventListener(event, callback, useCapture);82 el.addEventListener(event, callback, useCapture);
59 }83 }
60};84};
61 85
62$.off = function(el, event, callback, useCapture) {86$.off = function (el, event, callback, useCapture) {
63 if (useCapture == null) { useCapture = false; }87 if (useCapture == null) {
64 if (event.indexOf(' ') >= 0) {88 useCapture = false;
65 for (var name of Array.from(event.split(' '))) { $.off(el, name, callback); }89 }
90 if (event.indexOf(" ") >= 0) {
91 for (var name of Array.from(event.split(" "))) {
92 $.off(el, name, callback);
93 }
66 } else {94 } else {
67 el.removeEventListener(event, callback, useCapture);95 el.removeEventListener(event, callback, useCapture);
68 }96 }
69};97};
70 98
71$.trigger = function(el, type, canBubble, cancelable) {99$.trigger = function (el, type, canBubble, cancelable) {
72 if (canBubble == null) { canBubble = true; }100 if (canBubble == null) {
73 if (cancelable == null) { cancelable = true; }101 canBubble = true;
74 const event = document.createEvent('Event');102 }
103 if (cancelable == null) {
104 cancelable = true;
105 }
106 const event = document.createEvent("Event");
75 event.initEvent(type, canBubble, cancelable);107 event.initEvent(type, canBubble, cancelable);
76 el.dispatchEvent(event);108 el.dispatchEvent(event);
77};109};
78 110
79$.click = function(el) {111$.click = function (el) {
80 const event = document.createEvent('MouseEvent');112 const event = document.createEvent("MouseEvent");
81 event.initMouseEvent('click', true, true, window, null, 0, 0, 0, 0, false, false, false, false, 0, null);113 event.initMouseEvent(
114 "click",
115 true,
116 true,
117 window,
118 null,
119 0,
120 0,
121 0,
122 0,
123 false,
124 false,
125 false,
126 false,
127 0,
128 null,
129 );
82 el.dispatchEvent(event);130 el.dispatchEvent(event);
83};131};
84 132
85$.stopEvent = function(event) {133$.stopEvent = function (event) {
86 event.preventDefault();134 event.preventDefault();
87 event.stopPropagation();135 event.stopPropagation();
88 event.stopImmediatePropagation();136 event.stopImmediatePropagation();
89};137};
90 138
91$.eventTarget = event => event.target.correspondingUseElement || event.target;139$.eventTarget = (event) => event.target.correspondingUseElement || event.target;
92 140
93//141//
94// Manipulation142// Manipulation
95//143//
96 144
97const buildFragment = function(value) {145const buildFragment = function (value) {
98 const fragment = document.createDocumentFragment();146 const fragment = document.createDocumentFragment();
99 147
100 if ($.isCollection(value)) {148 if ($.isCollection(value)) {
101 for (var child of Array.from($.makeArray(value))) { fragment.appendChild(child); }149 for (var child of Array.from($.makeArray(value))) {
150 fragment.appendChild(child);
151 }
102 } else {152 } else {
103 fragment.innerHTML = value;153 fragment.innerHTML = value;
104 }154 }
@@ -106,36 +156,40 @@ const buildFragment = function(value) { · 1 unmodified line
106 return fragment;156 return fragment;
107};157};
108 158
109$.append = function(el, value) {159$.append = function (el, value) {
110 if (typeof value === 'string') {160 if (typeof value === "string") {
111 el.insertAdjacentHTML('beforeend', value);161 el.insertAdjacentHTML("beforeend", value);
112 } else {162 } else {
113 if ($.isCollection(value)) { value = buildFragment(value); }163 if ($.isCollection(value)) {
164 value = buildFragment(value);
165 }
114 el.appendChild(value);166 el.appendChild(value);
115 }167 }
116};168};
117 169
118$.prepend = function(el, value) {170$.prepend = function (el, value) {
119 if (!el.firstChild) {171 if (!el.firstChild) {
120 $.append(value);172 $.append(value);
121 } else if (typeof value === 'string') {173 } else if (typeof value === "string") {
122 el.insertAdjacentHTML('afterbegin', value);174 el.insertAdjacentHTML("afterbegin", value);
123 } else {175 } else {
124 if ($.isCollection(value)) { value = buildFragment(value); }176 if ($.isCollection(value)) {
177 value = buildFragment(value);
178 }
125 el.insertBefore(value, el.firstChild);179 el.insertBefore(value, el.firstChild);
126 }180 }
127};181};
128 182
129$.before = function(el, value) {183$.before = function (el, value) {
130 if ((typeof value === 'string') || $.isCollection(value)) {184 if (typeof value === "string" || $.isCollection(value)) {
131 value = buildFragment(value);185 value = buildFragment(value);
132 }186 }
133 187
134 el.parentNode.insertBefore(value, el);188 el.parentNode.insertBefore(value, el);
135};189};
136 190
137$.after = function(el, value) {191$.after = function (el, value) {
138 if ((typeof value === 'string') || $.isCollection(value)) {192 if (typeof value === "string" || $.isCollection(value)) {
139 value = buildFragment(value);193 value = buildFragment(value);
140 }194 }
141 195
@@ -146,11 +200,13 @@ $.after = function(el, value) { · 4 unmodified lines
146 }200 }
147};201};
148 202
149$.remove = function(value) {203$.remove = function (value) {
150 if ($.isCollection(value)) {204 if ($.isCollection(value)) {
151 for (var el of Array.from($.makeArray(value))) { if (el.parentNode != null) {205 for (var el of Array.from($.makeArray(value))) {
152 el.parentNode.removeChild(el);206 if (el.parentNode != null) {
153 } }207 el.parentNode.removeChild(el);
208 }
209 }
154 } else {210 } else {
155 if (value.parentNode != null) {211 if (value.parentNode != null) {
156 value.parentNode.removeChild(value);212 value.parentNode.removeChild(value);
@@ -158,13 +214,15 @@ $.remove = function(value) { · 1 unmodified line
158 }214 }
159};215};
160 216
161$.empty = function(el) {217$.empty = function (el) {
162 while (el.firstChild) { el.removeChild(el.firstChild); }218 while (el.firstChild) {
219 el.removeChild(el.firstChild);
220 }
163};221};
164 222
165// Calls the function while the element is off the DOM to avoid triggering223// Calls the function while the element is off the DOM to avoid triggering
166// unnecessary reflows and repaints.224// unnecessary reflows and repaints.
167$.batchUpdate = function(el, fn) {225$.batchUpdate = function (el, fn) {
168 const parent = el.parentNode;226 const parent = el.parentNode;
169 const sibling = el.nextSibling;227 const sibling = el.nextSibling;
170 parent.removeChild(el);228 parent.removeChild(el);
@@ -182,14 +240,16 @@ $.batchUpdate = function(el, fn) { · 11 unmodified lines
182// Offset240// Offset
183//241//
184 242
185$.rect = el => el.getBoundingClientRect();243$.rect = (el) => el.getBoundingClientRect();
186 244
187$.offset = function(el, container) {245$.offset = function (el, container) {
188 if (container == null) { container = document.body; }246 if (container == null) {
247 container = document.body;
248 }
189 let top = 0;249 let top = 0;
190 let left = 0;250 let left = 0;
191 251
192 while (el && (el !== container)) {252 while (el && el !== container) {
193 top += el.offsetTop;253 top += el.offsetTop;
194 left += el.offsetLeft;254 left += el.offsetLeft;
195 el = el.offsetParent;255 el = el.offsetParent;
@@ -197,102 +257,131 @@ $.offset = function(el, container) { · 1 unmodified line
197 257
198 return {258 return {
199 top,259 top,
200 left260 left,
201 };261 };
202};262};
203 263
204$.scrollParent = function(el) {264$.scrollParent = function (el) {
205 while ((el = el.parentNode) && (el.nodeType === 1)) {265 while ((el = el.parentNode) && el.nodeType === 1) {
206 var needle;266 var needle;
207 if (el.scrollTop > 0) { break; }267 if (el.scrollTop > 0) {
208 if ((needle = __guard__(getComputedStyle(el), x => x.overflowY), ['auto', 'scroll'].includes(needle))) { break; }268 break;
269 }
270 if (
271 ((needle = __guard__(getComputedStyle(el), (x) => x.overflowY)),
272 ["auto", "scroll"].includes(needle))
273 ) {
274 break;
275 }
209 }276 }
210 return el;277 return el;
211};278};
212 279
213$.scrollTo = function(el, parent, position, options) {280$.scrollTo = function (el, parent, position, options) {
214 if (position == null) { position = 'center'; }281 if (position == null) {
215 if (options == null) { options = {}; }282 position = "center";
216 if (!el) { return; }283 }
284 if (options == null) {
285 options = {};
286 }
287 if (!el) {
288 return;
289 }
217 290
218 if (parent == null) { parent = $.scrollParent(el); }291 if (parent == null) {
219 if (!parent) { return; }292 parent = $.scrollParent(el);
293 }
294 if (!parent) {
295 return;
296 }
220 297
221 const parentHeight = parent.clientHeight;298 const parentHeight = parent.clientHeight;
222 const parentScrollHeight = parent.scrollHeight;299 const parentScrollHeight = parent.scrollHeight;
223 if (!(parentScrollHeight > parentHeight)) { return; }300 if (!(parentScrollHeight > parentHeight)) {
301 return;
302 }
224 303
225 const {304 const { top } = $.offset(el, parent);
226 top305 const { offsetTop } = parent.firstElementChild;
227 } = $.offset(el, parent);
228 const {
229 offsetTop
230 } = parent.firstElementChild;
231 306
232 switch (position) {307 switch (position) {
233 case 'top':308 case "top":
234 parent.scrollTop = top - offsetTop - ((options.margin != null) ? options.margin : 0);309 parent.scrollTop =
310 top - offsetTop - (options.margin != null ? options.margin : 0);
235 break;311 break;
236 case 'center':312 case "center":
237 parent.scrollTop = top - Math.round((parentHeight / 2) - (el.offsetHeight / 2));313 parent.scrollTop =
314 top - Math.round(parentHeight / 2 - el.offsetHeight / 2);
238 break;315 break;
239 case 'continuous':316 case "continuous":
240 var {317 var { scrollTop } = parent;
241 scrollTop
242 } = parent;
243 var height = el.offsetHeight;318 var height = el.offsetHeight;
244 319
245 var lastElementOffset = parent.lastElementChild.offsetTop + parent.lastElementChild.offsetHeight;320 var lastElementOffset =
246 var offsetBottom = lastElementOffset > 0 ? parentScrollHeight - lastElementOffset : 0;321 parent.lastElementChild.offsetTop +
322 parent.lastElementChild.offsetHeight;
323 var offsetBottom =
324 lastElementOffset > 0 ? parentScrollHeight - lastElementOffset : 0;
247 325
248 // If the target element is above the visible portion of its scrollable326 // If the target element is above the visible portion of its scrollable
249 // ancestor, move it near the top with a gap = options.topGap * target's height.327 // ancestor, move it near the top with a gap = options.topGap * target's height.
250 if ((top - offsetTop) <= (scrollTop + (height * (options.topGap || 1)))) {328 if (top - offsetTop <= scrollTop + height * (options.topGap || 1)) {
251 parent.scrollTop = top - offsetTop - (height * (options.topGap || 1));329 parent.scrollTop = top - offsetTop - height * (options.topGap || 1);
252 // If the target element is below the visible portion of its scrollable330 // If the target element is below the visible portion of its scrollable
253 // ancestor, move it near the bottom with a gap = options.bottomGap * target's height.331 // ancestor, move it near the bottom with a gap = options.bottomGap * target's height.
254 } else if ((top + offsetBottom) >= ((scrollTop + parentHeight) - (height * ((options.bottomGap || 1) + 1)))) {332 } else if (
255 parent.scrollTop = ((top + offsetBottom) - parentHeight) + (height * ((options.bottomGap || 1) + 1));333 top + offsetBottom >=
334 scrollTop + parentHeight - height * ((options.bottomGap || 1) + 1)
335 ) {
336 parent.scrollTop =
337 top +
338 offsetBottom -
339 parentHeight +
340 height * ((options.bottomGap || 1) + 1);
256 }341 }
257 break;342 break;
258 }343 }
259};344};
260 345
261$.scrollToWithImageLock = function(el, parent, ...args) {346$.scrollToWithImageLock = function (el, parent, ...args) {
262 if (parent == null) { parent = $.scrollParent(el); }347 if (parent == null) {
263 if (!parent) { return; }348 parent = $.scrollParent(el);
349 }
350 if (!parent) {
351 return;
352 }
264 353
265 $.scrollTo(el, parent, ...Array.from(args));354 $.scrollTo(el, parent, ...Array.from(args));
266 355
267 // Lock the scroll position on the target element for up to 3 seconds while356 // Lock the scroll position on the target element for up to 3 seconds while
268 // nearby images are loaded and rendered.357 // nearby images are loaded and rendered.
269 for (var image of Array.from(parent.getElementsByTagName('img'))) {358 for (var image of Array.from(parent.getElementsByTagName("img"))) {
270 if (!image.complete) {359 if (!image.complete) {
271 (function() {360 (function () {
272 let timeout;361 let timeout;
273 const onLoad = function(event) {362 const onLoad = function (event) {
274 clearTimeout(timeout);363 clearTimeout(timeout);
275 unbind(event.target);364 unbind(event.target);
276 return $.scrollTo(el, parent, ...Array.from(args));365 return $.scrollTo(el, parent, ...Array.from(args));
277 };366 };
278 367
279 var unbind = target => $.off(target, 'load', onLoad);368 var unbind = (target) => $.off(target, "load", onLoad);
280 369
281 $.on(image, 'load', onLoad);370 $.on(image, "load", onLoad);
282 return timeout = setTimeout(unbind.bind(null, image), 3000);371 return (timeout = setTimeout(unbind.bind(null, image), 3000));
283 })();372 })();
284 }373 }
285 }374 }
286};375};
287 376
288// Calls the function while locking the element's position relative to the window.377// Calls the function while locking the element's position relative to the window.
289$.lockScroll = function(el, fn) {378$.lockScroll = function (el, fn) {
290 let parent;379 let parent;
291 if (parent = $.scrollParent(el)) {380 if ((parent = $.scrollParent(el))) {
292 let {381 let { top } = $.rect(el);
293 top382 if (![document.body, document.documentElement].includes(parent)) {
294 } = $.rect(el);383 top -= $.rect(parent).top;
295 if (![document.body, document.documentElement].includes(parent)) { top -= $.rect(parent).top; }384 }
296 fn();385 fn();
297 parent.scrollTop = $.offset(el, parent).top - top;386 parent.scrollTop = $.offset(el, parent).top - top;
298 } else {387 } else {
@@ -300,9 +389,14 @@ $.lockScroll = function(el, fn) { · 1 unmodified line
300 }389 }
301};390};
302 391
303let smoothScroll = (smoothStart = (smoothEnd = (smoothDistance = (smoothDuration = null))));392let smoothScroll =
393 (smoothStart =
394 smoothEnd =
395 smoothDistance =
396 smoothDuration =
397 null);
304 398
305$.smoothScroll = function(el, end) {399$.smoothScroll = function (el, end) {
306 if (!window.requestAnimationFrame) {400 if (!window.requestAnimationFrame) {
307 el.scrollTop = end;401 el.scrollTop = end;
308 return;402 return;
@@ -322,12 +416,18 @@ $.smoothScroll = function(el, end) { · 13 unmodified lines
322 smoothDuration = Math.min(300, Math.abs(smoothDistance));416 smoothDuration = Math.min(300, Math.abs(smoothDistance));
323 const startTime = Date.now();417 const startTime = Date.now();
324 418
325 smoothScroll = function() {419 smoothScroll = function () {
326 const p = Math.min(1, (Date.now() - startTime) / smoothDuration);420 const p = Math.min(1, (Date.now() - startTime) / smoothDuration);
327 const y = Math.max(0, Math.floor(smoothStart + (smoothDistance * (p < 0.5 ? 2 * p * p : (p * (4 - (p * 2))) - 1))));421 const y = Math.max(
422 0,
423 Math.floor(
424 smoothStart +
425 smoothDistance * (p < 0.5 ? 2 * p * p : p * (4 - p * 2) - 1),
426 ),
427 );
328 el.scrollTop = y;428 el.scrollTop = y;
329 if (p === 1) {429 if (p === 1) {
330 return smoothScroll = null;430 return (smoothScroll = null);
331 } else {431 } else {
332 return requestAnimationFrame(smoothScroll);432 return requestAnimationFrame(smoothScroll);
333 }433 }
@@ -339,7 +439,7 @@ $.smoothScroll = function(el, end) { · 5 unmodified lines
339// Utilities439// Utilities
340//440//
341 441
342$.extend = function(target, ...objects) {442$.extend = function (target, ...objects) {
343 for (var object of Array.from(objects)) {443 for (var object of Array.from(objects)) {
344 if (object) {444 if (object) {
345 for (var key in object) {445 for (var key in object) {
@@ -351,7 +451,7 @@ $.extend = function(target, ...objects) { · 5 unmodified lines
351 return target;451 return target;
352};452};
353 453
354$.makeArray = function(object) {454$.makeArray = function (object) {
355 if (Array.isArray(object)) {455 if (Array.isArray(object)) {
356 return object;456 return object;
357 } else {457 } else {
@@ -359,7 +459,7 @@ $.makeArray = function(object) { · 1 unmodified line
359 }459 }
360};460};
361 461
362$.arrayDelete = function(array, object) {462$.arrayDelete = function (array, object) {
363 const index = array.indexOf(object);463 const index = array.indexOf(object);
364 if (index >= 0) {464 if (index >= 0) {
365 array.splice(index, 1);465 array.splice(index, 1);
@@ -370,45 +470,49 @@ $.arrayDelete = function(array, object) { · 4 unmodified lines
370};470};
371 471
372// Returns true if the object is an array or a collection of DOM elements.472// Returns true if the object is an array or a collection of DOM elements.
373$.isCollection = object => Array.isArray(object) || (typeof (object != null ? object.item : undefined) === 'function');473$.isCollection = (object) =>
474 Array.isArray(object) ||
475 typeof (object != null ? object.item : undefined) === "function";
374 476
375const ESCAPE_HTML_MAP = {477const ESCAPE_HTML_MAP = {
376 '&': '&amp;',478 "&": "&amp;",
377 '<': '&lt;',479 "<": "&lt;",
378 '>': '&gt;',480 ">": "&gt;",
379 '"': '&quot;',481 '"': "&quot;",
380 "'": '&#x27;',482 "'": "&#x27;",
381 '/': '&#x2F;'483 "/": "&#x2F;",
382};484};
383 485
384const ESCAPE_HTML_REGEXP = /[&<>"'\/]/g;486const ESCAPE_HTML_REGEXP = /[&<>"'\/]/g;
385 487
386$.escape = string => string.replace(ESCAPE_HTML_REGEXP, match => ESCAPE_HTML_MAP[match]);488$.escape = (string) =>
489 string.replace(ESCAPE_HTML_REGEXP, (match) => ESCAPE_HTML_MAP[match]);
387 490
388const ESCAPE_REGEXP = /([.*+?^=!:${}()|\[\]\/\\])/g;491const ESCAPE_REGEXP = /([.*+?^=!:${}()|\[\]\/\\])/g;
389 492
390$.escapeRegexp = string => string.replace(ESCAPE_REGEXP, "\\$1");493$.escapeRegexp = (string) => string.replace(ESCAPE_REGEXP, "\\$1");
391 494
392$.urlDecode = string => decodeURIComponent(string.replace(/\+/g, '%20'));495$.urlDecode = (string) => decodeURIComponent(string.replace(/\+/g, "%20"));
393 496
394$.classify = function(string) {497$.classify = function (string) {
395 string = string.split('_');498 string = string.split("_");
396 for (let i = 0; i < string.length; i++) {499 for (let i = 0; i < string.length; i++) {
397 var substr = string[i];500 var substr = string[i];
398 string[i] = substr[0].toUpperCase() + substr.slice(1);501 string[i] = substr[0].toUpperCase() + substr.slice(1);
399 }502 }
400 return string.join('');503 return string.join("");
401};504};
402 505
403$.framify = function(fn, obj) {506$.framify = function (fn, obj) {
404 if (window.requestAnimationFrame) {507 if (window.requestAnimationFrame) {
405 return (...args) => requestAnimationFrame(fn.bind(obj, ...Array.from(args)));508 return (...args) =>
509 requestAnimationFrame(fn.bind(obj, ...Array.from(args)));
406 } else {510 } else {
407 return fn;511 return fn;
408 }512 }
409};513};
410 514
411$.requestAnimationFrame = function(fn) {515$.requestAnimationFrame = function (fn) {
412 if (window.requestAnimationFrame) {516 if (window.requestAnimationFrame) {
413 requestAnimationFrame(fn);517 requestAnimationFrame(fn);
414 } else {518 } else {
@@ -420,37 +524,81 @@ $.requestAnimationFrame = function(fn) { · 5 unmodified lines
420// Miscellaneous524// Miscellaneous
421//525//
422 526
423$.noop = function() {};527$.noop = function () {};
424 528
425$.popup = function(value) {529$.popup = function (value) {
426 try {530 try {
427 const win = window.open();531 const win = window.open();
428 if (win.opener) { win.opener = null; }532 if (win.opener) {
533 win.opener = null;
534 }
429 win.location = value.href || value;535 win.location = value.href || value;
430 } catch (error) {536 } catch (error) {
431 window.open(value.href || value, '_blank');537 window.open(value.href || value, "_blank");
432 }538 }
433};539};
434 540
435let isMac = null;541let isMac = null;
436$.isMac = () => isMac != null ? isMac : (isMac = (navigator.userAgent != null ? navigator.userAgent.indexOf('Mac') : undefined) >= 0);542$.isMac = () =>
543 isMac != null
544 ? isMac
545 : (isMac =
546 (navigator.userAgent != null
547 ? navigator.userAgent.indexOf("Mac")
548 : undefined) >= 0);
437 549
438let isIE = null;550let isIE = null;
439$.isIE = () => isIE != null ? isIE : (isIE = ((navigator.userAgent != null ? navigator.userAgent.indexOf('MSIE') : undefined) >= 0) || ((navigator.userAgent != null ? navigator.userAgent.indexOf('rv:11.0') : undefined) >= 0));551$.isIE = () =>
552 isIE != null
553 ? isIE
554 : (isIE =
555 (navigator.userAgent != null
556 ? navigator.userAgent.indexOf("MSIE")
557 : undefined) >= 0 ||
558 (navigator.userAgent != null
559 ? navigator.userAgent.indexOf("rv:11.0")
560 : undefined) >= 0);
440 561
441let isChromeForAndroid = null;562let isChromeForAndroid = null;
442$.isChromeForAndroid = () => isChromeForAndroid != null ? isChromeForAndroid : (isChromeForAndroid = ((navigator.userAgent != null ? navigator.userAgent.indexOf('Android') : undefined) >= 0) && /Chrome\/([.0-9])+ Mobile/.test(navigator.userAgent));563$.isChromeForAndroid = () =>
564 isChromeForAndroid != null
565 ? isChromeForAndroid
566 : (isChromeForAndroid =
567 (navigator.userAgent != null
568 ? navigator.userAgent.indexOf("Android")
569 : undefined) >= 0 &&
570 /Chrome\/([.0-9])+ Mobile/.test(navigator.userAgent));
443 571
444let isAndroid = null;572let isAndroid = null;
445$.isAndroid = () => isAndroid != null ? isAndroid : (isAndroid = (navigator.userAgent != null ? navigator.userAgent.indexOf('Android') : undefined) >= 0);573$.isAndroid = () =>
574 isAndroid != null
575 ? isAndroid
576 : (isAndroid =
577 (navigator.userAgent != null
578 ? navigator.userAgent.indexOf("Android")
579 : undefined) >= 0);
446 580
447let isIOS = null;581let isIOS = null;
448$.isIOS = () => isIOS != null ? isIOS : (isIOS = ((navigator.userAgent != null ? navigator.userAgent.indexOf('iPhone') : undefined) >= 0) || ((navigator.userAgent != null ? navigator.userAgent.indexOf('iPad') : undefined) >= 0));582$.isIOS = () =>
449 583 isIOS != null
450$.overlayScrollbarsEnabled = function() {584 ? isIOS
451 if (!$.isMac()) { return false; }585 : (isIOS =
452 const div = document.createElement('div');586 (navigator.userAgent != null
453 div.setAttribute('style', 'width: 100px; height: 100px; overflow: scroll; position: absolute');587 ? navigator.userAgent.indexOf("iPhone")
588 : undefined) >= 0 ||
589 (navigator.userAgent != null
590 ? navigator.userAgent.indexOf("iPad")
591 : undefined) >= 0);
592
593$.overlayScrollbarsEnabled = function () {
594 if (!$.isMac()) {
595 return false;
596 }
597 const div = document.createElement("div");
598 div.setAttribute(
599 "style",
600 "width: 100px; height: 100px; overflow: scroll; position: absolute",
601 );
454 document.body.appendChild(div);602 document.body.appendChild(div);
455 const result = div.offsetWidth === div.clientWidth;603 const result = div.offsetWidth === div.clientWidth;
456 document.body.removeChild(div);604 document.body.removeChild(div);
@@ -458,36 +606,39 @@ $.overlayScrollbarsEnabled = function() { · 1 unmodified line
458};606};
459 607
460const HIGHLIGHT_DEFAULTS = {608const HIGHLIGHT_DEFAULTS = {
461 className: 'highlight',609 className: "highlight",
462 delay: 1000610 delay: 1000,
463};611};
464 612
465$.highlight = function(el, options) {613$.highlight = function (el, options) {
466 if (options == null) { options = {}; }614 if (options == null) {
615 options = {};
616 }
467 options = $.extend({}, HIGHLIGHT_DEFAULTS, options);617 options = $.extend({}, HIGHLIGHT_DEFAULTS, options);
468 el.classList.add(options.className);618 el.classList.add(options.className);
469 setTimeout((() => el.classList.remove(options.className)), options.delay);619 setTimeout(() => el.classList.remove(options.className), options.delay);
470};620};
471 621
472$.copyToClipboard = function(string) {622$.copyToClipboard = function (string) {
473 let result;623 let result;
474 const textarea = document.createElement('textarea');624 const textarea = document.createElement("textarea");
475 textarea.style.position = 'fixed';625 textarea.style.position = "fixed";
476 textarea.style.opacity = 0;626 textarea.style.opacity = 0;
477 textarea.value = string;627 textarea.value = string;
478 document.body.appendChild(textarea);628 document.body.appendChild(textarea);
479 try {629 try {
480 textarea.select();630 textarea.select();
481 result = !!document.execCommand('copy');631 result = !!document.execCommand("copy");
482 } catch (error) {632 } catch (error) {
483 result = false;633 result = false;
484 }634 } finally {
485 finally {
486 document.body.removeChild(textarea);635 document.body.removeChild(textarea);
487 }636 }
488 return result;637 return result;
489};638};
490 639
491function __guard__(value, transform) {640function __guard__(value, transform) {
492 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;641 return typeof value !== "undefined" && value !== null
493}642 ? transform(value)
643 : undefined;
644}
assets/javascripts/models/doc.js+63−29
@@ -12,10 +12,12 @@ app.models.Doc = class Doc extends app.Model { · 11 unmodified lines
12 constructor() {12 constructor() {
13 super(...arguments);13 super(...arguments);
14 this.reset(this);14 this.reset(this);
15 this.slug_without_version = this.slug.split('~')[0];15 this.slug_without_version = this.slug.split("~")[0];
16 this.fullName = `${this.name}` + (this.version ? ` ${this.version}` : '');16 this.fullName = `${this.name}` + (this.version ? ` ${this.version}` : "");
17 this.icon = this.slug_without_version;17 this.icon = this.slug_without_version;
18 if (this.version) { this.short_version = this.version.split(' ')[0]; }18 if (this.version) {
19 this.short_version = this.version.split(" ")[0];
20 }
19 this.text = this.toEntry().text;21 this.text = this.toEntry().text;
20 }22 }
21 23
@@ -26,17 +28,25 @@ app.models.Doc = class Doc extends app.Model { · 4 unmodified lines
26 28
27 resetEntries(entries) {29 resetEntries(entries) {
28 this.entries = new app.collections.Entries(entries);30 this.entries = new app.collections.Entries(entries);
29 this.entries.each(entry => { return entry.doc = this; });31 this.entries.each((entry) => {
32 return (entry.doc = this);
33 });
30 }34 }
31 35
32 resetTypes(types) {36 resetTypes(types) {
33 this.types = new app.collections.Types(types);37 this.types = new app.collections.Types(types);
34 this.types.each(type => { return type.doc = this; });38 this.types.each((type) => {
39 return (type.doc = this);
40 });
35 }41 }
36 42
37 fullPath(path) {43 fullPath(path) {
38 if (path == null) { path = ''; }44 if (path == null) {
39 if (path[0] !== '/') { path = `/${path}`; }45 path = "";
46 }
47 if (path[0] !== "/") {
48 path = `/${path}`;
49 }
40 return `/${this.slug}${path}`;50 return `/${this.slug}${path}`;
41 }51 }
42 52
@@ -49,45 +59,57 @@ app.models.Doc = class Doc extends app.Model { · 6 unmodified lines
49 }59 }
50 60
51 indexUrl() {61 indexUrl() {
52 return `${app.indexHost()}/${this.slug}/${app.config.index_filename}?${this.mtime}`;62 return `${app.indexHost()}/${this.slug}/${app.config.index_filename}?${
63 this.mtime
64 }`;
53 }65 }
54 66
55 toEntry() {67 toEntry() {
56 if (this.entry) { return this.entry; }68 if (this.entry) {
69 return this.entry;
70 }
57 this.entry = new app.models.Entry({71 this.entry = new app.models.Entry({
58 doc: this,72 doc: this,
59 name: this.fullName,73 name: this.fullName,
60 path: 'index'74 path: "index",
61 });75 });
62 if (this.version) { this.entry.addAlias(this.name); }76 if (this.version) {
77 this.entry.addAlias(this.name);
78 }
63 return this.entry;79 return this.entry;
64 }80 }
65 81
66 findEntryByPathAndHash(path, hash) {82 findEntryByPathAndHash(path, hash) {
67 let entry;83 let entry;
68 if (hash && (entry = this.entries.findBy('path', `${path}#${hash}`))) {84 if (hash && (entry = this.entries.findBy("path", `${path}#${hash}`))) {
69 return entry;85 return entry;
70 } else if (path === 'index') {86 } else if (path === "index") {
71 return this.toEntry();87 return this.toEntry();
72 } else {88 } else {
73 return this.entries.findBy('path', path);89 return this.entries.findBy("path", path);
74 }90 }
75 }91 }
76 92
77 load(onSuccess, onError, options) {93 load(onSuccess, onError, options) {
78 if (options == null) { options = {}; }94 if (options == null) {
79 if (options.readCache && this._loadFromCache(onSuccess)) { return; }95 options = {};
96 }
97 if (options.readCache && this._loadFromCache(onSuccess)) {
98 return;
99 }
80 100
81 const callback = data => {101 const callback = (data) => {
82 this.reset(data);102 this.reset(data);
83 onSuccess();103 onSuccess();
84 if (options.writeCache) { this._setCache(data); }104 if (options.writeCache) {
105 this._setCache(data);
106 }
85 };107 };
86 108
87 return ajax({109 return ajax({
88 url: this.indexUrl(),110 url: this.indexUrl(),
89 success: callback,111 success: callback,
90 error: onError112 error: onError,
91 });113 });
92 }114 }
93 115
@@ -97,7 +119,9 @@ app.models.Doc = class Doc extends app.Model { · 3 unmodified lines
97 119
98 _loadFromCache(onSuccess) {120 _loadFromCache(onSuccess) {
99 let data;121 let data;
100 if (!(data = this._getCache())) { return; }122 if (!(data = this._getCache())) {
123 return;
124 }
101 125
102 const callback = () => {126 const callback = () => {
103 this.reset(data);127 this.reset(data);
@@ -110,7 +134,9 @@ app.models.Doc = class Doc extends app.Model { · 6 unmodified lines
110 134
111 _getCache() {135 _getCache() {
112 let data;136 let data;
113 if (!(data = app.localStorage.get(this.slug))) { return; }137 if (!(data = app.localStorage.get(this.slug))) {
138 return;
139 }
114 140
115 if (data[0] === this.mtime) {141 if (data[0] === this.mtime) {
116 return data[1];142 return data[1];
@@ -125,7 +151,9 @@ app.models.Doc = class Doc extends app.Model { · 8 unmodified lines
125 }151 }
126 152
127 install(onSuccess, onError, onProgress) {153 install(onSuccess, onError, onProgress) {
128 if (this.installing) { return; }154 if (this.installing) {
155 return;
156 }
129 this.installing = true;157 this.installing = true;
130 158
131 const error = () => {159 const error = () => {
@@ -133,7 +161,7 @@ app.models.Doc = class Doc extends app.Model { · 1 unmodified line
133 onError();161 onError();
134 };162 };
135 163
136 const success = data => {164 const success = (data) => {
137 this.installing = null;165 this.installing = null;
138 app.db.store(this, data, onSuccess, error);166 app.db.store(this, data, onSuccess, error);
139 };167 };
@@ -143,12 +171,14 @@ app.models.Doc = class Doc extends app.Model { · 3 unmodified lines
143 success,171 success,
144 error,172 error,
145 progress: onProgress,173 progress: onProgress,
146 timeout: 3600174 timeout: 3600,
147 });175 });
148 }176 }
149 177
150 uninstall(onSuccess, onError) {178 uninstall(onSuccess, onError) {
151 if (this.installing) { return; }179 if (this.installing) {
180 return;
181 }
152 this.installing = true;182 this.installing = true;
153 183
154 const success = () => {184 const success = () => {
@@ -165,12 +195,16 @@ app.models.Doc = class Doc extends app.Model { · 10 unmodified lines
165 }195 }
166 196
167 getInstallStatus(callback) {197 getInstallStatus(callback) {
168 app.db.version(this, value => callback({installed: !!value, mtime: value}));198 app.db.version(this, (value) =>
199 callback({ installed: !!value, mtime: value }),
200 );
169 }201 }
170 202
171 isOutdated(status) {203 isOutdated(status) {
172 if (!status) { return false; }204 if (!status) {
173 const isInstalled = status.installed || app.settings.get('autoInstall');205 return false;
174 return isInstalled && (this.mtime !== status.mtime);206 }
207 const isInstalled = status.installed || app.settings.get("autoInstall");
208 return isInstalled && this.mtime !== status.mtime;
175 }209 }
176};210};
assets/javascripts/models/entry.js+51−48
@@ -8,63 +8,62 @@ · 7 unmodified lines
8 */8 */
9//= require app/searcher9//= require app/searcher
10 10
11(function() {11(function () {
12 let applyAliases = undefined;12 let applyAliases = undefined;
13 const Cls = (app.models.Entry = class Entry extends app.Model {13 const Cls = (app.models.Entry = class Entry extends app.Model {
14 static initClass() {14 static initClass() {
15
16 let ALIASES;15 let ALIASES;
17 applyAliases = function(string) {16 applyAliases = function (string) {
18 if (ALIASES.hasOwnProperty(string)) {17 if (ALIASES.hasOwnProperty(string)) {
19 return [string, ALIASES[string]];18 return [string, ALIASES[string]];
20 } else {19 } else {
21 const words = string.split('.');20 const words = string.split(".");
22 for (let i = 0; i < words.length; i++) {21 for (let i = 0; i < words.length; i++) {
23 var word = words[i];22 var word = words[i];
24 if (ALIASES.hasOwnProperty(word)) {23 if (ALIASES.hasOwnProperty(word)) {
25 words[i] = ALIASES[word];24 words[i] = ALIASES[word];
26 return [string, words.join('.')];25 return [string, words.join(".")];
27 }26 }
28 }27 }
29 }28 }
30 return string;29 return string;
31 };30 };
32 31
33 this.ALIASES = (ALIASES = {32 this.ALIASES = ALIASES = {
34 'angular': 'ng',33 angular: "ng",
35 'angular.js': 'ng',34 "angular.js": "ng",
36 'backbone.js': 'bb',35 "backbone.js": "bb",
37 'c++': 'cpp',36 "c++": "cpp",
38 'coffeescript': 'cs',37 coffeescript: "cs",
39 'crystal': 'cr',38 crystal: "cr",
40 'elixir': 'ex',39 elixir: "ex",
41 'javascript': 'js',40 javascript: "js",
42 'julia': 'jl',41 julia: "jl",
43 'jquery': '$',42 jquery: "$",
44 'knockout.js': 'ko',43 "knockout.js": "ko",
45 'kubernetes': 'k8s',44 kubernetes: "k8s",
46 'less': 'ls',45 less: "ls",
47 'lodash': '_',46 lodash: "_",
48 'löve': 'love',47 löve: "love",
49 'marionette': 'mn',48 marionette: "mn",
50 'markdown': 'md',49 markdown: "md",
51 'matplotlib': 'mpl',50 matplotlib: "mpl",
52 'modernizr': 'mdr',51 modernizr: "mdr",
53 'moment.js': 'mt',52 "moment.js": "mt",
54 'openjdk': 'java',53 openjdk: "java",
55 'nginx': 'ngx',54 nginx: "ngx",
56 'numpy': 'np',55 numpy: "np",
57 'pandas': 'pd',56 pandas: "pd",
58 'postgresql': 'pg',57 postgresql: "pg",
59 'python': 'py',58 python: "py",
60 'ruby.on.rails': 'ror',59 "ruby.on.rails": "ror",
61 'ruby': 'rb',60 ruby: "rb",
62 'rust': 'rs',61 rust: "rs",
63 'sass': 'scss',62 sass: "scss",
64 'tensorflow': 'tf',63 tensorflow: "tf",
65 'typescript': 'ts',64 typescript: "ts",
66 'underscore.js': '_'65 "underscore.js": "_",
67 });66 };
68 }67 }
69 // Attributes: name, type, path68 // Attributes: name, type, path
70 69
@@ -75,16 +74,18 @@ · 4 unmodified lines
75 74
76 addAlias(name) {75 addAlias(name) {
77 const text = applyAliases(app.Searcher.normalizeString(name));76 const text = applyAliases(app.Searcher.normalizeString(name));
78 if (!Array.isArray(this.text)) { this.text = [this.text]; }77 if (!Array.isArray(this.text)) {
78 this.text = [this.text];
79 }
79 this.text.push(Array.isArray(text) ? text[1] : text);80 this.text.push(Array.isArray(text) ? text[1] : text);
80 }81 }
81 82
82 fullPath() {83 fullPath() {
83 return this.doc.fullPath(this.isIndex() ? '' : this.path);84 return this.doc.fullPath(this.isIndex() ? "" : this.path);
84 }85 }
85 86
86 dbPath() {87 dbPath() {
87 return this.path.replace(/#.*/, '');88 return this.path.replace(/#.*/, "");
88 }89 }
89 90
90 filePath() {91 filePath() {
@@ -96,17 +97,19 @@ · 5 unmodified lines
96 }97 }
97 98
98 _filePath() {99 _filePath() {
99 let result = this.path.replace(/#.*/, '');100 let result = this.path.replace(/#.*/, "");
100 if (result.slice(-5) !== '.html') { result += '.html'; }101 if (result.slice(-5) !== ".html") {
102 result += ".html";
103 }
101 return result;104 return result;
102 }105 }
103 106
104 isIndex() {107 isIndex() {
105 return this.path === 'index';108 return this.path === "index";
106 }109 }
107 110
108 getType() {111 getType() {
109 return this.doc.types.findBy('name', this.type);112 return this.doc.types.findBy("name", this.type);
110 }113 }
111 114
112 loadFile(onSuccess, onError) {115 loadFile(onSuccess, onError) {
assets/javascripts/models/model.js+4−1
@@ -2,6 +2,9 @@ · 1 unmodified line
2// Sanity-check the conversion and remove this comment.2// Sanity-check the conversion and remove this comment.
3app.Model = class Model {3app.Model = class Model {
4 constructor(attributes) {4 constructor(attributes) {
5 for (var key in attributes) { var value = attributes[key]; this[key] = value; }5 for (var key in attributes) {
6 var value = attributes[key];
7 this[key] = value;
8 }
6 }9 }
7};10};
assets/javascripts/models/type.js+2−2
@@ -13,14 +13,14 @@ app.models.Type = class Type extends app.Model { · 12 unmodified lines
13 }13 }
14 14
15 entries() {15 entries() {
16 return this.doc.entries.findAllBy('type', this.name);16 return this.doc.entries.findAllBy("type", this.name);
17 }17 }
18 18
19 toEntry() {19 toEntry() {
20 return new app.models.Entry({20 return new app.models.Entry({
21 doc: this.doc,21 doc: this.doc,
22 name: `${this.doc.name} / ${this.name}`,22 name: `${this.doc.name} / ${this.name}`,
23 path: '..' + this.fullPath()23 path: ".." + this.fullPath(),
24 });24 });
25 }25 }
26};26};
assets/javascripts/templates/base.js+6−4
@@ -6,14 +6,16 @@ · 5 unmodified lines
6 * DS102: Remove unnecessary code created because of implicit returns6 * DS102: Remove unnecessary code created because of implicit returns
7 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md7 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
8 */8 */
9app.templates.render = function(name, value, ...args) {9app.templates.render = function (name, value, ...args) {
10 const template = app.templates[name];10 const template = app.templates[name];
11 11
12 if (Array.isArray(value)) {12 if (Array.isArray(value)) {
13 let result = '';13 let result = "";
14 for (var val of Array.from(value)) { result += template(val, ...Array.from(args)); }14 for (var val of Array.from(value)) {
15 result += template(val, ...Array.from(args));
16 }
15 return result;17 return result;
16 } else if (typeof template === 'function') {18 } else if (typeof template === "function") {
17 return template(value, ...Array.from(args));19 return template(value, ...Array.from(args));
18 } else {20 } else {
19 return template;21 return template;
assets/javascripts/templates/error_tmpl.js+53−36
@@ -7,62 +7,79 @@ · 6 unmodified lines
7 * DS207: Consider shorter variations of null checks7 * DS207: Consider shorter variations of null checks
8 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md8 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
9 */9 */
10const error = function(title, text, links) {10const error = function (title, text, links) {
11 if (text == null) { text = ''; }11 if (text == null) {
12 if (links == null) { links = ''; }12 text = "";
13 if (text) { text = `<p class="_error-text">${text}</p>`; }13 }
14 if (links) { links = `<p class="_error-links">${links}</p>`; }14 if (links == null) {
15 links = "";
16 }
17 if (text) {
18 text = `<p class="_error-text">${text}</p>`;
19 }
20 if (links) {
21 links = `<p class="_error-links">${links}</p>`;
22 }
15 return `<div class="_error"><h1 class="_error-title">${title}</h1>${text}${links}</div>`;23 return `<div class="_error"><h1 class="_error-title">${title}</h1>${text}${links}</div>`;
16};24};
17 25
18const back = '<a href="#" data-behavior="back" class="_error-link">Go back</a>';26const back = '<a href="#" data-behavior="back" class="_error-link">Go back</a>';
19 27
20app.templates.notFoundPage = () => error(" Page not found. ",28app.templates.notFoundPage = () =>
21 " It may be missing from the source documentation or this could be a bug. ",29 error(
22 back);30 " Page not found. ",
31 " It may be missing from the source documentation or this could be a bug. ",
32 back,
33 );
23 34
24app.templates.pageLoadError = () => error(" The page failed to load. ",35app.templates.pageLoadError = () =>
25 ` 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>36 error(
37 " The page failed to load. ",
38 ` 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>
26If you're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. `,39If you're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. `,
27 ` ${back} &middot; <a href="/#${location.pathname}" target="_top" class="_error-link">Reload</a>40 ` ${back} &middot; <a href="/#${location.pathname}" target="_top" class="_error-link">Reload</a>
28&middot; <a href="#" class="_error-link" data-retry>Retry</a> `41&middot; <a href="#" class="_error-link" data-retry>Retry</a> `,
29);42 );
30 43
31app.templates.bootError = () => error(" The app failed to load. ",44app.templates.bootError = () =>
32 ` Check your Internet connection and try <a href="#" data-behavior="reload">reloading</a>.<br>45 error(
33If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. `46 " The app failed to load. ",
34);47 ` Check your Internet connection and try <a href="#" data-behavior="reload">reloading</a>.<br>
48If you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. `,
49 );
35 50
36app.templates.offlineError = function(reason, exception) {51app.templates.offlineError = function (reason, exception) {
37 if (reason === 'cookie_blocked') {52 if (reason === "cookie_blocked") {
38 return error(" Cookies must be enabled to use offline mode. ");53 return error(" Cookies must be enabled to use offline mode. ");
39 }54 }
40 55
41 reason = (() => { switch (reason) {56 reason = (() => {
42 case 'not_supported':57 switch (reason) {
43 return ` DevDocs requires IndexedDB to cache documentations for offline access.<br>58 case "not_supported":
59 return ` DevDocs requires IndexedDB to cache documentations for offline access.<br>
44Unfortunately your browser either doesn't support IndexedDB or doesn't make it available. `;60Unfortunately your browser either doesn't support IndexedDB or doesn't make it available. `;
45 case 'buggy':61 case "buggy":
46 return ` DevDocs requires IndexedDB to cache documentations for offline access.<br>62 return ` DevDocs requires IndexedDB to cache documentations for offline access.<br>
47Unfortunately your browser's implementation of IndexedDB contains bugs that prevent DevDocs from using it. `;63Unfortunately your browser's implementation of IndexedDB contains bugs that prevent DevDocs from using it. `;
48 case 'private_mode':64 case "private_mode":
49 return ` Your browser appears to be running in private mode.<br>65 return ` Your browser appears to be running in private mode.<br>
50This prevents DevDocs from caching documentations for offline access.`;66This prevents DevDocs from caching documentations for offline access.`;
51 case 'exception':67 case "exception":
52 return ` An error occurred when trying to open the IndexedDB database:<br>68 return ` An error occurred when trying to open the IndexedDB database:<br>
53<code class="_label">${exception.name}: ${exception.message}</code> `;69<code class="_label">${exception.name}: ${exception.message}</code> `;
54 case 'cant_open':70 case "cant_open":
55 return ` An error occurred when trying to open the IndexedDB database:<br>71 return ` An error occurred when trying to open the IndexedDB database:<br>
56<code class="_label">${exception.name}: ${exception.message}</code><br>72<code class="_label">${exception.name}: ${exception.message}</code><br>
57This could be because you're browsing in private mode or have disallowed offline storage on the domain. `;73This could be because you're browsing in private mode or have disallowed offline storage on the domain. `;
58 case 'version':74 case "version":
59 return ` The IndexedDB database was modified with a newer version of the app.<br>75 return ` The IndexedDB database was modified with a newer version of the app.<br>
60<a href="#" data-behavior="reload">Reload the page</a> to use offline mode. `;76<a href="#" data-behavior="reload">Reload the page</a> to use offline mode. `;
61 case 'empty':77 case "empty":
62 return " The IndexedDB database appears to be corrupted. Try <a href=\"#\" data-behavior=\"reset\">resetting the app</a>. ";78 return ' The IndexedDB database appears to be corrupted. Try <a href="#" data-behavior="reset">resetting the app</a>. ';
63 } })();79 }
80 })();
64 81
65 return error('Offline mode is unavailable.', reason);82 return error("Offline mode is unavailable.", reason);
66};83};
67 84
68app.templates.unsupportedBrowser = `\85app.templates.unsupportedBrowser = `\
assets/javascripts/templates/notice_tmpl.js+7−7
@@ -5,12 +5,12 @@ · 4 unmodified lines
5 * DS102: Remove unnecessary code created because of implicit returns5 * DS102: Remove unnecessary code created because of implicit returns
6 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md6 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
7 */7 */
8const notice = text => `<p class="_notice-text">${text}</p>`;8const notice = (text) => `<p class="_notice-text">${text}</p>`;
9 9
10app.templates.singleDocNotice = doc => notice(` You're browsing the ${doc.fullName} documentation. To browse all docs, go to10app.templates.singleDocNotice = (doc) =>
11<a href="//${app.config.production_host}" target="_top">${app.config.production_host}</a> (or press <code>esc</code>). `11 notice(` You're browsing the ${doc.fullName} documentation. To browse all docs, go to
12);12<a href="//${app.config.production_host}" target="_top">${app.config.production_host}</a> (or press <code>esc</code>). `);
13 13
14app.templates.disabledDocNotice = () => notice(` <strong>This documentation is disabled.</strong>14app.templates.disabledDocNotice = () =>
15To enable it, go to <a href="/settings" class="_notice-link">Preferences</a>. `15 notice(` <strong>This documentation is disabled.</strong>
16);16To enable it, go to <a href="/settings" class="_notice-link">Preferences</a>. `);
assets/javascripts/templates/notif_tmpl.js+69−34
@@ -6,7 +6,7 @@ · 5 unmodified lines
6 * DS102: Remove unnecessary code created because of implicit returns6 * DS102: Remove unnecessary code created because of implicit returns
7 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md7 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
8 */8 */
9const notif = function(title, html) {9const notif = function (title, html) {
10 html = html.replace(/<a /g, '<a class="_notif-link" ');10 html = html.replace(/<a /g, '<a class="_notif-link" ');
11 return ` <h5 class="_notif-title">${title}</h5>11 return ` <h5 class="_notif-title">${title}</h5>
12${html}12${html}
@@ -14,32 +14,56 @@ ${html} · 1 unmodified line
14`;14`;
15};15};
16 16
17const textNotif = (title, message) => notif(title, `<p class="_notif-text">${message}`);17const textNotif = (title, message) =>
18 notif(title, `<p class="_notif-text">${message}`);
18 19
19app.templates.notifUpdateReady = () => textNotif("<span data-behavior=\"reboot\">DevDocs has been updated.</span>",20app.templates.notifUpdateReady = () =>
20 "<span data-behavior=\"reboot\"><a href=\"#\" data-behavior=\"reboot\">Reload the page</a> to use the new version.</span>");21 textNotif(
22 '<span data-behavior="reboot">DevDocs has been updated.</span>',
23 '<span data-behavior="reboot"><a href="#" data-behavior="reboot">Reload the page</a> to use the new version.</span>',
24 );
21 25
22app.templates.notifError = () => textNotif(" Oops, an error occurred. ",26app.templates.notifError = () =>
23 ` Try <a href="#" data-behavior="hard-reload">reloading</a>, and if the problem persists,27 textNotif(
28 " Oops, an error occurred. ",
29 ` Try <a href="#" data-behavior="hard-reload">reloading</a>, and if the problem persists,
24<a href="#" data-behavior="reset">resetting the app</a>.<br>30<a href="#" data-behavior="reset">resetting the app</a>.<br>
25You can also report this issue on <a href="https://github.com/freeCodeCamp/devdocs/issues/new" target="_blank" rel="noopener">GitHub</a>. `31You can also report this issue on <a href="https://github.com/freeCodeCamp/devdocs/issues/new" target="_blank" rel="noopener">GitHub</a>. `,
26);32 );
27 33
28app.templates.notifQuotaExceeded = () => textNotif(" The offline database has exceeded its size limitation. ",34app.templates.notifQuotaExceeded = () =>
29 " Unfortunately this quota can't be detected programmatically, and the database can't be opened while over the quota, so it had to be reset. ");35 textNotif(
36 " The offline database has exceeded its size limitation. ",
37 " Unfortunately this quota can't be detected programmatically, and the database can't be opened while over the quota, so it had to be reset. ",
38 );
30 39
31app.templates.notifCookieBlocked = () => textNotif(" Please enable cookies. ",40app.templates.notifCookieBlocked = () =>
32 " DevDocs will not work properly if cookies are disabled. ");41 textNotif(
42 " Please enable cookies. ",
43 " DevDocs will not work properly if cookies are disabled. ",
44 );
33 45
34app.templates.notifInvalidLocation = () => textNotif(` DevDocs must be loaded from ${app.config.production_host} `,46app.templates.notifInvalidLocation = () =>
35 " Otherwise things are likely to break. ");47 textNotif(
48 ` DevDocs must be loaded from ${app.config.production_host} `,
49 " Otherwise things are likely to break. ",
50 );
36 51
37app.templates.notifImportInvalid = () => textNotif(" Oops, an error occurred. ",52app.templates.notifImportInvalid = () =>
38 " The file you selected is invalid. ");53 textNotif(
54 " Oops, an error occurred. ",
55 " The file you selected is invalid. ",
56 );
39 57
40app.templates.notifNews = news => notif('Changelog', `<div class="_notif-content _notif-news">${app.templates.newsList(news, {years: false})}</div>`);58app.templates.notifNews = (news) =>
59 notif(
60 "Changelog",
61 `<div class="_notif-content _notif-news">${app.templates.newsList(news, {
62 years: false,
63 })}</div>`,
64 );
41 65
42app.templates.notifUpdates = function(docs, disabledDocs) {66app.templates.notifUpdates = function (docs, disabledDocs) {
43 let doc;67 let doc;
44 let html = '<div class="_notif-content _notif-news">';68 let html = '<div class="_notif-content _notif-news">';
45 69
@@ -48,9 +72,11 @@ app.templates.notifUpdates = function(docs, disabledDocs) { · 2 unmodified lines
48 html += '<ul class="_notif-list">';72 html += '<ul class="_notif-list">';
49 for (doc of Array.from(docs)) {73 for (doc of Array.from(docs)) {
50 html += `<li>${doc.name}`;74 html += `<li>${doc.name}`;
51 if (doc.release) { html += ` <code>&rarr;</code> ${doc.release}`; }75 if (doc.release) {
76 html += ` <code>&rarr;</code> ${doc.release}`;
77 }
52 }78 }
53 html += '</ul></div>';79 html += "</ul></div>";
54 }80 }
55 81
56 if (disabledDocs.length > 0) {82 if (disabledDocs.length > 0) {
@@ -58,26 +84,35 @@ app.templates.notifUpdates = function(docs, disabledDocs) { · 1 unmodified line
58 html += '<ul class="_notif-list">';84 html += '<ul class="_notif-list">';
59 for (doc of Array.from(disabledDocs)) {85 for (doc of Array.from(disabledDocs)) {
60 html += `<li>${doc.name}`;86 html += `<li>${doc.name}`;
61 if (doc.release) { html += ` <code>&rarr;</code> ${doc.release}`; }87 if (doc.release) {
62 html += "<span class=\"_notif-info\"><a href=\"/settings\">Enable</a></span>";88 html += ` <code>&rarr;</code> ${doc.release}`;
89 }
90 html += '<span class="_notif-info"><a href="/settings">Enable</a></span>';
63 }91 }
64 html += '</ul></div>';92 html += "</ul></div>";
65 }93 }
66 94
67 return notif('Updates', `${html}</div>`);95 return notif("Updates", `${html}</div>`);
68};96};
69 97
70app.templates.notifShare = () => textNotif(" Hi there! ",98app.templates.notifShare = () =>
71 ` Like DevDocs? Help us reach more developers by sharing the link with your friends on99 textNotif(
100 " Hi there! ",
101 ` Like DevDocs? Help us reach more developers by sharing the link with your friends on
72<a href="https://out.devdocs.io/s/tw" target="_blank" rel="noopener">Twitter</a>, <a href="https://out.devdocs.io/s/fb" target="_blank" rel="noopener">Facebook</a>,102<a href="https://out.devdocs.io/s/tw" target="_blank" rel="noopener">Twitter</a>, <a href="https://out.devdocs.io/s/fb" target="_blank" rel="noopener">Facebook</a>,
73<a href="https://out.devdocs.io/s/re" target="_blank" rel="noopener">Reddit</a>, etc.<br>Thanks :) `103<a href="https://out.devdocs.io/s/re" target="_blank" rel="noopener">Reddit</a>, etc.<br>Thanks :) `,
74);104 );
75 105
76app.templates.notifUpdateDocs = () => textNotif(" Documentation updates available. ",106app.templates.notifUpdateDocs = () =>
77 " <a href=\"/offline\">Install them</a> as soon as possible to avoid broken pages. ");107 textNotif(
108 " Documentation updates available. ",
109 ' <a href="/offline">Install them</a> as soon as possible to avoid broken pages. ',
110 );
78 111
79app.templates.notifAnalyticsConsent = () => textNotif(" Tracking cookies ",112app.templates.notifAnalyticsConsent = () =>
80 ` We would like to gather usage data about how DevDocs is used through Google Analytics and Gauges. We only collect anonymous traffic information.113 textNotif(
114 " Tracking cookies ",
115 ` We would like to gather usage data about how DevDocs is used through Google Analytics and Gauges. We only collect anonymous traffic information.
81Please confirm if you accept our tracking cookies. You can always change your decision in the settings.116Please confirm if you accept our tracking cookies. You can always change your decision in the settings.
82<br><span class="_notif-right"><a href="#" data-behavior="accept-analytics">Accept</a> or <a href="#" data-behavior="decline-analytics">Decline</a></span> `117<br><span class="_notif-right"><a href="#" data-behavior="accept-analytics">Accept</a> or <a href="#" data-behavior="decline-analytics">Decline</a></span> `,
83);118 );
assets/javascripts/templates/pages/about_tmpl.js+24−13
@@ -8,12 +8,18 @@ · 7 unmodified lines
8 * DS207: Consider shorter variations of null checks8 * DS207: Consider shorter variations of null checks
9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
10 */10 */
11app.templates.aboutPage = function() {11app.templates.aboutPage = function () {
12 let doc;12 let doc;
13 const all_docs = app.docs.all().concat(...Array.from(app.disabledDocs.all() || []));13 const all_docs = app.docs
14 .all()
15 .concat(...Array.from(app.disabledDocs.all() || []));
14 // de-duplicate docs by doc.name16 // de-duplicate docs by doc.name
15 const docs = [];17 const docs = [];
16 for (doc of Array.from(all_docs)) { if (!(docs.find(d => d.name === doc.name))) { docs.push(doc); } }18 for (doc of Array.from(all_docs)) {
19 if (!docs.find((d) => d.name === doc.name)) {
20 docs.push(doc);
21 }
22 }
17 return `\23 return `\
18<nav class="_toc" role="directory">24<nav class="_toc" role="directory">
19 <h3 class="_toc-title">Table of Contents</h3>25 <h3 class="_toc-title">Table of Contents</h3>
@@ -79,18 +85,23 @@ app.templates.aboutPage = function() { · 59 unmodified lines
79 <th>Documentation85 <th>Documentation
80 <th>Copyright/License86 <th>Copyright/License
81 <th>Source code87 <th>Source code
82 ${((() => {88 ${(() => {
83 const result = [];89 const result = [];
84 90
85 for (doc of Array.from(docs)) { result.push(`<tr> \91 for (doc of Array.from(docs)) {
86<td><a href=\"${(doc.links != null ? doc.links.home : undefined)}\">${doc.name}</a></td> \92 result.push(`<tr> \
93<td><a href=\"${doc.links != null ? doc.links.home : undefined}\">${
94 doc.name
95 }</a></td> \
87<td>${doc.attribution}</td> \96<td>${doc.attribution}</td> \
88<td><a href=\"${(doc.links != null ? doc.links.code : undefined)}\">Source code</a></td> \97<td><a href=\"${
98 doc.links != null ? doc.links.code : undefined
99 }\">Source code</a></td> \
89</tr>`);100</tr>`);
90 }101 }
91 102
92 return result;103 return result;
93 })()).join('')}104 })().join("")}
94 </table>105 </table>
95</div>106</div>
96 107
assets/javascripts/templates/pages/help_tmpl.js+35−29
@@ -6,11 +6,11 @@ · 5 unmodified lines
6 * DS205: Consider reworking code to avoid use of IIFEs6 * DS205: Consider reworking code to avoid use of IIFEs
7 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md7 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
8 */8 */
9app.templates.helpPage = function() {9app.templates.helpPage = function () {
10 let key, value;10 let key, value;
11 const ctrlKey = $.isMac() ? 'cmd' : 'ctrl';11 const ctrlKey = $.isMac() ? "cmd" : "ctrl";
12 const navKey = $.isMac() ? 'cmd' : 'alt';12 const navKey = $.isMac() ? "cmd" : "alt";
13 const arrowScroll = app.settings.get('arrowScroll');13 const arrowScroll = app.settings.get("arrowScroll");
14 14
15 const aliases_one = {};15 const aliases_one = {};
16 const aliases_two = {};16 const aliases_two = {};
@@ -18,7 +18,8 @@ app.templates.helpPage = function() { · 1 unmodified line
18 const middle = Math.ceil(keys.length / 2) - 1;18 const middle = Math.ceil(keys.length / 2) - 1;
19 for (let i = 0; i < keys.length; i++) {19 for (let i = 0; i < keys.length; i++) {
20 key = keys[i];20 key = keys[i];
21 (i > middle ? aliases_two : aliases_one)[key] = app.models.Entry.ALIASES[key];21 (i > middle ? aliases_two : aliases_one)[key] =
22 app.models.Entry.ALIASES[key];
22 }23 }
23 24
24 return `\25 return `\
@@ -79,12 +80,12 @@ app.templates.helpPage = function() { · 54 unmodified lines
79<h3 class="_shortcuts-title">Sidebar</h3>80<h3 class="_shortcuts-title">Sidebar</h3>
80<dl class="_shortcuts-dl">81<dl class="_shortcuts-dl">
81 <dt class="_shortcuts-dt">82 <dt class="_shortcuts-dt">
82 ${arrowScroll ? '<code class="_shortcut-code">shift</code> + ' : ''}83 ${arrowScroll ? '<code class="_shortcut-code">shift</code> + ' : ""}
83 <code class="_shortcut-code">&darr;</code>84 <code class="_shortcut-code">&darr;</code>
84 <code class="_shortcut-code">&uarr;</code>85 <code class="_shortcut-code">&uarr;</code>
85 <dd class="_shortcuts-dd">Move selection86 <dd class="_shortcuts-dd">Move selection
86 <dt class="_shortcuts-dt">87 <dt class="_shortcuts-dt">
87 ${arrowScroll ? '<code class="_shortcut-code">shift</code> + ' : ''}88 ${arrowScroll ? '<code class="_shortcut-code">shift</code> + ' : ""}
88 <code class="_shortcut-code">&rarr;</code>89 <code class="_shortcut-code">&rarr;</code>
89 <code class="_shortcut-code">&larr;</code>90 <code class="_shortcut-code">&larr;</code>
90 <dd class="_shortcuts-dd">Show/hide sub-list91 <dd class="_shortcuts-dd">Show/hide sub-list
@@ -105,15 +106,16 @@ app.templates.helpPage = function() { · 14 unmodified lines
105 <code class="_shortcut-code">${navKey} + &rarr;</code>106 <code class="_shortcut-code">${navKey} + &rarr;</code>
106 <dd class="_shortcuts-dd">Go back/forward107 <dd class="_shortcuts-dd">Go back/forward
107 <dt class="_shortcuts-dt">108 <dt class="_shortcuts-dt">
108 ${arrowScroll ?109 ${
109 '<code class="_shortcut-code">&darr;</code> ' +110 arrowScroll
111 ? '<code class="_shortcut-code">&darr;</code> ' +
110 '<code class="_shortcut-code">&uarr;</code>'112 '<code class="_shortcut-code">&uarr;</code>'
111 :113 : '<code class="_shortcut-code">alt + &darr;</code> ' +
112 '<code class="_shortcut-code">alt + &darr;</code> ' +
113 '<code class="_shortcut-code">alt + &uarr;</code>' +114 '<code class="_shortcut-code">alt + &uarr;</code>' +
114 '<br>' +115 "<br>" +
115 '<code class="_shortcut-code">shift + &darr;</code> ' +116 '<code class="_shortcut-code">shift + &darr;</code> ' +
116 '<code class="_shortcut-code">shift + &uarr;</code>'}117 '<code class="_shortcut-code">shift + &uarr;</code>'
118 }
117 <dd class="_shortcuts-dd">Scroll step by step<br><br>119 <dd class="_shortcuts-dd">Scroll step by step<br><br>
118 <dt class="_shortcuts-dt">120 <dt class="_shortcuts-dt">
119 <code class="_shortcut-code">space</code>121 <code class="_shortcut-code">space</code>
@@ -167,27 +169,31 @@ app.templates.helpPage = function() { · 47 unmodified lines
167 <tr>169 <tr>
168 <th>Word170 <th>Word
169 <th>Alias171 <th>Alias
170 ${((() => {172 ${(() => {
171 const result = [];173 const result = [];
172 for (key in aliases_one) {174 for (key in aliases_one) {
173 value = aliases_one[key];175 value = aliases_one[key];
174 result.push(`<tr><td class=\"_code\">${key}<td class=\"_code\">${value}`);176 result.push(
175 }177 `<tr><td class=\"_code\">${key}<td class=\"_code\">${value}`,
176 return result;178 );
177 })()).join('')}179 }
180 return result;
181 })().join("")}
178 </table>182 </table>
179 <table>183 <table>
180 <tr>184 <tr>
181 <th>Word185 <th>Word
182 <th>Alias186 <th>Alias
183 ${((() => {187 ${(() => {
184 const result1 = [];188 const result1 = [];
185 for (key in aliases_two) {189 for (key in aliases_two) {
186 value = aliases_two[key];190 value = aliases_two[key];
187 result1.push(`<tr><td class=\"_code\">${key}<td class=\"_code\">${value}`);191 result1.push(
188 }192 `<tr><td class=\"_code\">${key}<td class=\"_code\">${value}`,
189 return result1;193 );
190 })()).join('')}194 }
195 return result1;
196 })().join("")}
191 </table>197 </table>
192</div>198</div>
193<p>Feel free to suggest new aliases on <a href="https://github.com/freeCodeCamp/devdocs/issues/new">GitHub</a>.\199<p>Feel free to suggest new aliases on <a href="https://github.com/freeCodeCamp/devdocs/issues/new">GitHub</a>.\
assets/javascripts/templates/pages/offline_tmpl.js+20−16
@@ -5,12 +5,14 @@ · 4 unmodified lines
5 * DS102: Remove unnecessary code created because of implicit returns5 * DS102: Remove unnecessary code created because of implicit returns
6 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md6 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
7 */7 */
8app.templates.offlinePage = docs => `\8app.templates.offlinePage = (docs) => `\
9<h1 class="_lined-heading">Offline Documentation</h1>9<h1 class="_lined-heading">Offline Documentation</h1>
10 10
11<div class="_docs-tools">11<div class="_docs-tools">
12 <label>12 <label>
13 <input type="checkbox" name="autoUpdate" value="1" ${app.settings.get('manualUpdate') ? '' : 'checked'}>Install updates automatically13 <input type="checkbox" name="autoUpdate" value="1" ${
14 app.settings.get("manualUpdate") ? "" : "checked"
15 }>Install updates automatically
14 </label>16 </label>
15 <div class="_docs-links">17 <div class="_docs-links">
16 <button type="button" class="_btn-link" data-action-all="install">Install all</button><button type="button" class="_btn-link" data-action-all="update"><strong>Update all</strong></button><button type="button" class="_btn-link" data-action-all="uninstall">Uninstall all</button>18 <button type="button" class="_btn-link" data-action-all="install">Install all</button><button type="button" class="_btn-link" data-action-all="update"><strong>Update all</strong></button><button type="button" class="_btn-link" data-action-all="uninstall">Uninstall all</button>
@@ -47,14 +49,15 @@ app.templates.offlinePage = docs => `\ · 30 unmodified lines
47</dl>\49</dl>\
48`;50`;
49 51
50var canICloseTheTab = function() {52var canICloseTheTab = function () {
51 if (app.ServiceWorker.isEnabled()) {53 if (app.ServiceWorker.isEnabled()) {
52 return " 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). ";54 return ' 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). ';
53 } else {55 } else {
54 let reason = "aren't available in your browser (or are disabled)";56 let reason = "aren't available in your browser (or are disabled)";
55 57
56 if (app.config.env !== 'production') {58 if (app.config.env !== "production") {
57 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>)";59 reason =
60 "are disabled in your development instance of DevDocs (enable them by setting the <code>ENABLE_SERVICE_WORKER</code> environment variable to <code>true</code>)";
58 }61 }
59 62
60 return ` No. Service Workers ${reason}, so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br>63 return ` No. Service Workers ${reason}, so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br>
@@ -62,30 +65,31 @@ The current tab will continue to function even when you go offline (provided you · 1 unmodified line
62 }65 }
63};66};
64 67
65app.templates.offlineDoc = function(doc, status) {68app.templates.offlineDoc = function (doc, status) {
66 const outdated = doc.isOutdated(status);69 const outdated = doc.isOutdated(status);
67 70
68 let html = `\71 let html = `\
69<tr data-slug="${doc.slug}"${outdated ? ' class="_highlight"' : ''}>72<tr data-slug="${doc.slug}"${outdated ? ' class="_highlight"' : ""}>
70 <td class="_docs-name _icon-${doc.icon}">${doc.fullName}</td>73 <td class="_docs-name _icon-${doc.icon}">${doc.fullName}</td>
71 <td class="_docs-size">${Math.ceil(doc.db_size / 100000) / 10}&nbsp;<small>MB</small></td>\74 <td class="_docs-size">${
75 Math.ceil(doc.db_size / 100000) / 10
76 }&nbsp;<small>MB</small></td>\
72`;77`;
73 78
74 html += !(status && status.installed) ?79 html += !(status && status.installed)
75 `\80 ? `\
76<td>-</td>81<td>-</td>
77<td><button type="button" class="_btn-link" data-action="install">Install</button></td>\82<td><button type="button" class="_btn-link" data-action="install">Install</button></td>\
78`83`
79 : outdated ?84 : outdated
80 `\85 ? `\
81<td><strong>Outdated</strong></td>86<td><strong>Outdated</strong></td>
82<td><button type="button" class="_btn-link _bold" data-action="update">Update</button> - <button type="button" class="_btn-link" data-action="uninstall">Uninstall</button></td>\87<td><button type="button" class="_btn-link _bold" data-action="update">Update</button> - <button type="button" class="_btn-link" data-action="uninstall">Uninstall</button></td>\
83`88`
84 :89 : `\
85 `\
86<td>Up&#8209;to&#8209;date</td>90<td>Up&#8209;to&#8209;date</td>
87<td><button type="button" class="_btn-link" data-action="uninstall">Uninstall</button></td>\91<td><button type="button" class="_btn-link" data-action="uninstall">Uninstall</button></td>\
88`;92`;
89 93
90 return html + '</tr>';94 return html + "</tr>";
91};95};
assets/javascripts/templates/pages/settings_tmpl.js+50−19
@@ -7,23 +7,32 @@ · 6 unmodified lines
7 */7 */
8const themeOption = ({ label, value }, settings) => `\8const themeOption = ({ label, value }, settings) => `\
9<label class="_settings-label _theme-label">9<label class="_settings-label _theme-label">
10 <input type="radio" name="theme" value="${value}"${settings.theme === value ? ' checked' : ''}>10 <input type="radio" name="theme" value="${value}"${
11 settings.theme === value ? " checked" : ""
12 }>
11 ${label}13 ${label}
12</label>\14</label>\
13`;15`;
14 16
15app.templates.settingsPage = settings => `\17app.templates.settingsPage = (settings) => `\
16<h1 class="_lined-heading">Preferences</h1>18<h1 class="_lined-heading">Preferences</h1>
17 19
18<div class="_settings-fieldset">20<div class="_settings-fieldset">
19 <h2 class="_settings-legend">Theme:</h2>21 <h2 class="_settings-legend">Theme:</h2>
20 <div class="_settings-inputs">22 <div class="_settings-inputs">
21 ${settings.autoSupported ?23 ${
22 themeOption({label: "Automatic <small>Matches system setting</small>", value: "auto"}, settings)24 settings.autoSupported
23 :25 ? themeOption(
24 ""}26 {
25 ${themeOption({label: "Light", value: "default"}, settings)}27 label: "Automatic <small>Matches system setting</small>",
26 ${themeOption({label: "Dark", value: "dark"}, settings)}28 value: "auto",
29 },
30 settings,
31 )
32 : ""
33 }
34 ${themeOption({ label: "Light", value: "default" }, settings)}
35 ${themeOption({ label: "Dark", value: "dark" }, settings)}
27 </div>36 </div>
28</div>37</div>
29 38
@@ -32,24 +41,36 @@ app.templates.settingsPage = settings => `\ · 2 unmodified lines
32 41
33 <div class="_settings-inputs">42 <div class="_settings-inputs">
34 <label class="_settings-label _setting-max-width">43 <label class="_settings-label _setting-max-width">
35 <input type="checkbox" form="settings" name="layout" value="_max-width"${settings['_max-width'] ? ' checked' : ''}>Enable fixed-width layout44 <input type="checkbox" form="settings" name="layout" value="_max-width"${
45 settings["_max-width"] ? " checked" : ""
46 }>Enable fixed-width layout
36 </label>47 </label>
37 <label class="_settings-label _setting-text-justify-hyphenate">48 <label class="_settings-label _setting-text-justify-hyphenate">
38 <input type="checkbox" form="settings" name="layout" value="_text-justify-hyphenate"${settings['_text-justify-hyphenate'] ? ' checked' : ''}>Enable justified layout and automatic hyphenation49 <input type="checkbox" form="settings" name="layout" value="_text-justify-hyphenate"${
50 settings["_text-justify-hyphenate"] ? " checked" : ""
51 }>Enable justified layout and automatic hyphenation
39 </label>52 </label>
40 <label class="_settings-label _hide-on-mobile">53 <label class="_settings-label _hide-on-mobile">
41 <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"${settings['_sidebar-hidden'] ? ' checked' : ''}>Automatically hide and show the sidebar54 <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"${
55 settings["_sidebar-hidden"] ? " checked" : ""
56 }>Automatically hide and show the sidebar
42 <small>Tip: drag the edge of the sidebar to resize it.</small>57 <small>Tip: drag the edge of the sidebar to resize it.</small>
43 </label>58 </label>
44 <label class="_settings-label _hide-on-mobile">59 <label class="_settings-label _hide-on-mobile">
45 <input type="checkbox" form="settings" name="noAutofocus" value="_no-autofocus"${settings.noAutofocus ? ' checked' : ''}>Disable autofocus of search input60 <input type="checkbox" form="settings" name="noAutofocus" value="_no-autofocus"${
61 settings.noAutofocus ? " checked" : ""
62 }>Disable autofocus of search input
46 </label>63 </label>
47 <label class="_settings-label">64 <label class="_settings-label">
48 <input type="checkbox" form="settings" name="autoInstall" value="_auto-install"${settings.autoInstall ? ' checked' : ''}>Automatically download documentation for offline use65 <input type="checkbox" form="settings" name="autoInstall" value="_auto-install"${
66 settings.autoInstall ? " checked" : ""
67 }>Automatically download documentation for offline use
49 <small>Only enable this when bandwidth isn't a concern to you.</small>68 <small>Only enable this when bandwidth isn't a concern to you.</small>
50 </label>69 </label>
51 <label class="_settings-label _hide-in-development">70 <label class="_settings-label _hide-in-development">
52 <input type="checkbox" form="settings" name="analyticsConsent"${settings.analyticsConsent ? ' checked' : ''}>Enable tracking cookies71 <input type="checkbox" form="settings" name="analyticsConsent"${
72 settings.analyticsConsent ? " checked" : ""
73 }>Enable tracking cookies
53 <small>With this checked, we enable Google Analytics and Gauges to collect anonymous traffic information.</small>74 <small>With this checked, we enable Google Analytics and Gauges to collect anonymous traffic information.</small>
54 </label>75 </label>
55 </div>76 </div>
@@ -60,20 +81,30 @@ app.templates.settingsPage = settings => `\ · 4 unmodified lines
60 81
61 <div class="_settings-inputs">82 <div class="_settings-inputs">
62 <label class="_settings-label">83 <label class="_settings-label">
63 <input type="checkbox" form="settings" name="smoothScroll" value="1"${settings.smoothScroll ? ' checked' : ''}>Use smooth scrolling84 <input type="checkbox" form="settings" name="smoothScroll" value="1"${
85 settings.smoothScroll ? " checked" : ""
86 }>Use smooth scrolling
64 </label>87 </label>
65 <label class="_settings-label _setting-native-scrollbar">88 <label class="_settings-label _setting-native-scrollbar">
66 <input type="checkbox" form="settings" name="layout" value="_native-scrollbars"${settings['_native-scrollbars'] ? ' checked' : ''}>Use native scrollbars89 <input type="checkbox" form="settings" name="layout" value="_native-scrollbars"${
90 settings["_native-scrollbars"] ? " checked" : ""
91 }>Use native scrollbars
67 </label>92 </label>
68 <label class="_settings-label">93 <label class="_settings-label">
69 <input type="checkbox" form="settings" name="arrowScroll" value="1"${settings.arrowScroll ? ' checked' : ''}>Use arrow keys to scroll the main content area94 <input type="checkbox" form="settings" name="arrowScroll" value="1"${
95 settings.arrowScroll ? " checked" : ""
96 }>Use arrow keys to scroll the main content area
70 <small>With this checked, use <code class="_label">shift</code> + <code class="_label">&uarr;</code><code class="_label">&darr;</code><code class="_label">&larr;</code><code class="_label">&rarr;</code> to navigate the sidebar.</small>97 <small>With this checked, use <code class="_label">shift</code> + <code class="_label">&uarr;</code><code class="_label">&darr;</code><code class="_label">&larr;</code><code class="_label">&rarr;</code> to navigate the sidebar.</small>
71 </label>98 </label>
72 <label class="_settings-label">99 <label class="_settings-label">
73 <input type="checkbox" form="settings" name="spaceScroll" value="1"${settings.spaceScroll ? ' checked' : ''}>Use spacebar to scroll during search100 <input type="checkbox" form="settings" name="spaceScroll" value="1"${
101 settings.spaceScroll ? " checked" : ""
102 }>Use spacebar to scroll during search
74 </label>103 </label>
75 <label class="_settings-label">104 <label class="_settings-label">
76 <input type="number" step="0.1" form="settings" name="spaceTimeout" min="0" max="5" value="${settings.spaceTimeout}"> Delay until you can scroll by pressing space105 <input type="number" step="0.1" form="settings" name="spaceTimeout" min="0" max="5" value="${
106 settings.spaceTimeout
107 }"> Delay until you can scroll by pressing space
77 <small>Time in seconds</small>108 <small>Time in seconds</small>
78 </label>109 </label>
79 </div>110 </div>
assets/javascripts/templates/pages/type_tmpl.js+9−3
@@ -5,7 +5,13 @@ · 4 unmodified lines
5 * DS102: Remove unnecessary code created because of implicit returns5 * DS102: Remove unnecessary code created because of implicit returns
6 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md6 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
7 */7 */
8app.templates.typePage = type => ` <h1>${type.doc.fullName} / ${type.name}</h1>8app.templates.typePage = (type) => ` <h1>${type.doc.fullName} / ${
9<ul class="_entry-list">${app.templates.render('typePageEntry', type.entries())}</ul> `;9 type.name
10}</h1>
11<ul class="_entry-list">${app.templates.render(
12 "typePageEntry",
13 type.entries(),
14)}</ul> `;
10 15
11app.templates.typePageEntry = entry => `<li><a href="${entry.fullPath()}">${$.escape(entry.name)}</a></li>`;16app.templates.typePageEntry = (entry) =>
17 `<li><a href="${entry.fullPath()}">${$.escape(entry.name)}</a></li>`;
assets/javascripts/templates/path_tmpl.js+13−5
@@ -5,11 +5,19 @@ · 4 unmodified lines
5 * DS102: Remove unnecessary code created because of implicit returns5 * DS102: Remove unnecessary code created because of implicit returns
6 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md6 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
7 */7 */
8const arrow = "<svg class=\"_path-arrow\"><use xlink:href=\"#icon-dir\"/></svg>";8const arrow = '<svg class="_path-arrow"><use xlink:href="#icon-dir"/></svg>';
9 9
10app.templates.path = function(doc, type, entry) {10app.templates.path = function (doc, type, entry) {
11 let html = `<a href="${doc.fullPath()}" class="_path-item _icon-${doc.icon}">${doc.fullName}</a>`;11 let html = `<a href="${doc.fullPath()}" class="_path-item _icon-${
12 if (type) { html += `${arrow}<a href="${type.fullPath()}" class="_path-item">${type.name}</a>`; }12 doc.icon
13 if (entry) { html += `${arrow}<span class="_path-item">${$.escape(entry.name)}</span>`; }13 }">${doc.fullName}</a>`;
14 if (type) {
15 html += `${arrow}<a href="${type.fullPath()}" class="_path-item">${
16 type.name
17 }</a>`;
18 }
19 if (entry) {
20 html += `${arrow}<span class="_path-item">${$.escape(entry.name)}</span>`;
21 }
14 return html;22 return html;
15};23};
assets/javascripts/templates/sidebar_tmpl.js+75−37
@@ -6,74 +6,112 @@ · 5 unmodified lines
6 * DS207: Consider shorter variations of null checks6 * DS207: Consider shorter variations of null checks
7 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md7 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
8 */8 */
9const {9const { templates } = app;
10 templates
11} = app;
12 10
13const arrow = "<svg class=\"_list-arrow\"><use xlink:href=\"#icon-dir\"/></svg>";11const arrow = '<svg class="_list-arrow"><use xlink:href="#icon-dir"/></svg>';
14 12
15templates.sidebarDoc = function(doc, options) {13templates.sidebarDoc = function (doc, options) {
16 if (options == null) { options = {}; }14 if (options == null) {
17 let link = `<a href="${doc.fullPath()}" class="_list-item _icon-${doc.icon} `;15 options = {};
18 link += options.disabled ? '_list-disabled' : '_list-dir';16 }
17 let link = `<a href="${doc.fullPath()}" class="_list-item _icon-${doc.icon} `;
18 link += options.disabled ? "_list-disabled" : "_list-dir";
19 link += `" data-slug="${doc.slug}" title="${doc.fullName}" tabindex="-1">`;19 link += `" data-slug="${doc.slug}" title="${doc.fullName}" tabindex="-1">`;
20 if (options.disabled) {20 if (options.disabled) {
21 link += `<span class="_list-enable" data-enable="${doc.slug}">Enable</span>`;21 link += `<span class="_list-enable" data-enable="${doc.slug}">Enable</span>`;
22 } else {22 } else {
23 link += arrow;23 link += arrow;
24 }24 }
25 if (doc.release) { link += `<span class="_list-count">${doc.release}</span>`; }25 if (doc.release) {
26 link += `<span class="_list-count">${doc.release}</span>`;
27 }
26 link += `<span class="_list-text">${doc.name}`;28 link += `<span class="_list-text">${doc.name}`;
27 if (options.fullName || (options.disabled && doc.version)) { link += ` ${doc.version}`; }29 if (options.fullName || (options.disabled && doc.version)) {
30 link += ` ${doc.version}`;
31 }
28 return link + "</span></a>";32 return link + "</span></a>";
29};33};
30 34
31templates.sidebarType = type => `<a href="${type.fullPath()}" class="_list-item _list-dir" data-slug="${type.slug}" tabindex="-1">${arrow}<span class="_list-count">${type.count}</span><span class="_list-text">${$.escape(type.name)}</span></a>`;35templates.sidebarType = (type) =>
36 `<a href="${type.fullPath()}" class="_list-item _list-dir" data-slug="${
37 type.slug
38 }" tabindex="-1">${arrow}<span class="_list-count">${
39 type.count
40 }</span><span class="_list-text">${$.escape(type.name)}</span></a>`;
32 41
33templates.sidebarEntry = entry => `<a href="${entry.fullPath()}" class="_list-item _list-hover" tabindex="-1">${$.escape(entry.name)}</a>`;42templates.sidebarEntry = (entry) =>
43 `<a href="${entry.fullPath()}" class="_list-item _list-hover" tabindex="-1">${$.escape(
44 entry.name,
45 )}</a>`;
34 46
35templates.sidebarResult = function(entry) {47templates.sidebarResult = function (entry) {
36 let addons = entry.isIndex() && app.disabledDocs.contains(entry.doc) ?48 let addons =
37 `<span class="_list-enable" data-enable="${entry.doc.slug}">Enable</span>`49 entry.isIndex() && app.disabledDocs.contains(entry.doc)
38 :50 ? `<span class="_list-enable" data-enable="${entry.doc.slug}">Enable</span>`
39 "<span class=\"_list-reveal\" data-reset-list title=\"Reveal in list\"></span>";51 : '<span class="_list-reveal" data-reset-list title="Reveal in list"></span>';
40 if (entry.doc.version && !entry.isIndex()) { addons += `<span class="_list-count">${entry.doc.short_version}</span>`; }52 if (entry.doc.version && !entry.isIndex()) {
41 return `<a href="${entry.fullPath()}" class="_list-item _list-hover _list-result _icon-${entry.doc.icon}" tabindex="-1">${addons}<span class="_list-text">${$.escape(entry.name)}</span></a>`;53 addons += `<span class="_list-count">${entry.doc.short_version}</span>`;
54 }
55 return `<a href="${entry.fullPath()}" class="_list-item _list-hover _list-result _icon-${
56 entry.doc.icon
57 }" tabindex="-1">${addons}<span class="_list-text">${$.escape(
58 entry.name,
59 )}</span></a>`;
42};60};
43 61
44templates.sidebarNoResults = function() {62templates.sidebarNoResults = function () {
45 let html = " <div class=\"_list-note\">No results.</div> ";63 let html = ' <div class="_list-note">No results.</div> ';
46 if (!app.isSingleDoc() && !app.disabledDocs.isEmpty()) { html += `\64 if (!app.isSingleDoc() && !app.disabledDocs.isEmpty()) {
65 html += `\
47<div class="_list-note">Note: documentations must be <a href="/settings" class="_list-note-link">enabled</a> to appear in the search.</div>\66<div class="_list-note">Note: documentations must be <a href="/settings" class="_list-note-link">enabled</a> to appear in the search.</div>\
48`; }67`;
68 }
49 return html;69 return html;
50};70};
51 71
52templates.sidebarPageLink = count => `<span role="link" class="_list-item _list-pagelink">Show more\u2026 (${count})</span>`;72templates.sidebarPageLink = (count) =>
73 `<span role="link" class="_list-item _list-pagelink">Show more\u2026 (${count})</span>`;
53 74
54templates.sidebarLabel = function(doc, options) {75templates.sidebarLabel = function (doc, options) {
55 if (options == null) { options = {}; }76 if (options == null) {
56 let label = "<label class=\"_list-item";77 options = {};
57 if (!doc.version) { label += ` _icon-${doc.icon}`; }78 }
79 let label = '<label class="_list-item';
80 if (!doc.version) {
81 label += ` _icon-${doc.icon}`;
82 }
58 label += `"><input type="checkbox" name="${doc.slug}" class="_list-checkbox" `;83 label += `"><input type="checkbox" name="${doc.slug}" class="_list-checkbox" `;
59 if (options.checked) { label += "checked"; }84 if (options.checked) {
85 label += "checked";
86 }
60 return label + `><span class="_list-text">${doc.fullName}</span></label>`;87 return label + `><span class="_list-text">${doc.fullName}</span></label>`;
61};88};
62 89
63templates.sidebarVersionedDoc = function(doc, versions, options) {90templates.sidebarVersionedDoc = function (doc, versions, options) {
64 if (options == null) { options = {}; }91 if (options == null) {
92 options = {};
93 }
65 let html = `<div class="_list-item _list-dir _list-rdir _icon-${doc.icon}`;94 let html = `<div class="_list-item _list-dir _list-rdir _icon-${doc.icon}`;
66 if (options.open) { html += " open"; }95 if (options.open) {
67 return html + `" tabindex="0">${arrow}${doc.name}</div><div class="_list _list-sub">${versions}</div>`;96 html += " open";
97 }
98 return (
99 html +
100 `" tabindex="0">${arrow}${doc.name}</div><div class="_list _list-sub">${versions}</div>`
101 );
68};102};
69 103
70templates.sidebarDisabled = options => `<h6 class="_list-title">${arrow}Disabled (${options.count}) <a href="/settings" class="_list-title-link" tabindex="-1">Customize</a></h6>`;104templates.sidebarDisabled = (options) =>
105 `<h6 class="_list-title">${arrow}Disabled (${options.count}) <a href="/settings" class="_list-title-link" tabindex="-1">Customize</a></h6>`;
71 106
72templates.sidebarDisabledList = html => `<div class="_disabled-list">${html}</div>`;107templates.sidebarDisabledList = (html) =>
108 `<div class="_disabled-list">${html}</div>`;
73 109
74templates.sidebarDisabledVersionedDoc = (doc, versions) => `<a class="_list-item _list-dir _icon-${doc.icon} _list-disabled" data-slug="${doc.slug_without_version}" tabindex="-1">${arrow}${doc.name}</a><div class="_list _list-sub">${versions}</div>`;110templates.sidebarDisabledVersionedDoc = (doc, versions) =>
111 `<a class="_list-item _list-dir _icon-${doc.icon} _list-disabled" data-slug="${doc.slug_without_version}" tabindex="-1">${arrow}${doc.name}</a><div class="_list _list-sub">${versions}</div>`;
75 112
76templates.docPickerHeader = "<div class=\"_list-picker-head\"><span>Documentation</span> <span>Enable</span></div>";113templates.docPickerHeader =
114 '<div class="_list-picker-head"><span>Documentation</span> <span>Enable</span></div>';
77 115
78templates.docPickerNote = `\116templates.docPickerNote = `\
79<div class="_list-note">Tip: for faster and better search results, select only the docs you need.</div>117<div class="_list-note">Tip: for faster and better search results, select only the docs you need.</div>
assets/javascripts/templates/tip_tmpl.js+8−2
@@ -10,8 +10,14 @@ app.templates.tipKeyNav = () => `\ · 9 unmodified lines
10 <strong>ProTip</strong>10 <strong>ProTip</strong>
11 <span class="_notif-info">(click to dismiss)</span>11 <span class="_notif-info">(click to dismiss)</span>
12<p class="_notif-text">12<p class="_notif-text">
13 Hit ${app.settings.get('arrowScroll') ? '<code class="_label">shift</code> +' : ''} <code class="_label">&darr;</code> <code class="_label">&uarr;</code> <code class="_label">&larr;</code> <code class="_label">&rarr;</code> to navigate the sidebar.<br>13 Hit ${
14 Hit <code class="_label">space / shift space</code>${app.settings.get('arrowScroll') ? ' or <code class="_label">&darr;/&uarr;</code>' : ', <code class="_label">alt &darr;/&uarr;</code> or <code class="_label">shift &darr;/&uarr;</code>'} to scroll the page.14 app.settings.get("arrowScroll") ? '<code class="_label">shift</code> +' : ""
15 } <code class="_label">&darr;</code> <code class="_label">&uarr;</code> <code class="_label">&larr;</code> <code class="_label">&rarr;</code> to navigate the sidebar.<br>
16 Hit <code class="_label">space / shift space</code>${
17 app.settings.get("arrowScroll")
18 ? ' or <code class="_label">&darr;/&uarr;</code>'
19 : ', <code class="_label">alt &darr;/&uarr;</code> or <code class="_label">shift &darr;/&uarr;</code>'
20 } to scroll the page.
15<p class="_notif-text">21<p class="_notif-text">
16 <a href="/help#shortcuts" class="_notif-link">See all keyboard shortcuts</a>\22 <a href="/help#shortcuts" class="_notif-link">See all keyboard shortcuts</a>\
17`;23`;
assets/javascripts/tracking.js+43−20
@@ -1,32 +1,55 @@
1try {1try {
2 if (app.config.env === 'production') {2 if (app.config.env === "production") {
3 if (Cookies.get('analyticsConsent') === '1') {3 if (Cookies.get("analyticsConsent") === "1") {
4 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){4 (function (i, s, o, g, r, a, m) {
5 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),5 i["GoogleAnalyticsObject"] = r;
6 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)6 (i[r] =
7 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');7 i[r] ||
8 ga('create', 'UA-5544833-12', 'devdocs.io');8 function () {
9 page.track(function() {9 (i[r].q = i[r].q || []).push(arguments);
10 ga('send', 'pageview', {10 }),
11 (i[r].l = 1 * new Date());
12 (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
13 a.async = 1;
14 a.src = g;
15 m.parentNode.insertBefore(a, m);
16 })(
17 window,
18 document,
19 "script",
20 "https://www.google-analytics.com/analytics.js",
21 "ga",
22 );
23 ga("create", "UA-5544833-12", "devdocs.io");
24 page.track(function () {
25 ga("send", "pageview", {
11 page: location.pathname + location.search + location.hash,26 page: location.pathname + location.search + location.hash,
12 dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version27 dimension1:
28 app.router.context &&
29 app.router.context.doc &&
30 app.router.context.doc.slug_without_version,
13 });31 });
14 });32 });
15 33
16 page.track(function() {34 page.track(function () {
17 if (window._gauges)35 if (window._gauges) _gauges.push(["track"]);
18 _gauges.push(['track']);
19 else36 else
20 (function() {37 (function () {
21 var _gauges=_gauges||[];!function(){var a=document.createElement("script");38 var _gauges = _gauges || [];
22 a.type="text/javascript",a.async=!0,a.id="gauges-tracker",39 !(function () {
23 a.setAttribute("data-site-id","51c15f82613f5d7819000067"),40 var a = document.createElement("script");
24 a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];41 (a.type = "text/javascript"),
25 b.parentNode.insertBefore(a,b)}();42 (a.async = !0),
43 (a.id = "gauges-tracker"),
44 a.setAttribute("data-site-id", "51c15f82613f5d7819000067"),
45 (a.src = "https://secure.gaug.es/track.js");
46 var b = document.getElementsByTagName("script")[0];
47 b.parentNode.insertBefore(a, b);
48 })();
26 })();49 })();
27 });50 });
28 } else {51 } else {
29 resetAnalytics();52 resetAnalytics();
30 }53 }
31 }54 }
32} catch(e) { }55} catch (e) {}
assets/javascripts/vendor/cookies.js+191−158
@@ -5,171 +5,204 @@ · 4 unmodified lines
5 * This is free and unencumbered software released into the public domain.5 * This is free and unencumbered software released into the public domain.
6 */6 */
7(function (global, undefined) {7(function (global, undefined) {
8 'use strict';8 "use strict";
9 9
10 var factory = function (window) {10 var factory = function (window) {
11 if (typeof window.document !== 'object') {11 if (typeof window.document !== "object") {
12 throw new Error('Cookies.js requires a `window` with a `document` object');12 throw new Error(
13 "Cookies.js requires a `window` with a `document` object",
14 );
15 }
16
17 var Cookies = function (key, value, options) {
18 return arguments.length === 1
19 ? Cookies.get(key)
20 : Cookies.set(key, value, options);
21 };
22
23 // Allows for setter injection in unit tests
24 Cookies._document = window.document;
25
26 // Used to ensure cookie keys do not collide with
27 // built-in `Object` properties
28 Cookies._cacheKeyPrefix = "cookey."; // Hurr hurr, :)
29
30 Cookies._maxExpireDate = new Date("Fri, 31 Dec 9999 23:59:59 UTC");
31
32 Cookies.defaults = {
33 path: "/",
34 SameSite: "Strict",
35 secure: true,
36 };
37
38 Cookies.get = function (key) {
39 if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) {
40 Cookies._renewCache();
41 }
42
43 var value = Cookies._cache[Cookies._cacheKeyPrefix + key];
44
45 return value === undefined ? undefined : decodeURIComponent(value);
46 };
47
48 Cookies.set = function (key, value, options) {
49 options = Cookies._getExtendedOptions(options);
50 options.expires = Cookies._getExpiresDate(
51 value === undefined ? -1 : options.expires,
52 );
53
54 Cookies._document.cookie = Cookies._generateCookieString(
55 key,
56 value,
57 options,
58 );
59
60 return Cookies;
61 };
62
63 Cookies.expire = function (key, options) {
64 return Cookies.set(key, undefined, options);
65 };
66
67 Cookies._getExtendedOptions = function (options) {
68 return {
69 path: (options && options.path) || Cookies.defaults.path,
70 domain: (options && options.domain) || Cookies.defaults.domain,
71 SameSite: (options && options.SameSite) || Cookies.defaults.SameSite,
72 expires: (options && options.expires) || Cookies.defaults.expires,
73 secure:
74 options && options.secure !== undefined
75 ? options.secure
76 : Cookies.defaults.secure,
77 };
78 };
79
80 Cookies._isValidDate = function (date) {
81 return (
82 Object.prototype.toString.call(date) === "[object Date]" &&
83 !isNaN(date.getTime())
84 );
85 };
86
87 Cookies._getExpiresDate = function (expires, now) {
88 now = now || new Date();
89
90 if (typeof expires === "number") {
91 expires =
92 expires === Infinity
93 ? Cookies._maxExpireDate
94 : new Date(now.getTime() + expires * 1000);
95 } else if (typeof expires === "string") {
96 expires = new Date(expires);
97 }
98
99 if (expires && !Cookies._isValidDate(expires)) {
100 throw new Error(
101 "`expires` parameter cannot be converted to a valid Date instance",
102 );
103 }
104
105 return expires;
106 };
107
108 Cookies._generateCookieString = function (key, value, options) {
109 key = key.replace(/[^#$&+\^`|]/g, encodeURIComponent);
110 key = key.replace(/\(/g, "%28").replace(/\)/g, "%29");
111 value = (value + "").replace(
112 /[^!#$&-+\--:<-\[\]-~]/g,
113 encodeURIComponent,
114 );
115 options = options || {};
116
117 var cookieString = key + "=" + value;
118 cookieString += options.path ? ";path=" + options.path : "";
119 cookieString += options.domain ? ";domain=" + options.domain : "";
120 cookieString += options.SameSite ? ";SameSite=" + options.SameSite : "";
121 cookieString += options.expires
122 ? ";expires=" + options.expires.toUTCString()
123 : "";
124 cookieString += options.secure ? ";secure" : "";
125
126 return cookieString;
127 };
128
129 Cookies._getCacheFromString = function (documentCookie) {
130 var cookieCache = {};
131 var cookiesArray = documentCookie ? documentCookie.split("; ") : [];
132
133 for (var i = 0; i < cookiesArray.length; i++) {
134 var cookieKvp = Cookies._getKeyValuePairFromCookieString(
135 cookiesArray[i],
136 );
137
138 if (
139 cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] === undefined
140 ) {
141 cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] =
142 cookieKvp.value;
13 }143 }
144 }
14 145
15 var Cookies = function (key, value, options) {146 return cookieCache;
16 return arguments.length === 1 ?147 };
17 Cookies.get(key) : Cookies.set(key, value, options);
18 };
19 148
20 // Allows for setter injection in unit tests149 Cookies._getKeyValuePairFromCookieString = function (cookieString) {
21 Cookies._document = window.document;150 // "=" is a valid character in a cookie value according to RFC6265, so cannot `split('=')`
22 151 var separatorIndex = cookieString.indexOf("=");
23 // Used to ensure cookie keys do not collide with152
24 // built-in `Object` properties153 // IE omits the "=" when the cookie value is an empty string
25 Cookies._cacheKeyPrefix = 'cookey.'; // Hurr hurr, :)154 separatorIndex =
155 separatorIndex < 0 ? cookieString.length : separatorIndex;
156
157 var key = cookieString.substr(0, separatorIndex);
158 var decodedKey;
159 try {
160 decodedKey = decodeURIComponent(key);
161 } catch (e) {
162 if (console && typeof console.error === "function") {
163 console.error('Could not decode cookie with key "' + key + '"', e);
164 }
165 }
26 166
27 Cookies._maxExpireDate = new Date('Fri, 31 Dec 9999 23:59:59 UTC');167 return {
168 key: decodedKey,
169 value: cookieString.substr(separatorIndex + 1), // Defer decoding value until accessed
170 };
171 };
28 172
29 Cookies.defaults = {173 Cookies._renewCache = function () {
30 path: '/',174 Cookies._cache = Cookies._getCacheFromString(Cookies._document.cookie);
31 SameSite: 'Strict',175 Cookies._cachedDocumentCookie = Cookies._document.cookie;
32 secure: true
33 };
34
35 Cookies.get = function (key) {
36 if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) {
37 Cookies._renewCache();
38 }
39
40 var value = Cookies._cache[Cookies._cacheKeyPrefix + key];
41
42 return value === undefined ? undefined : decodeURIComponent(value);
43 };
44
45 Cookies.set = function (key, value, options) {
46 options = Cookies._getExtendedOptions(options);
47 options.expires = Cookies._getExpiresDate(value === undefined ? -1 : options.expires);
48
49 Cookies._document.cookie = Cookies._generateCookieString(key, value, options);
50
51 return Cookies;
52 };
53
54 Cookies.expire = function (key, options) {
55 return Cookies.set(key, undefined, options);
56 };
57
58 Cookies._getExtendedOptions = function (options) {
59 return {
60 path: options && options.path || Cookies.defaults.path,
61 domain: options && options.domain || Cookies.defaults.domain,
62 SameSite: options && options.SameSite || Cookies.defaults.SameSite,
63 expires: options && options.expires || Cookies.defaults.expires,
64 secure: options && options.secure !== undefined ? options.secure : Cookies.defaults.secure
65 };
66 };
67
68 Cookies._isValidDate = function (date) {
69 return Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
70 };
71
72 Cookies._getExpiresDate = function (expires, now) {
73 now = now || new Date();
74
75 if (typeof expires === 'number') {
76 expires = expires === Infinity ?
77 Cookies._maxExpireDate : new Date(now.getTime() + expires * 1000);
78 } else if (typeof expires === 'string') {
79 expires = new Date(expires);
80 }
81
82 if (expires && !Cookies._isValidDate(expires)) {
83 throw new Error('`expires` parameter cannot be converted to a valid Date instance');
84 }
85
86 return expires;
87 };
88
89 Cookies._generateCookieString = function (key, value, options) {
90 key = key.replace(/[^#$&+\^`|]/g, encodeURIComponent);
91 key = key.replace(/\(/g, '%28').replace(/\)/g, '%29');
92 value = (value + '').replace(/[^!#$&-+\--:<-\[\]-~]/g, encodeURIComponent);
93 options = options || {};
94
95 var cookieString = key + '=' + value;
96 cookieString += options.path ? ';path=' + options.path : '';
97 cookieString += options.domain ? ';domain=' + options.domain : '';
98 cookieString += options.SameSite ? ';SameSite=' + options.SameSite : '';
99 cookieString += options.expires ? ';expires=' + options.expires.toUTCString() : '';
100 cookieString += options.secure ? ';secure' : '';
101
102 return cookieString;
103 };
104
105 Cookies._getCacheFromString = function (documentCookie) {
106 var cookieCache = {};
107 var cookiesArray = documentCookie ? documentCookie.split('; ') : [];
108
109 for (var i = 0; i < cookiesArray.length; i++) {
110 var cookieKvp = Cookies._getKeyValuePairFromCookieString(cookiesArray[i]);
111
112 if (cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] === undefined) {
113 cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] = cookieKvp.value;
114 }
115 }
116
117 return cookieCache;
118 };
119
120 Cookies._getKeyValuePairFromCookieString = function (cookieString) {
121 // "=" is a valid character in a cookie value according to RFC6265, so cannot `split('=')`
122 var separatorIndex = cookieString.indexOf('=');
123
124 // IE omits the "=" when the cookie value is an empty string
125 separatorIndex = separatorIndex < 0 ? cookieString.length : separatorIndex;
126
127 var key = cookieString.substr(0, separatorIndex);
128 var decodedKey;
129 try {
130 decodedKey = decodeURIComponent(key);
131 } catch (e) {
132 if (console && typeof console.error === 'function') {
133 console.error('Could not decode cookie with key "' + key + '"', e);
134 }
135 }
136
137 return {
138 key: decodedKey,
139 value: cookieString.substr(separatorIndex + 1) // Defer decoding value until accessed
140 };
141 };
142
143 Cookies._renewCache = function () {
144 Cookies._cache = Cookies._getCacheFromString(Cookies._document.cookie);
145 Cookies._cachedDocumentCookie = Cookies._document.cookie;
146 };
147
148 Cookies._areEnabled = function () {
149 var testKey = 'cookies.js';
150 var areEnabled = Cookies.set(testKey, 1).get(testKey) === '1';
151 Cookies.expire(testKey);
152 return areEnabled;
153 };
154
155 Cookies.enabled = Cookies._areEnabled();
156
157 return Cookies;
158 };176 };
159 var cookiesExport = (global && typeof global.document === 'object') ? factory(global) : factory;
160 177
161 // AMD support178 Cookies._areEnabled = function () {
162 if (typeof define === 'function' && define.amd) {179 var testKey = "cookies.js";
163 define(function () { return cookiesExport; });180 var areEnabled = Cookies.set(testKey, 1).get(testKey) === "1";
181 Cookies.expire(testKey);
182 return areEnabled;
183 };
184
185 Cookies.enabled = Cookies._areEnabled();
186
187 return Cookies;
188 };
189 var cookiesExport =
190 global && typeof global.document === "object" ? factory(global) : factory;
191
192 // AMD support
193 if (typeof define === "function" && define.amd) {
194 define(function () {
195 return cookiesExport;
196 });
164 // CommonJS/Node.js support197 // CommonJS/Node.js support
165 } else if (typeof exports === 'object') {198 } else if (typeof exports === "object") {
166 // Support Node.js specific `module.exports` (which can be a function)199 // Support Node.js specific `module.exports` (which can be a function)
167 if (typeof module === 'object' && typeof module.exports === 'object') {200 if (typeof module === "object" && typeof module.exports === "object") {
168 exports = module.exports = cookiesExport;201 exports = module.exports = cookiesExport;
169 }
170 // But always support CommonJS module 1.1.1 spec (`exports` cannot be a function)
171 exports.Cookies = cookiesExport;
172 } else {
173 global.Cookies = cookiesExport;
174 }202 }
175})(typeof window === 'undefined' ? this : window);203 // But always support CommonJS module 1.1.1 spec (`exports` cannot be a function)
204 exports.Cookies = cookiesExport;
205 } else {
206 global.Cookies = cookiesExport;
207 }
208})(typeof window === "undefined" ? this : window);
assets/javascripts/vendor/mathml.js+9−4
@@ -4,17 +4,22 @@ · 3 unmodified lines
4 * Adapted from: https://github.com/fred-wang/mathml.css */4 * Adapted from: https://github.com/fred-wang/mathml.css */
5 5
6(function () {6(function () {
7 window.addEventListener("load", function() {7 window.addEventListener("load", function () {
8 var box, div, link, namespaceURI;8 var box, div, link, namespaceURI;
9 // First check whether the page contains any <math> element.9 // First check whether the page contains any <math> element.
10 namespaceURI = "http://www.w3.org/1998/Math/MathML";10 namespaceURI = "http://www.w3.org/1998/Math/MathML";
11 // Create a div to test mspace, using Kuma's "offscreen" CSS11 // Create a div to test mspace, using Kuma's "offscreen" CSS
12 document.body.insertAdjacentHTML("afterbegin", "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" + namespaceURI + "'><mspace height='23px' width='77px'></mspace></math></div>");12 document.body.insertAdjacentHTML(
13 "afterbegin",
14 "<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='" +
15 namespaceURI +
16 "'><mspace height='23px' width='77px'></mspace></math></div>",
17 );
13 div = document.body.firstChild;18 div = document.body.firstChild;
14 box = div.firstChild.firstChild.getBoundingClientRect();19 box = div.firstChild.firstChild.getBoundingClientRect();
15 document.body.removeChild(div);20 document.body.removeChild(div);
16 if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {21 if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {
17 window.supportsMathML = false;22 window.supportsMathML = false;
18 }23 }
19 });24 });
20}());25})();
assets/javascripts/vendor/prism.js+5777−5324
@@ -2,13 +2,13 @@ · 1 unmodified line
2https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+c+cpp+cmake+coffeescript+crystal+d+dart+diff+django+elixir+erlang+go+groovy+java+json+julia+kotlin+latex+lua+markdown+markup-templating+matlab+nginx+nim+nix+ocaml+perl+php+python+qml+r+jsx+ruby+rust+scss+scala+shell-session+sql+typescript+yaml+zig */2https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+c+cpp+cmake+coffeescript+crystal+d+dart+diff+django+elixir+erlang+go+groovy+java+json+julia+kotlin+latex+lua+markdown+markup-templating+matlab+nginx+nim+nix+ocaml+perl+php+python+qml+r+jsx+ruby+rust+scss+scala+shell-session+sql+typescript+yaml+zig */
3/// <reference lib="WebWorker"/>3/// <reference lib="WebWorker"/>
4 4
5var _self = (typeof window !== 'undefined')5var _self =
6 ? window // if in browser6 typeof window !== "undefined"
7 : (7 ? window // if in browser
8 (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)8 : typeof WorkerGlobalScope !== "undefined" &&
9 ? self // if in worker9 self instanceof WorkerGlobalScope
10 : {} // if in node js10 ? self // if in worker
11 );11 : {}; // if in node js
12 12
13/**13/**
14 * Prism: Lightweight, robust, elegant syntax highlighting14 * Prism: Lightweight, robust, elegant syntax highlighting
@@ -19,1200 +19,1260 @@ var _self = (typeof window !== 'undefined') · 4 unmodified lines
19 * @public19 * @public
20 */20 */
21var Prism = (function (_self) {21var Prism = (function (_self) {
22 22 // Private helper vars
23 // Private helper vars23 var lang = /(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i;
24 var lang = /(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i;24 var uniqueId = 0;
25 var uniqueId = 0;25
26 26 // The grammar object for plaintext
27 // The grammar object for plaintext27 var plainTextGrammar = {};
28 var plainTextGrammar = {};28
29 29 var _ = {
30 30 /**
31 var _ = {31 * By default, Prism will attempt to highlight all code elements (by calling {@link Prism.highlightAll}) on the
32 /**32 * current page after the page finished loading. This might be a problem if e.g. you wanted to asynchronously load
33 * By default, Prism will attempt to highlight all code elements (by calling {@link Prism.highlightAll}) on the33 * additional languages or plugins yourself.
34 * current page after the page finished loading. This might be a problem if e.g. you wanted to asynchronously load34 *
35 * additional languages or plugins yourself.35 * By setting this value to `true`, Prism will not automatically highlight all code elements on the page.
36 *36 *
37 * By setting this value to `true`, Prism will not automatically highlight all code elements on the page.37 * You obviously have to change this value before the automatic highlighting started. To do this, you can add an
38 *38 * empty Prism object into the global scope before loading the Prism script like this:
39 * You obviously have to change this value before the automatic highlighting started. To do this, you can add an39 *
40 * empty Prism object into the global scope before loading the Prism script like this:40 * ```js
41 *41 * window.Prism = window.Prism || {};
42 * ```js42 * Prism.manual = true;
43 * window.Prism = window.Prism || {};43 * // add a new <script> to load Prism's script
44 * Prism.manual = true;44 * ```
45 * // add a new <script> to load Prism's script45 *
46 * ```46 * @default false
47 *47 * @type {boolean}
48 * @default false48 * @memberof Prism
49 * @type {boolean}49 * @public
50 * @memberof Prism50 */
51 * @public51 manual: _self.Prism && _self.Prism.manual,
52 */52 /**
53 manual: _self.Prism && _self.Prism.manual,53 * By default, if Prism is in a web worker, it assumes that it is in a worker it created itself, so it uses
54 /**54 * `addEventListener` to communicate with its parent instance. However, if you're using Prism manually in your
55 * By default, if Prism is in a web worker, it assumes that it is in a worker it created itself, so it uses55 * own worker, you don't want it to do this.
56 * `addEventListener` to communicate with its parent instance. However, if you're using Prism manually in your56 *
57 * own worker, you don't want it to do this.57 * By setting this value to `true`, Prism will not add its own listeners to the worker.
58 *58 *
59 * By setting this value to `true`, Prism will not add its own listeners to the worker.59 * You obviously have to change this value before Prism executes. To do this, you can add an
60 *60 * empty Prism object into the global scope before loading the Prism script like this:
61 * You obviously have to change this value before Prism executes. To do this, you can add an61 *
62 * empty Prism object into the global scope before loading the Prism script like this:62 * ```js
63 *63 * window.Prism = window.Prism || {};
64 * ```js64 * Prism.disableWorkerMessageHandler = true;
65 * window.Prism = window.Prism || {};65 * // Load Prism's script
66 * Prism.disableWorkerMessageHandler = true;66 * ```
67 * // Load Prism's script67 *
68 * ```68 * @default false
69 *69 * @type {boolean}
70 * @default false70 * @memberof Prism
71 * @type {boolean}71 * @public
72 * @memberof Prism72 */
73 * @public73 disableWorkerMessageHandler:
74 */74 _self.Prism && _self.Prism.disableWorkerMessageHandler,
75 disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,75
76 76 /**
77 /**77 * A namespace for utility methods.
78 * A namespace for utility methods.78 *
79 *79 * All function in this namespace that are not explicitly marked as _public_ are for __internal use only__ and may
80 * All function in this namespace that are not explicitly marked as _public_ are for __internal use only__ and may80 * change or disappear at any time.
81 * change or disappear at any time.81 *
82 *82 * @namespace
83 * @namespace83 * @memberof Prism
84 * @memberof Prism84 */
85 */85 util: {
86 util: {86 encode: function encode(tokens) {
87 encode: function encode(tokens) {87 if (tokens instanceof Token) {
88 if (tokens instanceof Token) {88 return new Token(tokens.type, encode(tokens.content), tokens.alias);
89 return new Token(tokens.type, encode(tokens.content), tokens.alias);89 } else if (Array.isArray(tokens)) {
90 } else if (Array.isArray(tokens)) {90 return tokens.map(encode);
91 return tokens.map(encode);91 } else {
92 } else {92 return tokens
93 return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');93 .replace(/&/g, "&amp;")
94 }94 .replace(/</g, "&lt;")
95 },95 .replace(/\u00a0/g, " ");
96 96 }
97 /**97 },
98 * Returns the name of the type of the given value.98
99 *99 /**
100 * @param {any} o100 * Returns the name of the type of the given value.
101 * @returns {string}101 *
102 * @example102 * @param {any} o
103 * type(null) === 'Null'103 * @returns {string}
104 * type(undefined) === 'Undefined'104 * @example
105 * type(123) === 'Number'105 * type(null) === 'Null'
106 * type('foo') === 'String'106 * type(undefined) === 'Undefined'
107 * type(true) === 'Boolean'107 * type(123) === 'Number'
108 * type([1, 2]) === 'Array'108 * type('foo') === 'String'
109 * type({}) === 'Object'109 * type(true) === 'Boolean'
110 * type(String) === 'Function'110 * type([1, 2]) === 'Array'
111 * type(/abc+/) === 'RegExp'111 * type({}) === 'Object'
112 */112 * type(String) === 'Function'
113 type: function (o) {113 * type(/abc+/) === 'RegExp'
114 return Object.prototype.toString.call(o).slice(8, -1);114 */
115 },115 type: function (o) {
116 116 return Object.prototype.toString.call(o).slice(8, -1);
117 /**117 },
118 * Returns a unique number for the given object. Later calls will still return the same number.118
119 *119 /**
120 * @param {Object} obj120 * Returns a unique number for the given object. Later calls will still return the same number.
121 * @returns {number}121 *
122 */122 * @param {Object} obj
123 objId: function (obj) {123 * @returns {number}
124 if (!obj['__id']) {124 */
125 Object.defineProperty(obj, '__id', { value: ++uniqueId });125 objId: function (obj) {
126 }126 if (!obj["__id"]) {
127 return obj['__id'];127 Object.defineProperty(obj, "__id", { value: ++uniqueId });
128 },128 }
129 129 return obj["__id"];
130 /**130 },
131 * Creates a deep clone of the given object.131
132 *132 /**
133 * The main intended use of this function is to clone language definitions.133 * Creates a deep clone of the given object.
134 *134 *
135 * @param {T} o135 * The main intended use of this function is to clone language definitions.
136 * @param {Record<number, any>} [visited]136 *
137 * @returns {T}137 * @param {T} o
138 * @template T138 * @param {Record<number, any>} [visited]
139 */139 * @returns {T}
140 clone: function deepClone(o, visited) {140 * @template T
141 visited = visited || {};141 */
142 142 clone: function deepClone(o, visited) {
143 var clone; var id;143 visited = visited || {};
144 switch (_.util.type(o)) {144
145 case 'Object':145 var clone;
146 id = _.util.objId(o);146 var id;
147 if (visited[id]) {147 switch (_.util.type(o)) {
148 return visited[id];148 case "Object":
149 }149 id = _.util.objId(o);
150 clone = /** @type {Record<string, any>} */ ({});150 if (visited[id]) {
151 visited[id] = clone;151 return visited[id];
152 152 }
153 for (var key in o) {153 clone = /** @type {Record<string, any>} */ ({});
154 if (o.hasOwnProperty(key)) {154 visited[id] = clone;
155 clone[key] = deepClone(o[key], visited);155
156 }156 for (var key in o) {
157 }157 if (o.hasOwnProperty(key)) {
158 158 clone[key] = deepClone(o[key], visited);
159 return /** @type {any} */ (clone);159 }
160 160 }
161 case 'Array':161
162 id = _.util.objId(o);162 return /** @type {any} */ (clone);
163 if (visited[id]) {163
164 return visited[id];164 case "Array":
165 }165 id = _.util.objId(o);
166 clone = [];166 if (visited[id]) {
167 visited[id] = clone;167 return visited[id];
168 168 }
169 (/** @type {Array} */(/** @type {any} */(o))).forEach(function (v, i) {169 clone = [];
170 clone[i] = deepClone(v, visited);170 visited[id] = clone;
171 });171
172 172 /** @type {Array} */ (/** @type {any} */ (o)).forEach(
173 return /** @type {any} */ (clone);173 function (v, i) {
174 174 clone[i] = deepClone(v, visited);
175 default:175 },
176 return o;176 );
177 }177
178 },178 return /** @type {any} */ (clone);
179 179
180 /**180 default:
181 * Returns the Prism language of the given element set by a `language-xxxx` or `lang-xxxx` class.181 return o;
182 *182 }
183 * If no language is set for the element or the element is `null` or `undefined`, `none` will be returned.183 },
184 *184
185 * @param {Element} element185 /**
186 * @returns {string}186 * Returns the Prism language of the given element set by a `language-xxxx` or `lang-xxxx` class.
187 */187 *
188 getLanguage: function (element) {188 * If no language is set for the element or the element is `null` or `undefined`, `none` will be returned.
189 while (element) {189 *
190 var m = lang.exec(element.className);190 * @param {Element} element
191 if (m) {191 * @returns {string}
192 return m[1].toLowerCase();192 */
193 }193 getLanguage: function (element) {
194 element = element.parentElement;194 while (element) {
195 }195 var m = lang.exec(element.className);
196 return 'none';196 if (m) {
197 },197 return m[1].toLowerCase();
198 198 }
199 /**199 element = element.parentElement;
200 * Sets the Prism `language-xxxx` class of the given element.200 }
201 *201 return "none";
202 * @param {Element} element202 },
203 * @param {string} language203
204 * @returns {void}204 /**
205 */205 * Sets the Prism `language-xxxx` class of the given element.
206 setLanguage: function (element, language) {206 *
207 // remove all `language-xxxx` classes207 * @param {Element} element
208 // (this might leave behind a leading space)208 * @param {string} language
209 element.className = element.className.replace(RegExp(lang, 'gi'), '');209 * @returns {void}
210 210 */
211 // add the new `language-xxxx` class211 setLanguage: function (element, language) {
212 // (using `classList` will automatically clean up spaces for us)212 // remove all `language-xxxx` classes
213 element.classList.add('language-' + language);213 // (this might leave behind a leading space)
214 },214 element.className = element.className.replace(RegExp(lang, "gi"), "");
215 215
216 /**216 // add the new `language-xxxx` class
217 * Returns the script element that is currently executing.217 // (using `classList` will automatically clean up spaces for us)
218 *218 element.classList.add("language-" + language);
219 * This does __not__ work for line script element.219 },
220 *220
221 * @returns {HTMLScriptElement | null}221 /**
222 */222 * Returns the script element that is currently executing.
223 currentScript: function () {223 *
224 if (typeof document === 'undefined') {224 * This does __not__ work for line script element.
225 return null;225 *
226 }226 * @returns {HTMLScriptElement | null}
227 if ('currentScript' in document && 1 < 2 /* hack to trip TS' flow analysis */) {227 */
228 return /** @type {any} */ (document.currentScript);228 currentScript: function () {
229 }229 if (typeof document === "undefined") {
230 230 return null;
231 // IE11 workaround231 }
232 // we'll get the src of the current script by parsing IE11's error stack trace232 if (
233 // this will not work for inline scripts233 "currentScript" in document &&
234 234 1 < 2 /* hack to trip TS' flow analysis */
235 try {235 ) {
236 throw new Error();236 return /** @type {any} */ (document.currentScript);
237 } catch (err) {237 }
238 // Get file src url from stack. Specifically works with the format of stack traces in IE.238
239 // A stack will look like this:239 // IE11 workaround
240 //240 // we'll get the src of the current script by parsing IE11's error stack trace
241 // Error241 // this will not work for inline scripts
242 // at _.util.currentScript (http://localhost/components/prism-core.js:119:5)242
243 // at Global code (http://localhost/components/prism-core.js:606:1)243 try {
244 244 throw new Error();
245 var src = (/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(err.stack) || [])[1];245 } catch (err) {
246 if (src) {246 // Get file src url from stack. Specifically works with the format of stack traces in IE.
247 var scripts = document.getElementsByTagName('script');247 // A stack will look like this:
248 for (var i in scripts) {248 //
249 if (scripts[i].src == src) {249 // Error
250 return scripts[i];250 // at _.util.currentScript (http://localhost/components/prism-core.js:119:5)
251 }251 // at Global code (http://localhost/components/prism-core.js:606:1)
252 }252
253 }253 var src = (/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(err.stack) ||
254 return null;254 [])[1];
255 }255 if (src) {
256 },256 var scripts = document.getElementsByTagName("script");
257 257 for (var i in scripts) {
258 /**258 if (scripts[i].src == src) {
259 * Returns whether a given class is active for `element`.259 return scripts[i];
260 *260 }
261 * The class can be activated if `element` or one of its ancestors has the given class and it can be deactivated261 }
262 * if `element` or one of its ancestors has the negated version of the given class. The _negated version_ of the262 }
263 * given class is just the given class with a `no-` prefix.263 return null;
264 *264 }
265 * Whether the class is active is determined by the closest ancestor of `element` (where `element` itself is265 },
266 * closest ancestor) that has the given class or the negated version of it. If neither `element` nor any of its266
267 * ancestors have the given class or the negated version of it, then the default activation will be returned.267 /**
268 *268 * Returns whether a given class is active for `element`.
269 * In the paradoxical situation where the closest ancestor contains __both__ the given class and the negated269 *
270 * version of it, the class is considered active.270 * The class can be activated if `element` or one of its ancestors has the given class and it can be deactivated
271 *271 * if `element` or one of its ancestors has the negated version of the given class. The _negated version_ of the
272 * @param {Element} element272 * given class is just the given class with a `no-` prefix.
273 * @param {string} className273 *
274 * @param {boolean} [defaultActivation=false]274 * Whether the class is active is determined by the closest ancestor of `element` (where `element` itself is
275 * @returns {boolean}275 * closest ancestor) that has the given class or the negated version of it. If neither `element` nor any of its
276 */276 * ancestors have the given class or the negated version of it, then the default activation will be returned.
277 isActive: function (element, className, defaultActivation) {277 *
278 var no = 'no-' + className;278 * In the paradoxical situation where the closest ancestor contains __both__ the given class and the negated
279 279 * version of it, the class is considered active.
280 while (element) {280 *
281 var classList = element.classList;281 * @param {Element} element
282 if (classList.contains(className)) {282 * @param {string} className
283 return true;283 * @param {boolean} [defaultActivation=false]
284 }284 * @returns {boolean}
285 if (classList.contains(no)) {285 */
286 return false;286 isActive: function (element, className, defaultActivation) {
287 }287 var no = "no-" + className;
288 element = element.parentElement;288
289 }289 while (element) {
290 return !!defaultActivation;290 var classList = element.classList;
291 }291 if (classList.contains(className)) {
292 },292 return true;
293 293 }
294 /**294 if (classList.contains(no)) {
295 * This namespace contains all currently loaded languages and the some helper functions to create and modify languages.295 return false;
296 *296 }
297 * @namespace297 element = element.parentElement;
298 * @memberof Prism298 }
299 * @public299 return !!defaultActivation;
300 */300 },
301 languages: {301 },
302 /**302
303 * The grammar for plain, unformatted text.303 /**
304 */304 * This namespace contains all currently loaded languages and the some helper functions to create and modify languages.
305 plain: plainTextGrammar,305 *
306 plaintext: plainTextGrammar,306 * @namespace
307 text: plainTextGrammar,307 * @memberof Prism
308 txt: plainTextGrammar,308 * @public
309 309 */
310 /**310 languages: {
311 * Creates a deep copy of the language with the given id and appends the given tokens.311 /**
312 *312 * The grammar for plain, unformatted text.
313 * If a token in `redef` also appears in the copied language, then the existing token in the copied language313 */
314 * will be overwritten at its original position.314 plain: plainTextGrammar,
315 *315 plaintext: plainTextGrammar,
316 * ## Best practices316 text: plainTextGrammar,
317 *317 txt: plainTextGrammar,
318 * Since the position of overwriting tokens (token in `redef` that overwrite tokens in the copied language)318
319 * doesn't matter, they can technically be in any order. However, this can be confusing to others that trying to319 /**
320 * understand the language definition because, normally, the order of tokens matters in Prism grammars.320 * Creates a deep copy of the language with the given id and appends the given tokens.
321 *321 *
322 * Therefore, it is encouraged to order overwriting tokens according to the positions of the overwritten tokens.322 * If a token in `redef` also appears in the copied language, then the existing token in the copied language
323 * Furthermore, all non-overwriting tokens should be placed after the overwriting ones.323 * will be overwritten at its original position.
324 *324 *
325 * @param {string} id The id of the language to extend. This has to be a key in `Prism.languages`.325 * ## Best practices
326 * @param {Grammar} redef The new tokens to append.326 *
327 * @returns {Grammar} The new language created.327 * Since the position of overwriting tokens (token in `redef` that overwrite tokens in the copied language)
328 * @public328 * doesn't matter, they can technically be in any order. However, this can be confusing to others that trying to
329 * @example329 * understand the language definition because, normally, the order of tokens matters in Prism grammars.
330 * Prism.languages['css-with-colors'] = Prism.languages.extend('css', {330 *
331 * // Prism.languages.css already has a 'comment' token, so this token will overwrite CSS' 'comment' token331 * Therefore, it is encouraged to order overwriting tokens according to the positions of the overwritten tokens.
332 * // at its original position332 * Furthermore, all non-overwriting tokens should be placed after the overwriting ones.
333 * 'comment': { ... },333 *
334 * // CSS doesn't have a 'color' token, so this token will be appended334 * @param {string} id The id of the language to extend. This has to be a key in `Prism.languages`.
335 * 'color': /\b(?:red|green|blue)\b/335 * @param {Grammar} redef The new tokens to append.
336 * });336 * @returns {Grammar} The new language created.
337 */337 * @public
338 extend: function (id, redef) {338 * @example
339 var lang = _.util.clone(_.languages[id]);339 * Prism.languages['css-with-colors'] = Prism.languages.extend('css', {
340 340 * // Prism.languages.css already has a 'comment' token, so this token will overwrite CSS' 'comment' token
341 for (var key in redef) {341 * // at its original position
342 lang[key] = redef[key];342 * 'comment': { ... },
343 }343 * // CSS doesn't have a 'color' token, so this token will be appended
344 344 * 'color': /\b(?:red|green|blue)\b/
345 return lang;345 * });
346 },346 */
347 347 extend: function (id, redef) {
348 /**348 var lang = _.util.clone(_.languages[id]);
349 * Inserts tokens _before_ another token in a language definition or any other grammar.349
350 *350 for (var key in redef) {
351 * ## Usage351 lang[key] = redef[key];
352 *352 }
353 * This helper method makes it easy to modify existing languages. For example, the CSS language definition353
354 * not only defines CSS highlighting for CSS documents, but also needs to define highlighting for CSS embedded354 return lang;
355 * in HTML through `<style>` elements. To do this, it needs to modify `Prism.languages.markup` and add the355 },
356 * appropriate tokens. However, `Prism.languages.markup` is a regular JavaScript object literal, so if you do356
357 * this:357 /**
358 *358 * Inserts tokens _before_ another token in a language definition or any other grammar.
359 * ```js359 *
360 * Prism.languages.markup.style = {360 * ## Usage
361 * // token361 *
362 * };362 * This helper method makes it easy to modify existing languages. For example, the CSS language definition
363 * ```363 * not only defines CSS highlighting for CSS documents, but also needs to define highlighting for CSS embedded
364 *364 * in HTML through `<style>` elements. To do this, it needs to modify `Prism.languages.markup` and add the
365 * then the `style` token will be added (and processed) at the end. `insertBefore` allows you to insert tokens365 * appropriate tokens. However, `Prism.languages.markup` is a regular JavaScript object literal, so if you do
366 * before existing tokens. For the CSS example above, you would use it like this:366 * this:
367 *367 *
368 * ```js368 * ```js
369 * Prism.languages.insertBefore('markup', 'cdata', {369 * Prism.languages.markup.style = {
370 * 'style': {370 * // token
371 * // token371 * };
372 * }372 * ```
373 * });373 *
374 * ```374 * then the `style` token will be added (and processed) at the end. `insertBefore` allows you to insert tokens
375 *375 * before existing tokens. For the CSS example above, you would use it like this:
376 * ## Special cases376 *
377 *377 * ```js
378 * If the grammars of `inside` and `insert` have tokens with the same name, the tokens in `inside`'s grammar378 * Prism.languages.insertBefore('markup', 'cdata', {
379 * will be ignored.379 * 'style': {
380 *380 * // token
381 * This behavior can be used to insert tokens after `before`:381 * }
382 *382 * });
383 * ```js383 * ```
384 * Prism.languages.insertBefore('markup', 'comment', {384 *
385 * 'comment': Prism.languages.markup.comment,385 * ## Special cases
386 * // tokens after 'comment'386 *
387 * });387 * If the grammars of `inside` and `insert` have tokens with the same name, the tokens in `inside`'s grammar
388 * ```388 * will be ignored.
389 *389 *
390 * ## Limitations390 * This behavior can be used to insert tokens after `before`:
391 *391 *
392 * The main problem `insertBefore` has to solve is iteration order. Since ES2015, the iteration order for object392 * ```js
393 * properties is guaranteed to be the insertion order (except for integer keys) but some browsers behave393 * Prism.languages.insertBefore('markup', 'comment', {
394 * differently when keys are deleted and re-inserted. So `insertBefore` can't be implemented by temporarily394 * 'comment': Prism.languages.markup.comment,
395 * deleting properties which is necessary to insert at arbitrary positions.395 * // tokens after 'comment'
396 *396 * });
397 * To solve this problem, `insertBefore` doesn't actually insert the given tokens into the target object.397 * ```
398 * Instead, it will create a new object and replace all references to the target object with the new one. This398 *
399 * can be done without temporarily deleting properties, so the iteration order is well-defined.399 * ## Limitations
400 *400 *
401 * However, only references that can be reached from `Prism.languages` or `insert` will be replaced. I.e. if401 * The main problem `insertBefore` has to solve is iteration order. Since ES2015, the iteration order for object
402 * you hold the target object in a variable, then the value of the variable will not change.402 * properties is guaranteed to be the insertion order (except for integer keys) but some browsers behave
403 *403 * differently when keys are deleted and re-inserted. So `insertBefore` can't be implemented by temporarily
404 * ```js404 * deleting properties which is necessary to insert at arbitrary positions.
405 * var oldMarkup = Prism.languages.markup;405 *
406 * var newMarkup = Prism.languages.insertBefore('markup', 'comment', { ... });406 * To solve this problem, `insertBefore` doesn't actually insert the given tokens into the target object.
407 *407 * Instead, it will create a new object and replace all references to the target object with the new one. This
408 * assert(oldMarkup !== Prism.languages.markup);408 * can be done without temporarily deleting properties, so the iteration order is well-defined.
409 * assert(newMarkup === Prism.languages.markup);409 *
410 * ```410 * However, only references that can be reached from `Prism.languages` or `insert` will be replaced. I.e. if
411 *411 * you hold the target object in a variable, then the value of the variable will not change.
412 * @param {string} inside The property of `root` (e.g. a language id in `Prism.languages`) that contains the412 *
413 * object to be modified.413 * ```js
414 * @param {string} before The key to insert before.414 * var oldMarkup = Prism.languages.markup;
415 * @param {Grammar} insert An object containing the key-value pairs to be inserted.415 * var newMarkup = Prism.languages.insertBefore('markup', 'comment', { ... });
416 * @param {Object<string, any>} [root] The object containing `inside`, i.e. the object that contains the416 *
417 * object to be modified.417 * assert(oldMarkup !== Prism.languages.markup);
418 *418 * assert(newMarkup === Prism.languages.markup);
419 * Defaults to `Prism.languages`.419 * ```
420 * @returns {Grammar} The new grammar object.420 *
421 * @public421 * @param {string} inside The property of `root` (e.g. a language id in `Prism.languages`) that contains the
422 */422 * object to be modified.
423 insertBefore: function (inside, before, insert, root) {423 * @param {string} before The key to insert before.
424 root = root || /** @type {any} */ (_.languages);424 * @param {Grammar} insert An object containing the key-value pairs to be inserted.
425 var grammar = root[inside];425 * @param {Object<string, any>} [root] The object containing `inside`, i.e. the object that contains the
426 /** @type {Grammar} */426 * object to be modified.
427 var ret = {};427 *
428 428 * Defaults to `Prism.languages`.
429 for (var token in grammar) {429 * @returns {Grammar} The new grammar object.
430 if (grammar.hasOwnProperty(token)) {430 * @public
431 431 */
432 if (token == before) {432 insertBefore: function (inside, before, insert, root) {
433 for (var newToken in insert) {433 root = root || /** @type {any} */ (_.languages);
434 if (insert.hasOwnProperty(newToken)) {434 var grammar = root[inside];
435 ret[newToken] = insert[newToken];435 /** @type {Grammar} */
436 }436 var ret = {};
437 }437
438 }438 for (var token in grammar) {
439 439 if (grammar.hasOwnProperty(token)) {
440 // Do not insert token which also occur in insert. See #1525440 if (token == before) {
441 if (!insert.hasOwnProperty(token)) {441 for (var newToken in insert) {
442 ret[token] = grammar[token];442 if (insert.hasOwnProperty(newToken)) {
443 }443 ret[newToken] = insert[newToken];
444 }444 }
445 }445 }
446 446 }
447 var old = root[inside];447
448 root[inside] = ret;448 // Do not insert token which also occur in insert. See #1525
449 449 if (!insert.hasOwnProperty(token)) {
450 // Update references in other language definitions450 ret[token] = grammar[token];
451 _.languages.DFS(_.languages, function (key, value) {451 }
452 if (value === old && key != inside) {452 }
453 this[key] = ret;453 }
454 }454
455 });455 var old = root[inside];
456 456 root[inside] = ret;
457 return ret;457
458 },458 // Update references in other language definitions
459 459 _.languages.DFS(_.languages, function (key, value) {
460 // Traverse a language definition with Depth First Search460 if (value === old && key != inside) {
461 DFS: function DFS(o, callback, type, visited) {461 this[key] = ret;
462 visited = visited || {};462 }
463 463 });
464 var objId = _.util.objId;464
465 465 return ret;
466 for (var i in o) {466 },
467 if (o.hasOwnProperty(i)) {467
468 callback.call(o, i, o[i], type || i);468 // Traverse a language definition with Depth First Search
469 469 DFS: function DFS(o, callback, type, visited) {
470 var property = o[i];470 visited = visited || {};
471 var propertyType = _.util.type(property);471
472 472 var objId = _.util.objId;
473 if (propertyType === 'Object' && !visited[objId(property)]) {473
474 visited[objId(property)] = true;474 for (var i in o) {
475 DFS(property, callback, null, visited);475 if (o.hasOwnProperty(i)) {
476 } else if (propertyType === 'Array' && !visited[objId(property)]) {476 callback.call(o, i, o[i], type || i);
477 visited[objId(property)] = true;477
478 DFS(property, callback, i, visited);478 var property = o[i];
479 }479 var propertyType = _.util.type(property);
480 }480
481 }481 if (propertyType === "Object" && !visited[objId(property)]) {
482 }482 visited[objId(property)] = true;
483 },483 DFS(property, callback, null, visited);
484 484 } else if (propertyType === "Array" && !visited[objId(property)]) {
485 plugins: {},485 visited[objId(property)] = true;
486 486 DFS(property, callback, i, visited);
487 /**487 }
488 * This is the most high-level function in Prism’s API.488 }
489 * It fetches all the elements that have a `.language-xxxx` class and then calls {@link Prism.highlightElement} on489 }
490 * each one of them.490 },
491 *491 },
492 * This is equivalent to `Prism.highlightAllUnder(document, async, callback)`.492
493 *493 plugins: {},
494 * @param {boolean} [async=false] Same as in {@link Prism.highlightAllUnder}.494
495 * @param {HighlightCallback} [callback] Same as in {@link Prism.highlightAllUnder}.495 /**
496 * @memberof Prism496 * This is the most high-level function in Prism’s API.
497 * @public497 * It fetches all the elements that have a `.language-xxxx` class and then calls {@link Prism.highlightElement} on
498 */498 * each one of them.
499 highlightAll: function (async, callback) {499 *
500 _.highlightAllUnder(document, async, callback);500 * This is equivalent to `Prism.highlightAllUnder(document, async, callback)`.
501 },501 *
502 502 * @param {boolean} [async=false] Same as in {@link Prism.highlightAllUnder}.
503 /**503 * @param {HighlightCallback} [callback] Same as in {@link Prism.highlightAllUnder}.
504 * Fetches all the descendants of `container` that have a `.language-xxxx` class and then calls504 * @memberof Prism
505 * {@link Prism.highlightElement} on each one of them.505 * @public
506 *506 */
507 * The following hooks will be run:507 highlightAll: function (async, callback) {
508 * 1. `before-highlightall`508 _.highlightAllUnder(document, async, callback);
509 * 2. `before-all-elements-highlight`509 },
510 * 3. All hooks of {@link Prism.highlightElement} for each element.510
511 *511 /**
512 * @param {ParentNode} container The root element, whose descendants that have a `.language-xxxx` class will be highlighted.512 * Fetches all the descendants of `container` that have a `.language-xxxx` class and then calls
513 * @param {boolean} [async=false] Whether each element is to be highlighted asynchronously using Web Workers.513 * {@link Prism.highlightElement} on each one of them.
514 * @param {HighlightCallback} [callback] An optional callback to be invoked on each element after its highlighting is done.514 *
515 * @memberof Prism515 * The following hooks will be run:
516 * @public516 * 1. `before-highlightall`
517 */517 * 2. `before-all-elements-highlight`
518 highlightAllUnder: function (container, async, callback) {518 * 3. All hooks of {@link Prism.highlightElement} for each element.
519 var env = {519 *
520 callback: callback,520 * @param {ParentNode} container The root element, whose descendants that have a `.language-xxxx` class will be highlighted.
521 container: container,521 * @param {boolean} [async=false] Whether each element is to be highlighted asynchronously using Web Workers.
522 selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'522 * @param {HighlightCallback} [callback] An optional callback to be invoked on each element after its highlighting is done.
523 };523 * @memberof Prism
524 524 * @public
525 _.hooks.run('before-highlightall', env);525 */
526 526 highlightAllUnder: function (container, async, callback) {
527 env.elements = Array.prototype.slice.apply(env.container.querySelectorAll(env.selector));527 var env = {
528 528 callback: callback,
529 _.hooks.run('before-all-elements-highlight', env);529 container: container,
530 530 selector:
531 for (var i = 0, element; (element = env.elements[i++]);) {531 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code',
532 _.highlightElement(element, async === true, env.callback);532 };
533 }533
534 },534 _.hooks.run("before-highlightall", env);
535 535
536 /**536 env.elements = Array.prototype.slice.apply(
537 * Highlights the code inside a single element.537 env.container.querySelectorAll(env.selector),
538 *538 );
539 * The following hooks will be run:539
540 * 1. `before-sanity-check`540 _.hooks.run("before-all-elements-highlight", env);
541 * 2. `before-highlight`541
542 * 3. All hooks of {@link Prism.highlight}. These hooks will be run by an asynchronous worker if `async` is `true`.542 for (var i = 0, element; (element = env.elements[i++]); ) {
543 * 4. `before-insert`543 _.highlightElement(element, async === true, env.callback);
544 * 5. `after-highlight`544 }
545 * 6. `complete`545 },
546 *546
547 * Some the above hooks will be skipped if the element doesn't contain any text or there is no grammar loaded for547 /**
548 * the element's language.548 * Highlights the code inside a single element.
549 *549 *
550 * @param {Element} element The element containing the code.550 * The following hooks will be run:
551 * It must have a class of `language-xxxx` to be processed, where `xxxx` is a valid language identifier.551 * 1. `before-sanity-check`
552 * @param {boolean} [async=false] Whether the element is to be highlighted asynchronously using Web Workers552 * 2. `before-highlight`
553 * to improve performance and avoid blocking the UI when highlighting very large chunks of code. This option is553 * 3. All hooks of {@link Prism.highlight}. These hooks will be run by an asynchronous worker if `async` is `true`.
554 * [disabled by default](https://prismjs.com/faq.html#why-is-asynchronous-highlighting-disabled-by-default).554 * 4. `before-insert`
555 *555 * 5. `after-highlight`
556 * Note: All language definitions required to highlight the code must be included in the main `prism.js` file for556 * 6. `complete`
557 * asynchronous highlighting to work. You can build your own bundle on the557 *
558 * [Download page](https://prismjs.com/download.html).558 * Some the above hooks will be skipped if the element doesn't contain any text or there is no grammar loaded for
559 * @param {HighlightCallback} [callback] An optional callback to be invoked after the highlighting is done.559 * the element's language.
560 * Mostly useful when `async` is `true`, since in that case, the highlighting is done asynchronously.560 *
561 * @memberof Prism561 * @param {Element} element The element containing the code.
562 * @public562 * It must have a class of `language-xxxx` to be processed, where `xxxx` is a valid language identifier.
563 */563 * @param {boolean} [async=false] Whether the element is to be highlighted asynchronously using Web Workers
564 highlightElement: function (element, async, callback) {564 * to improve performance and avoid blocking the UI when highlighting very large chunks of code. This option is
565 // Find language565 * [disabled by default](https://prismjs.com/faq.html#why-is-asynchronous-highlighting-disabled-by-default).
566 var language = _.util.getLanguage(element);566 *
567 var grammar = _.languages[language];567 * Note: All language definitions required to highlight the code must be included in the main `prism.js` file for
568 568 * asynchronous highlighting to work. You can build your own bundle on the
569 // Set language on the element, if not present569 * [Download page](https://prismjs.com/download.html).
570 _.util.setLanguage(element, language);570 * @param {HighlightCallback} [callback] An optional callback to be invoked after the highlighting is done.
571 571 * Mostly useful when `async` is `true`, since in that case, the highlighting is done asynchronously.
572 // Set language on the parent, for styling572 * @memberof Prism
573 var parent = element.parentElement;573 * @public
574 if (parent && parent.nodeName.toLowerCase() === 'pre') {574 */
575 _.util.setLanguage(parent, language);575 highlightElement: function (element, async, callback) {
576 }576 // Find language
577 577 var language = _.util.getLanguage(element);
578 var code = element.textContent;578 var grammar = _.languages[language];
579 579
580 var env = {580 // Set language on the element, if not present
581 element: element,581 _.util.setLanguage(element, language);
582 language: language,582
583 grammar: grammar,583 // Set language on the parent, for styling
584 code: code584 var parent = element.parentElement;
585 };585 if (parent && parent.nodeName.toLowerCase() === "pre") {
586 586 _.util.setLanguage(parent, language);
587 function insertHighlightedCode(highlightedCode) {587 }
588 env.highlightedCode = highlightedCode;588
589 589 var code = element.textContent;
590 _.hooks.run('before-insert', env);590
591 591 var env = {
592 env.element.innerHTML = env.highlightedCode;592 element: element,
593 593 language: language,
594 _.hooks.run('after-highlight', env);594 grammar: grammar,
595 _.hooks.run('complete', env);595 code: code,
596 callback && callback.call(env.element);596 };
597 }597
598 598 function insertHighlightedCode(highlightedCode) {
599 _.hooks.run('before-sanity-check', env);599 env.highlightedCode = highlightedCode;
600 600
601 // plugins may change/add the parent/element601 _.hooks.run("before-insert", env);
602 parent = env.element.parentElement;602
603 if (parent && parent.nodeName.toLowerCase() === 'pre' && !parent.hasAttribute('tabindex')) {603 env.element.innerHTML = env.highlightedCode;
604 parent.setAttribute('tabindex', '0');604
605 }605 _.hooks.run("after-highlight", env);
606 606 _.hooks.run("complete", env);
607 if (!env.code) {607 callback && callback.call(env.element);
608 _.hooks.run('complete', env);608 }
609 callback && callback.call(env.element);609
610 return;610 _.hooks.run("before-sanity-check", env);
611 }611
612 612 // plugins may change/add the parent/element
613 _.hooks.run('before-highlight', env);613 parent = env.element.parentElement;
614 614 if (
615 if (!env.grammar) {615 parent &&
616 insertHighlightedCode(_.util.encode(env.code));616 parent.nodeName.toLowerCase() === "pre" &&
617 return;617 !parent.hasAttribute("tabindex")
618 }618 ) {
619 619 parent.setAttribute("tabindex", "0");
620 if (async && _self.Worker) {620 }
621 var worker = new Worker(_.filename);621
622 622 if (!env.code) {
623 worker.onmessage = function (evt) {623 _.hooks.run("complete", env);
624 insertHighlightedCode(evt.data);624 callback && callback.call(env.element);
625 };625 return;
626 626 }
627 worker.postMessage(JSON.stringify({627
628 language: env.language,628 _.hooks.run("before-highlight", env);
629 code: env.code,629
630 immediateClose: true630 if (!env.grammar) {
631 }));631 insertHighlightedCode(_.util.encode(env.code));
632 } else {632 return;
633 insertHighlightedCode(_.highlight(env.code, env.grammar, env.language));633 }
634 }634
635 },635 if (async && _self.Worker) {
636 636 var worker = new Worker(_.filename);
637 /**637
638 * Low-level function, only use if you know what you’re doing. It accepts a string of text as input638 worker.onmessage = function (evt) {
639 * and the language definitions to use, and returns a string with the HTML produced.639 insertHighlightedCode(evt.data);
640 *640 };
641 * The following hooks will be run:641
642 * 1. `before-tokenize`642 worker.postMessage(
643 * 2. `after-tokenize`643 JSON.stringify({
644 * 3. `wrap`: On each {@link Token}.644 language: env.language,
645 *645 code: env.code,
646 * @param {string} text A string with the code to be highlighted.646 immediateClose: true,
647 * @param {Grammar} grammar An object containing the tokens to use.647 }),
648 *648 );
649 * Usually a language definition like `Prism.languages.markup`.649 } else {
650 * @param {string} language The name of the language definition passed to `grammar`.650 insertHighlightedCode(_.highlight(env.code, env.grammar, env.language));
651 * @returns {string} The highlighted HTML.651 }
652 * @memberof Prism652 },
653 * @public653
654 * @example654 /**
655 * Prism.highlight('var foo = true;', Prism.languages.javascript, 'javascript');655 * Low-level function, only use if you know what you’re doing. It accepts a string of text as input
656 */656 * and the language definitions to use, and returns a string with the HTML produced.
657 highlight: function (text, grammar, language) {657 *
658 var env = {658 * The following hooks will be run:
659 code: text,659 * 1. `before-tokenize`
660 grammar: grammar,660 * 2. `after-tokenize`
661 language: language661 * 3. `wrap`: On each {@link Token}.
662 };662 *
663 _.hooks.run('before-tokenize', env);663 * @param {string} text A string with the code to be highlighted.
664 if (!env.grammar) {664 * @param {Grammar} grammar An object containing the tokens to use.
665 throw new Error('The language "' + env.language + '" has no grammar.');665 *
666 }666 * Usually a language definition like `Prism.languages.markup`.
667 env.tokens = _.tokenize(env.code, env.grammar);667 * @param {string} language The name of the language definition passed to `grammar`.
668 _.hooks.run('after-tokenize', env);668 * @returns {string} The highlighted HTML.
669 return Token.stringify(_.util.encode(env.tokens), env.language);669 * @memberof Prism
670 },670 * @public
671 671 * @example
672 /**672 * Prism.highlight('var foo = true;', Prism.languages.javascript, 'javascript');
673 * This is the heart of Prism, and the most low-level function you can use. It accepts a string of text as input673 */
674 * and the language definitions to use, and returns an array with the tokenized code.674 highlight: function (text, grammar, language) {
675 *675 var env = {
676 * When the language definition includes nested tokens, the function is called recursively on each of these tokens.676 code: text,
677 *677 grammar: grammar,
678 * This method could be useful in other contexts as well, as a very crude parser.678 language: language,
679 *679 };
680 * @param {string} text A string with the code to be highlighted.680 _.hooks.run("before-tokenize", env);
681 * @param {Grammar} grammar An object containing the tokens to use.681 if (!env.grammar) {
682 *682 throw new Error('The language "' + env.language + '" has no grammar.');
683 * Usually a language definition like `Prism.languages.markup`.683 }
684 * @returns {TokenStream} An array of strings and tokens, a token stream.684 env.tokens = _.tokenize(env.code, env.grammar);
685 * @memberof Prism685 _.hooks.run("after-tokenize", env);
686 * @public686 return Token.stringify(_.util.encode(env.tokens), env.language);
687 * @example687 },
688 * let code = `var foo = 0;`;688
689 * let tokens = Prism.tokenize(code, Prism.languages.javascript);689 /**
690 * tokens.forEach(token => {690 * This is the heart of Prism, and the most low-level function you can use. It accepts a string of text as input
691 * if (token instanceof Prism.Token && token.type === 'number') {691 * and the language definitions to use, and returns an array with the tokenized code.
692 * console.log(`Found numeric literal: ${token.content}`);692 *
693 * }693 * When the language definition includes nested tokens, the function is called recursively on each of these tokens.
694 * });694 *
695 */695 * This method could be useful in other contexts as well, as a very crude parser.
696 tokenize: function (text, grammar) {696 *
697 var rest = grammar.rest;697 * @param {string} text A string with the code to be highlighted.
698 if (rest) {698 * @param {Grammar} grammar An object containing the tokens to use.
699 for (var token in rest) {699 *
700 grammar[token] = rest[token];700 * Usually a language definition like `Prism.languages.markup`.
701 }701 * @returns {TokenStream} An array of strings and tokens, a token stream.
702 702 * @memberof Prism
703 delete grammar.rest;703 * @public
704 }704 * @example
705 705 * let code = `var foo = 0;`;
706 var tokenList = new LinkedList();706 * let tokens = Prism.tokenize(code, Prism.languages.javascript);
707 addAfter(tokenList, tokenList.head, text);707 * tokens.forEach(token => {
708 708 * if (token instanceof Prism.Token && token.type === 'number') {
709 matchGrammar(text, tokenList, grammar, tokenList.head, 0);709 * console.log(`Found numeric literal: ${token.content}`);
710 710 * }
711 return toArray(tokenList);711 * });
712 },712 */
713 713 tokenize: function (text, grammar) {
714 /**714 var rest = grammar.rest;
715 * @namespace715 if (rest) {
716 * @memberof Prism716 for (var token in rest) {
717 * @public717 grammar[token] = rest[token];
718 */718 }
719 hooks: {719
720 all: {},720 delete grammar.rest;
721 721 }
722 /**722
723 * Adds the given callback to the list of callbacks for the given hook.723 var tokenList = new LinkedList();
724 *724 addAfter(tokenList, tokenList.head, text);
725 * The callback will be invoked when the hook it is registered for is run.725
726 * Hooks are usually directly run by a highlight function but you can also run hooks yourself.726 matchGrammar(text, tokenList, grammar, tokenList.head, 0);
727 *727
728 * One callback function can be registered to multiple hooks and the same hook multiple times.728 return toArray(tokenList);
729 *729 },
730 * @param {string} name The name of the hook.730
731 * @param {HookCallback} callback The callback function which is given environment variables.731 /**
732 * @public732 * @namespace
733 */733 * @memberof Prism
734 add: function (name, callback) {734 * @public
735 var hooks = _.hooks.all;735 */
736 736 hooks: {
737 hooks[name] = hooks[name] || [];737 all: {},
738 738
739 hooks[name].push(callback);739 /**
740 },740 * Adds the given callback to the list of callbacks for the given hook.
741 741 *
742 /**742 * The callback will be invoked when the hook it is registered for is run.
743 * Runs a hook invoking all registered callbacks with the given environment variables.743 * Hooks are usually directly run by a highlight function but you can also run hooks yourself.
744 *744 *
745 * Callbacks will be invoked synchronously and in the order in which they were registered.745 * One callback function can be registered to multiple hooks and the same hook multiple times.
746 *746 *
747 * @param {string} name The name of the hook.747 * @param {string} name The name of the hook.
748 * @param {Object<string, any>} env The environment variables of the hook passed to all callbacks registered.748 * @param {HookCallback} callback The callback function which is given environment variables.
749 * @public749 * @public
750 */750 */
751 run: function (name, env) {751 add: function (name, callback) {
752 var callbacks = _.hooks.all[name];752 var hooks = _.hooks.all;
753 753
754 if (!callbacks || !callbacks.length) {754 hooks[name] = hooks[name] || [];
755 return;755
756 }756 hooks[name].push(callback);
757 757 },
758 for (var i = 0, callback; (callback = callbacks[i++]);) {758
759 callback(env);759 /**
760 }760 * Runs a hook invoking all registered callbacks with the given environment variables.
761 }761 *
762 },762 * Callbacks will be invoked synchronously and in the order in which they were registered.
763 763 *
764 Token: Token764 * @param {string} name The name of the hook.
765 };765 * @param {Object<string, any>} env The environment variables of the hook passed to all callbacks registered.
766 _self.Prism = _;766 * @public
767 767 */
768 768 run: function (name, env) {
769 // Typescript note:769 var callbacks = _.hooks.all[name];
770 // The following can be used to import the Token type in JSDoc:770
771 //771 if (!callbacks || !callbacks.length) {
772 // @typedef {InstanceType<import("./prism-core")["Token"]>} Token772 return;
773 773 }
774 /**774
775 * Creates a new token.775 for (var i = 0, callback; (callback = callbacks[i++]); ) {
776 *776 callback(env);
777 * @param {string} type See {@link Token#type type}777 }
778 * @param {string | TokenStream} content See {@link Token#content content}778 },
779 * @param {string|string[]} [alias] The alias(es) of the token.779 },
780 * @param {string} [matchedStr=""] A copy of the full string this token was created from.780
781 * @class781 Token: Token,
782 * @global782 };
783 * @public783 _self.Prism = _;
784 */784
785 function Token(type, content, alias, matchedStr) {785 // Typescript note:
786 /**786 // The following can be used to import the Token type in JSDoc:
787 * The type of the token.787 //
788 *788 // @typedef {InstanceType<import("./prism-core")["Token"]>} Token
789 * This is usually the key of a pattern in a {@link Grammar}.789
790 *790 /**
791 * @type {string}791 * Creates a new token.
792 * @see GrammarToken792 *
793 * @public793 * @param {string} type See {@link Token#type type}
794 */794 * @param {string | TokenStream} content See {@link Token#content content}
795 this.type = type;795 * @param {string|string[]} [alias] The alias(es) of the token.
796 /**796 * @param {string} [matchedStr=""] A copy of the full string this token was created from.
797 * The strings or tokens contained by this token.797 * @class
798 *798 * @global
799 * This will be a token stream if the pattern matched also defined an `inside` grammar.799 * @public
800 *800 */
801 * @type {string | TokenStream}801 function Token(type, content, alias, matchedStr) {
802 * @public802 /**
803 */803 * The type of the token.
804 this.content = content;804 *
805 /**805 * This is usually the key of a pattern in a {@link Grammar}.
806 * The alias(es) of the token.806 *
807 *807 * @type {string}
808 * @type {string|string[]}808 * @see GrammarToken
809 * @see GrammarToken809 * @public
810 * @public810 */
811 */811 this.type = type;
812 this.alias = alias;812 /**
813 // Copy of the full string this token was created from813 * The strings or tokens contained by this token.
814 this.length = (matchedStr || '').length | 0;814 *
815 }815 * This will be a token stream if the pattern matched also defined an `inside` grammar.
816 816 *
817 /**817 * @type {string | TokenStream}
818 * A token stream is an array of strings and {@link Token Token} objects.818 * @public
819 *819 */
820 * Token streams have to fulfill a few properties that are assumed by most functions (mostly internal ones) that process820 this.content = content;
821 * them.821 /**
822 *822 * The alias(es) of the token.
823 * 1. No adjacent strings.823 *
824 * 2. No empty strings.824 * @type {string|string[]}
825 *825 * @see GrammarToken
826 * The only exception here is the token stream that only contains the empty string and nothing else.826 * @public
827 *827 */
828 * @typedef {Array<string | Token>} TokenStream828 this.alias = alias;
829 * @global829 // Copy of the full string this token was created from
830 * @public830 this.length = (matchedStr || "").length | 0;
831 */831 }
832 832
833 /**833 /**
834 * Converts the given token or token stream to an HTML representation.834 * A token stream is an array of strings and {@link Token Token} objects.
835 *835 *
836 * The following hooks will be run:836 * Token streams have to fulfill a few properties that are assumed by most functions (mostly internal ones) that process
837 * 1. `wrap`: On each {@link Token}.837 * them.
838 *838 *
839 * @param {string | Token | TokenStream} o The token or token stream to be converted.839 * 1. No adjacent strings.
840 * @param {string} language The name of current language.840 * 2. No empty strings.
841 * @returns {string} The HTML representation of the token or token stream.841 *
842 * @memberof Token842 * The only exception here is the token stream that only contains the empty string and nothing else.
843 * @static843 *
844 */844 * @typedef {Array<string | Token>} TokenStream
845 Token.stringify = function stringify(o, language) {845 * @global
846 if (typeof o == 'string') {846 * @public
847 return o;847 */
848 }848
849 if (Array.isArray(o)) {849 /**
850 var s = '';850 * Converts the given token or token stream to an HTML representation.
851 o.forEach(function (e) {851 *
852 s += stringify(e, language);852 * The following hooks will be run:
853 });853 * 1. `wrap`: On each {@link Token}.
854 return s;854 *
855 }855 * @param {string | Token | TokenStream} o The token or token stream to be converted.
856 856 * @param {string} language The name of current language.
857 var env = {857 * @returns {string} The HTML representation of the token or token stream.
858 type: o.type,858 * @memberof Token
859 content: stringify(o.content, language),859 * @static
860 tag: 'span',860 */
861 classes: ['token', o.type],861 Token.stringify = function stringify(o, language) {
862 attributes: {},862 if (typeof o == "string") {
863 language: language863 return o;
864 };864 }
865 865 if (Array.isArray(o)) {
866 var aliases = o.alias;866 var s = "";
867 if (aliases) {867 o.forEach(function (e) {
868 if (Array.isArray(aliases)) {868 s += stringify(e, language);
869 Array.prototype.push.apply(env.classes, aliases);869 });
870 } else {870 return s;
871 env.classes.push(aliases);871 }
872 }872
873 }873 var env = {
874 874 type: o.type,
875 _.hooks.run('wrap', env);875 content: stringify(o.content, language),
876 876 tag: "span",
877 var attributes = '';877 classes: ["token", o.type],
878 for (var name in env.attributes) {878 attributes: {},
879 attributes += ' ' + name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';879 language: language,
880 }880 };
881 881
882 return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + attributes + '>' + env.content + '</' + env.tag + '>';882 var aliases = o.alias;
883 };883 if (aliases) {
884 884 if (Array.isArray(aliases)) {
885 /**885 Array.prototype.push.apply(env.classes, aliases);
886 * @param {RegExp} pattern886 } else {
887 * @param {number} pos887 env.classes.push(aliases);
888 * @param {string} text888 }
889 * @param {boolean} lookbehind889 }
890 * @returns {RegExpExecArray | null}890
891 */891 _.hooks.run("wrap", env);
892 function matchPattern(pattern, pos, text, lookbehind) {892
893 pattern.lastIndex = pos;893 var attributes = "";
894 var match = pattern.exec(text);894 for (var name in env.attributes) {
895 if (match && lookbehind && match[1]) {895 attributes +=
896 // change the match to remove the text matched by the Prism lookbehind group896 " " +
897 var lookbehindLength = match[1].length;897 name +
898 match.index += lookbehindLength;898 '="' +
899 match[0] = match[0].slice(lookbehindLength);899 (env.attributes[name] || "").replace(/"/g, "&quot;") +
900 }900 '"';
901 return match;901 }
902 }902
903 903 return (
904 /**904 "<" +
905 * @param {string} text905 env.tag +
906 * @param {LinkedList<string | Token>} tokenList906 ' class="' +
907 * @param {any} grammar907 env.classes.join(" ") +
908 * @param {LinkedListNode<string | Token>} startNode908 '"' +
909 * @param {number} startPos909 attributes +
910 * @param {RematchOptions} [rematch]910 ">" +
911 * @returns {void}911 env.content +
912 * @private912 "</" +
913 *913 env.tag +
914 * @typedef RematchOptions914 ">"
915 * @property {string} cause915 );
916 * @property {number} reach916 };
917 */917
918 function matchGrammar(text, tokenList, grammar, startNode, startPos, rematch) {918 /**
919 for (var token in grammar) {919 * @param {RegExp} pattern
920 if (!grammar.hasOwnProperty(token) || !grammar[token]) {920 * @param {number} pos
921 continue;921 * @param {string} text
922 }922 * @param {boolean} lookbehind
923 923 * @returns {RegExpExecArray | null}
924 var patterns = grammar[token];924 */
925 patterns = Array.isArray(patterns) ? patterns : [patterns];925 function matchPattern(pattern, pos, text, lookbehind) {
926 926 pattern.lastIndex = pos;
927 for (var j = 0; j < patterns.length; ++j) {927 var match = pattern.exec(text);
928 if (rematch && rematch.cause == token + ',' + j) {928 if (match && lookbehind && match[1]) {
929 return;929 // change the match to remove the text matched by the Prism lookbehind group
930 }930 var lookbehindLength = match[1].length;
931 931 match.index += lookbehindLength;
932 var patternObj = patterns[j];932 match[0] = match[0].slice(lookbehindLength);
933 var inside = patternObj.inside;933 }
934 var lookbehind = !!patternObj.lookbehind;934 return match;
935 var greedy = !!patternObj.greedy;935 }
936 var alias = patternObj.alias;936
937 937 /**
938 if (greedy && !patternObj.pattern.global) {938 * @param {string} text
939 // Without the global flag, lastIndex won't work939 * @param {LinkedList<string | Token>} tokenList
940 var flags = patternObj.pattern.toString().match(/[imsuy]*$/)[0];940 * @param {any} grammar
941 patternObj.pattern = RegExp(patternObj.pattern.source, flags + 'g');941 * @param {LinkedListNode<string | Token>} startNode
942 }942 * @param {number} startPos
943 943 * @param {RematchOptions} [rematch]
944 /** @type {RegExp} */944 * @returns {void}
945 var pattern = patternObj.pattern || patternObj;945 * @private
946 946 *
947 for ( // iterate the token list and keep track of the current token/string position947 * @typedef RematchOptions
948 var currentNode = startNode.next, pos = startPos;948 * @property {string} cause
949 currentNode !== tokenList.tail;949 * @property {number} reach
950 pos += currentNode.value.length, currentNode = currentNode.next950 */
951 ) {951 function matchGrammar(
952 952 text,
953 if (rematch && pos >= rematch.reach) {953 tokenList,
954 break;954 grammar,
955 }955 startNode,
956 956 startPos,
957 var str = currentNode.value;957 rematch,
958 958 ) {
959 if (tokenList.length > text.length) {959 for (var token in grammar) {
960 // Something went terribly wrong, ABORT, ABORT!960 if (!grammar.hasOwnProperty(token) || !grammar[token]) {
961 return;961 continue;
962 }962 }
963 963
964 if (str instanceof Token) {964 var patterns = grammar[token];
965 continue;965 patterns = Array.isArray(patterns) ? patterns : [patterns];
966 }966
967 967 for (var j = 0; j < patterns.length; ++j) {
968 var removeCount = 1; // this is the to parameter of removeBetween968 if (rematch && rematch.cause == token + "," + j) {
969 var match;969 return;
970 970 }
971 if (greedy) {971
972 match = matchPattern(pattern, pos, text, lookbehind);972 var patternObj = patterns[j];
973 if (!match || match.index >= text.length) {973 var inside = patternObj.inside;
974 break;974 var lookbehind = !!patternObj.lookbehind;
975 }975 var greedy = !!patternObj.greedy;
976 976 var alias = patternObj.alias;
977 var from = match.index;977
978 var to = match.index + match[0].length;978 if (greedy && !patternObj.pattern.global) {
979 var p = pos;979 // Without the global flag, lastIndex won't work
980 980 var flags = patternObj.pattern.toString().match(/[imsuy]*$/)[0];
981 // find the node that contains the match981 patternObj.pattern = RegExp(patternObj.pattern.source, flags + "g");
982 p += currentNode.value.length;982 }
983 while (from >= p) {983
984 currentNode = currentNode.next;984 /** @type {RegExp} */
985 p += currentNode.value.length;985 var pattern = patternObj.pattern || patternObj;
986 }986
987 // adjust pos (and p)987 for (
988 p -= currentNode.value.length;988 // iterate the token list and keep track of the current token/string position
989 pos = p;989 var currentNode = startNode.next, pos = startPos;
990 990 currentNode !== tokenList.tail;
991 // the current node is a Token, then the match starts inside another Token, which is invalid991 pos += currentNode.value.length, currentNode = currentNode.next
992 if (currentNode.value instanceof Token) {992 ) {
993 continue;993 if (rematch && pos >= rematch.reach) {
994 }994 break;
995 995 }
996 // find the last node which is affected by this match996
997 for (997 var str = currentNode.value;
998 var k = currentNode;998
999 k !== tokenList.tail && (p < to || typeof k.value === 'string');999 if (tokenList.length > text.length) {
1000 k = k.next1000 // Something went terribly wrong, ABORT, ABORT!
1001 ) {1001 return;
1002 removeCount++;1002 }
1003 p += k.value.length;1003
1004 }1004 if (str instanceof Token) {
1005 removeCount--;1005 continue;
1006 1006 }
1007 // replace with the new match1007
1008 str = text.slice(pos, p);1008 var removeCount = 1; // this is the to parameter of removeBetween
1009 match.index -= pos;1009 var match;
1010 } else {1010
1011 match = matchPattern(pattern, 0, str, lookbehind);1011 if (greedy) {
1012 if (!match) {1012 match = matchPattern(pattern, pos, text, lookbehind);
1013 continue;1013 if (!match || match.index >= text.length) {
1014 }1014 break;
1015 }1015 }
1016 1016
1017 // eslint-disable-next-line no-redeclare1017 var from = match.index;
1018 var from = match.index;1018 var to = match.index + match[0].length;
1019 var matchStr = match[0];1019 var p = pos;
1020 var before = str.slice(0, from);1020
1021 var after = str.slice(from + matchStr.length);1021 // find the node that contains the match
1022 1022 p += currentNode.value.length;
1023 var reach = pos + str.length;1023 while (from >= p) {
1024 if (rematch && reach > rematch.reach) {1024 currentNode = currentNode.next;
1025 rematch.reach = reach;1025 p += currentNode.value.length;
1026 }1026 }
1027 1027 // adjust pos (and p)
1028 var removeFrom = currentNode.prev;1028 p -= currentNode.value.length;
1029 1029 pos = p;
1030 if (before) {1030
1031 removeFrom = addAfter(tokenList, removeFrom, before);1031 // the current node is a Token, then the match starts inside another Token, which is invalid
1032 pos += before.length;1032 if (currentNode.value instanceof Token) {
1033 }1033 continue;
1034 1034 }
1035 removeRange(tokenList, removeFrom, removeCount);1035
1036 1036 // find the last node which is affected by this match
1037 var wrapped = new Token(token, inside ? _.tokenize(matchStr, inside) : matchStr, alias, matchStr);1037 for (
1038 currentNode = addAfter(tokenList, removeFrom, wrapped);1038 var k = currentNode;
1039 1039 k !== tokenList.tail && (p < to || typeof k.value === "string");
1040 if (after) {1040 k = k.next
1041 addAfter(tokenList, currentNode, after);1041 ) {
1042 }1042 removeCount++;
1043 1043 p += k.value.length;
1044 if (removeCount > 1) {1044 }
1045 // at least one Token object was removed, so we have to do some rematching1045 removeCount--;
1046 // this can only happen if the current pattern is greedy1046
1047 1047 // replace with the new match
1048 /** @type {RematchOptions} */1048 str = text.slice(pos, p);
1049 var nestedRematch = {1049 match.index -= pos;
1050 cause: token + ',' + j,1050 } else {
1051 reach: reach1051 match = matchPattern(pattern, 0, str, lookbehind);
1052 };1052 if (!match) {
1053 matchGrammar(text, tokenList, grammar, currentNode.prev, pos, nestedRematch);1053 continue;
1054 1054 }
1055 // the reach might have been extended because of the rematching1055 }
1056 if (rematch && nestedRematch.reach > rematch.reach) {1056
1057 rematch.reach = nestedRematch.reach;1057 // eslint-disable-next-line no-redeclare
1058 }1058 var from = match.index;
1059 }1059 var matchStr = match[0];
1060 }1060 var before = str.slice(0, from);
1061 }1061 var after = str.slice(from + matchStr.length);
1062 }1062
1063 }1063 var reach = pos + str.length;
1064 1064 if (rematch && reach > rematch.reach) {
1065 /**1065 rematch.reach = reach;
1066 * @typedef LinkedListNode1066 }
1067 * @property {T} value1067
1068 * @property {LinkedListNode<T> | null} prev The previous node.1068 var removeFrom = currentNode.prev;
1069 * @property {LinkedListNode<T> | null} next The next node.1069
1070 * @template T1070 if (before) {
1071 * @private1071 removeFrom = addAfter(tokenList, removeFrom, before);
1072 */1072 pos += before.length;
1073 1073 }
1074 /**1074
1075 * @template T1075 removeRange(tokenList, removeFrom, removeCount);
1076 * @private1076
1077 */1077 var wrapped = new Token(
1078 function LinkedList() {1078 token,
1079 /** @type {LinkedListNode<T>} */1079 inside ? _.tokenize(matchStr, inside) : matchStr,
1080 var head = { value: null, prev: null, next: null };1080 alias,
1081 /** @type {LinkedListNode<T>} */1081 matchStr,
1082 var tail = { value: null, prev: head, next: null };1082 );
1083 head.next = tail;1083 currentNode = addAfter(tokenList, removeFrom, wrapped);
1084 1084
1085 /** @type {LinkedListNode<T>} */1085 if (after) {
1086 this.head = head;1086 addAfter(tokenList, currentNode, after);
1087 /** @type {LinkedListNode<T>} */1087 }
1088 this.tail = tail;1088
1089 this.length = 0;1089 if (removeCount > 1) {
1090 }1090 // at least one Token object was removed, so we have to do some rematching
1091 1091 // this can only happen if the current pattern is greedy
1092 /**1092
1093 * Adds a new node with the given value to the list.1093 /** @type {RematchOptions} */
1094 *1094 var nestedRematch = {
1095 * @param {LinkedList<T>} list1095 cause: token + "," + j,
1096 * @param {LinkedListNode<T>} node1096 reach: reach,
1097 * @param {T} value1097 };
1098 * @returns {LinkedListNode<T>} The added node.1098 matchGrammar(
1099 * @template T1099 text,
1100 */1100 tokenList,
1101 function addAfter(list, node, value) {1101 grammar,
1102 // assumes that node != list.tail && values.length >= 01102 currentNode.prev,
1103 var next = node.next;1103 pos,
1104 1104 nestedRematch,
1105 var newNode = { value: value, prev: node, next: next };1105 );
1106 node.next = newNode;1106
1107 next.prev = newNode;1107 // the reach might have been extended because of the rematching
1108 list.length++;1108 if (rematch && nestedRematch.reach > rematch.reach) {
1109 1109 rematch.reach = nestedRematch.reach;
1110 return newNode;1110 }
1111 }1111 }
1112 /**1112 }
1113 * Removes `count` nodes after the given node. The given node will not be removed.1113 }
1114 *1114 }
1115 * @param {LinkedList<T>} list1115 }
1116 * @param {LinkedListNode<T>} node1116
1117 * @param {number} count1117 /**
1118 * @template T1118 * @typedef LinkedListNode
1119 */1119 * @property {T} value
1120 function removeRange(list, node, count) {1120 * @property {LinkedListNode<T> | null} prev The previous node.
1121 var next = node.next;1121 * @property {LinkedListNode<T> | null} next The next node.
1122 for (var i = 0; i < count && next !== list.tail; i++) {1122 * @template T
1123 next = next.next;1123 * @private
1124 }1124 */
1125 node.next = next;1125
1126 next.prev = node;1126 /**
1127 list.length -= i;1127 * @template T
1128 }1128 * @private
1129 /**1129 */
1130 * @param {LinkedList<T>} list1130 function LinkedList() {
1131 * @returns {T[]}1131 /** @type {LinkedListNode<T>} */
1132 * @template T1132 var head = { value: null, prev: null, next: null };
1133 */1133 /** @type {LinkedListNode<T>} */
1134 function toArray(list) {1134 var tail = { value: null, prev: head, next: null };
1135 var array = [];1135 head.next = tail;
1136 var node = list.head.next;1136
1137 while (node !== list.tail) {1137 /** @type {LinkedListNode<T>} */
1138 array.push(node.value);1138 this.head = head;
1139 node = node.next;1139 /** @type {LinkedListNode<T>} */
1140 }1140 this.tail = tail;
1141 return array;1141 this.length = 0;
1142 }1142 }
1143 1143
1144 1144 /**
1145 if (!_self.document) {1145 * Adds a new node with the given value to the list.
1146 if (!_self.addEventListener) {1146 *
1147 // in Node.js1147 * @param {LinkedList<T>} list
1148 return _;1148 * @param {LinkedListNode<T>} node
1149 }1149 * @param {T} value
1150 1150 * @returns {LinkedListNode<T>} The added node.
1151 if (!_.disableWorkerMessageHandler) {1151 * @template T
1152 // In worker1152 */
1153 _self.addEventListener('message', function (evt) {1153 function addAfter(list, node, value) {
1154 var message = JSON.parse(evt.data);1154 // assumes that node != list.tail && values.length >= 0
1155 var lang = message.language;1155 var next = node.next;
1156 var code = message.code;1156
1157 var immediateClose = message.immediateClose;1157 var newNode = { value: value, prev: node, next: next };
1158 1158 node.next = newNode;
1159 _self.postMessage(_.highlight(code, _.languages[lang], lang));1159 next.prev = newNode;
1160 if (immediateClose) {1160 list.length++;
1161 _self.close();1161
1162 }1162 return newNode;
1163 }, false);1163 }
1164 }1164 /**
1165 1165 * Removes `count` nodes after the given node. The given node will not be removed.
1166 return _;1166 *
1167 }1167 * @param {LinkedList<T>} list
1168 1168 * @param {LinkedListNode<T>} node
1169 // Get current script and highlight1169 * @param {number} count
1170 var script = _.util.currentScript();1170 * @template T
1171 1171 */
1172 if (script) {1172 function removeRange(list, node, count) {
1173 _.filename = script.src;1173 var next = node.next;
1174 1174 for (var i = 0; i < count && next !== list.tail; i++) {
1175 if (script.hasAttribute('data-manual')) {1175 next = next.next;
1176 _.manual = true;1176 }
1177 }1177 node.next = next;
1178 }1178 next.prev = node;
1179 1179 list.length -= i;
1180 function highlightAutomaticallyCallback() {1180 }
1181 if (!_.manual) {1181 /**
1182 _.highlightAll();1182 * @param {LinkedList<T>} list
1183 }1183 * @returns {T[]}
1184 }1184 * @template T
1185 1185 */
1186 if (!_.manual) {1186 function toArray(list) {
1187 // If the document state is "loading", then we'll use DOMContentLoaded.1187 var array = [];
1188 // If the document state is "interactive" and the prism.js script is deferred, then we'll also use the1188 var node = list.head.next;
1189 // DOMContentLoaded event because there might be some plugins or languages which have also been deferred and they1189 while (node !== list.tail) {
1190 // might take longer one animation frame to execute which can create a race condition where only some plugins have1190 array.push(node.value);
1191 // been loaded when Prism.highlightAll() is executed, depending on how fast resources are loaded.1191 node = node.next;
1192 // See https://github.com/PrismJS/prism/issues/21021192 }
1193 var readyState = document.readyState;1193 return array;
1194 if (readyState === 'loading' || readyState === 'interactive' && script && script.defer) {1194 }
1195 document.addEventListener('DOMContentLoaded', highlightAutomaticallyCallback);1195
1196 } else {1196 if (!_self.document) {
1197 if (window.requestAnimationFrame) {1197 if (!_self.addEventListener) {
1198 window.requestAnimationFrame(highlightAutomaticallyCallback);1198 // in Node.js
1199 } else {1199 return _;
1200 window.setTimeout(highlightAutomaticallyCallback, 16);1200 }
1201 }1201
1202 }1202 if (!_.disableWorkerMessageHandler) {
1203 }1203 // In worker
1204 1204 _self.addEventListener(
1205 return _;1205 "message",
1206 1206 function (evt) {
1207}(_self));1207 var message = JSON.parse(evt.data);
1208 1208 var lang = message.language;
1209if (typeof module !== 'undefined' && module.exports) {1209 var code = message.code;
1210 module.exports = Prism;1210 var immediateClose = message.immediateClose;
1211
1212 _self.postMessage(_.highlight(code, _.languages[lang], lang));
1213 if (immediateClose) {
1214 _self.close();
1215 }
1216 },
1217 false,
1218 );
1219 }
1220
1221 return _;
1222 }
1223
1224 // Get current script and highlight
1225 var script = _.util.currentScript();
1226
1227 if (script) {
1228 _.filename = script.src;
1229
1230 if (script.hasAttribute("data-manual")) {
1231 _.manual = true;
1232 }
1233 }
1234
1235 function highlightAutomaticallyCallback() {
1236 if (!_.manual) {
1237 _.highlightAll();
1238 }
1239 }
1240
1241 if (!_.manual) {
1242 // If the document state is "loading", then we'll use DOMContentLoaded.
1243 // If the document state is "interactive" and the prism.js script is deferred, then we'll also use the
1244 // DOMContentLoaded event because there might be some plugins or languages which have also been deferred and they
1245 // might take longer one animation frame to execute which can create a race condition where only some plugins have
1246 // been loaded when Prism.highlightAll() is executed, depending on how fast resources are loaded.
1247 // See https://github.com/PrismJS/prism/issues/2102
1248 var readyState = document.readyState;
1249 if (
1250 readyState === "loading" ||
1251 (readyState === "interactive" && script && script.defer)
1252 ) {
1253 document.addEventListener(
1254 "DOMContentLoaded",
1255 highlightAutomaticallyCallback,
1256 );
1257 } else {
1258 if (window.requestAnimationFrame) {
1259 window.requestAnimationFrame(highlightAutomaticallyCallback);
1260 } else {
1261 window.setTimeout(highlightAutomaticallyCallback, 16);
1262 }
1263 }
1264 }
1265
1266 return _;
1267})(_self);
1268
1269if (typeof module !== "undefined" && module.exports) {
1270 module.exports = Prism;
1211}1271}
1212 1272
1213// hack for components to work correctly in node.js1273// hack for components to work correctly in node.js
1214if (typeof global !== 'undefined') {1274if (typeof global !== "undefined") {
1215 global.Prism = Prism;1275 global.Prism = Prism;
1216}1276}
1217 1277
1218// some additional documentation/types1278// some additional documentation/types
@@ -1263,1007 +1323,1096 @@ if (typeof global !== 'undefined') { · 44 unmodified lines
1263 * @global1323 * @global
1264 * @public1324 * @public
1265 */1325 */
1266;
1267Prism.languages.markup = {1326Prism.languages.markup = {
1268 'comment': {1327 comment: {
1269 pattern: /<!--(?:(?!<!--)[\s\S])*?-->/,1328 pattern: /<!--(?:(?!<!--)[\s\S])*?-->/,
1270 greedy: true1329 greedy: true,
1271 },1330 },
1272 'prolog': {1331 prolog: {
1273 pattern: /<\?[\s\S]+?\?>/,1332 pattern: /<\?[\s\S]+?\?>/,
1274 greedy: true1333 greedy: true,
1275 },1334 },
1276 'doctype': {1335 doctype: {
1277 // https://www.w3.org/TR/xml/#NT-doctypedecl1336 // https://www.w3.org/TR/xml/#NT-doctypedecl
1278 pattern: /<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,1337 pattern:
1279 greedy: true,1338 /<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,
1280 inside: {1339 greedy: true,
1281 'internal-subset': {1340 inside: {
1282 pattern: /(^[^\[]*\[)[\s\S]+(?=\]>$)/,1341 "internal-subset": {
1283 lookbehind: true,1342 pattern: /(^[^\[]*\[)[\s\S]+(?=\]>$)/,
1284 greedy: true,1343 lookbehind: true,
1285 inside: null // see below1344 greedy: true,
1286 },1345 inside: null, // see below
1287 'string': {1346 },
1288 pattern: /"[^"]*"|'[^']*'/,1347 string: {
1289 greedy: true1348 pattern: /"[^"]*"|'[^']*'/,
1290 },1349 greedy: true,
1291 'punctuation': /^<!|>$|[[\]]/,1350 },
1292 'doctype-tag': /^DOCTYPE/i,1351 punctuation: /^<!|>$|[[\]]/,
1293 'name': /[^\s<>'"]+/1352 "doctype-tag": /^DOCTYPE/i,
1294 }1353 name: /[^\s<>'"]+/,
1295 },1354 },
1296 'cdata': {1355 },
1297 pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,1356 cdata: {
1298 greedy: true1357 pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,
1299 },1358 greedy: true,
1300 'tag': {1359 },
1301 pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,1360 tag: {
1302 greedy: true,1361 pattern:
1303 inside: {1362 /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,
1304 'tag': {1363 greedy: true,
1305 pattern: /^<\/?[^\s>\/]+/,1364 inside: {
1306 inside: {1365 tag: {
1307 'punctuation': /^<\/?/,1366 pattern: /^<\/?[^\s>\/]+/,
1308 'namespace': /^[^\s>\/:]+:/1367 inside: {
1309 }1368 punctuation: /^<\/?/,
1310 },1369 namespace: /^[^\s>\/:]+:/,
1311 'special-attr': [],1370 },
1312 'attr-value': {1371 },
1313 pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,1372 "special-attr": [],
1314 inside: {1373 "attr-value": {
1315 'punctuation': [1374 pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,
1316 {1375 inside: {
1317 pattern: /^=/,1376 punctuation: [
1318 alias: 'attr-equals'1377 {
1319 },1378 pattern: /^=/,
1320 {1379 alias: "attr-equals",
1321 pattern: /^(\s*)["']|["']$/,1380 },
1322 lookbehind: true1381 {
1323 }1382 pattern: /^(\s*)["']|["']$/,
1324 ]1383 lookbehind: true,
1325 }1384 },
1326 },1385 ],
1327 'punctuation': /\/?>/,1386 },
1328 'attr-name': {1387 },
1329 pattern: /[^\s>\/]+/,1388 punctuation: /\/?>/,
1330 inside: {1389 "attr-name": {
1331 'namespace': /^[^\s>\/:]+:/1390 pattern: /[^\s>\/]+/,
1332 }1391 inside: {
1333 }1392 namespace: /^[^\s>\/:]+:/,
1334 1393 },
1335 }1394 },
1336 },1395 },
1337 'entity': [1396 },
1338 {1397 entity: [
1339 pattern: /&[\da-z]{1,8};/i,1398 {
1340 alias: 'named-entity'1399 pattern: /&[\da-z]{1,8};/i,
1341 },1400 alias: "named-entity",
1342 /&#x?[\da-f]{1,8};/i1401 },
1343 ]1402 /&#x?[\da-f]{1,8};/i,
1403 ],
1344};1404};
1345 1405
1346Prism.languages.markup['tag'].inside['attr-value'].inside['entity'] =1406Prism.languages.markup["tag"].inside["attr-value"].inside["entity"] =
1347 Prism.languages.markup['entity'];1407 Prism.languages.markup["entity"];
1348Prism.languages.markup['doctype'].inside['internal-subset'].inside = Prism.languages.markup;1408Prism.languages.markup["doctype"].inside["internal-subset"].inside =
1409 Prism.languages.markup;
1349 1410
1350// Plugin to make entity title show the real entity, idea by Roman Komarov1411// Plugin to make entity title show the real entity, idea by Roman Komarov
1351Prism.hooks.add('wrap', function (env) {1412Prism.hooks.add("wrap", function (env) {
1352 1413 if (env.type === "entity") {
1353 if (env.type === 'entity') {1414 env.attributes["title"] = env.content.replace(/&amp;/, "&");
1354 env.attributes['title'] = env.content.replace(/&amp;/, '&');1415 }
1355 }
1356});1416});
1357 1417
1358Object.defineProperty(Prism.languages.markup.tag, 'addInlined', {1418Object.defineProperty(Prism.languages.markup.tag, "addInlined", {
1359 /**1419 /**
1360 * Adds an inlined language to markup.1420 * Adds an inlined language to markup.
1361 *1421 *
1362 * An example of an inlined language is CSS with `<style>` tags.1422 * An example of an inlined language is CSS with `<style>` tags.
1363 *1423 *
1364 * @param {string} tagName The name of the tag that contains the inlined language. This name will be treated as1424 * @param {string} tagName The name of the tag that contains the inlined language. This name will be treated as
1365 * case insensitive.1425 * case insensitive.
1366 * @param {string} lang The language key.1426 * @param {string} lang The language key.
1367 * @example1427 * @example
1368 * addInlined('style', 'css');1428 * addInlined('style', 'css');
1369 */1429 */
1370 value: function addInlined(tagName, lang) {1430 value: function addInlined(tagName, lang) {
1371 var includedCdataInside = {};1431 var includedCdataInside = {};
1372 includedCdataInside['language-' + lang] = {1432 includedCdataInside["language-" + lang] = {
1373 pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,1433 pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,
1374 lookbehind: true,1434 lookbehind: true,
1375 inside: Prism.languages[lang]1435 inside: Prism.languages[lang],
1376 };1436 };
1377 includedCdataInside['cdata'] = /^<!\[CDATA\[|\]\]>$/i;1437 includedCdataInside["cdata"] = /^<!\[CDATA\[|\]\]>$/i;
1378 1438
1379 var inside = {1439 var inside = {
1380 'included-cdata': {1440 "included-cdata": {
1381 pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,1441 pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,
1382 inside: includedCdataInside1442 inside: includedCdataInside,
1383 }1443 },
1384 };1444 };
1385 inside['language-' + lang] = {1445 inside["language-" + lang] = {
1386 pattern: /[\s\S]+/,1446 pattern: /[\s\S]+/,
1387 inside: Prism.languages[lang]1447 inside: Prism.languages[lang],
1388 };1448 };
1389 1449
1390 var def = {};1450 var def = {};
1391 def[tagName] = {1451 def[tagName] = {
1392 pattern: RegExp(/(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g, function () { return tagName; }), 'i'),1452 pattern: RegExp(
1393 lookbehind: true,1453 /(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(
1394 greedy: true,1454 /__/g,
1395 inside: inside1455 function () {
1396 };1456 return tagName;
1397 1457 },
1398 Prism.languages.insertBefore('markup', 'cdata', def);1458 ),
1399 }1459 "i",
1460 ),
1461 lookbehind: true,
1462 greedy: true,
1463 inside: inside,
1464 };
1465
1466 Prism.languages.insertBefore("markup", "cdata", def);
1467 },
1400});1468});
1401Object.defineProperty(Prism.languages.markup.tag, 'addAttribute', {1469Object.defineProperty(Prism.languages.markup.tag, "addAttribute", {
1402 /**1470 /**
1403 * Adds an pattern to highlight languages embedded in HTML attributes.1471 * Adds an pattern to highlight languages embedded in HTML attributes.
1404 *1472 *
1405 * An example of an inlined language is CSS with `style` attributes.1473 * An example of an inlined language is CSS with `style` attributes.
1406 *1474 *
1407 * @param {string} attrName The name of the tag that contains the inlined language. This name will be treated as1475 * @param {string} attrName The name of the tag that contains the inlined language. This name will be treated as
1408 * case insensitive.1476 * case insensitive.
1409 * @param {string} lang The language key.1477 * @param {string} lang The language key.
1410 * @example1478 * @example
1411 * addAttribute('style', 'css');1479 * addAttribute('style', 'css');
1412 */1480 */
1413 value: function (attrName, lang) {1481 value: function (attrName, lang) {
1414 Prism.languages.markup.tag.inside['special-attr'].push({1482 Prism.languages.markup.tag.inside["special-attr"].push({
1415 pattern: RegExp(1483 pattern: RegExp(
1416 /(^|["'\s])/.source + '(?:' + attrName + ')' + /\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,1484 /(^|["'\s])/.source +
1417 'i'1485 "(?:" +
1418 ),1486 attrName +
1419 lookbehind: true,1487 ")" +
1420 inside: {1488 /\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,
1421 'attr-name': /^[^\s=]+/,1489 "i",
1422 'attr-value': {1490 ),
1423 pattern: /=[\s\S]+/,1491 lookbehind: true,
1424 inside: {1492 inside: {
1425 'value': {1493 "attr-name": /^[^\s=]+/,
1426 pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,1494 "attr-value": {
1427 lookbehind: true,1495 pattern: /=[\s\S]+/,
1428 alias: [lang, 'language-' + lang],1496 inside: {
1429 inside: Prism.languages[lang]1497 value: {
1430 },1498 pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,
1431 'punctuation': [1499 lookbehind: true,
1432 {1500 alias: [lang, "language-" + lang],
1433 pattern: /^=/,1501 inside: Prism.languages[lang],
1434 alias: 'attr-equals'1502 },
1435 },1503 punctuation: [
1436 /"|'/1504 {
1437 ]1505 pattern: /^=/,
1438 }1506 alias: "attr-equals",
1439 }1507 },
1440 }1508 /"|'/,
1441 });1509 ],
1442 }1510 },
1511 },
1512 },
1513 });
1514 },
1443});1515});
1444 1516
1445Prism.languages.html = Prism.languages.markup;1517Prism.languages.html = Prism.languages.markup;
1446Prism.languages.mathml = Prism.languages.markup;1518Prism.languages.mathml = Prism.languages.markup;
1447Prism.languages.svg = Prism.languages.markup;1519Prism.languages.svg = Prism.languages.markup;
1448 1520
1449Prism.languages.xml = Prism.languages.extend('markup', {});1521Prism.languages.xml = Prism.languages.extend("markup", {});
1450Prism.languages.ssml = Prism.languages.xml;1522Prism.languages.ssml = Prism.languages.xml;
1451Prism.languages.atom = Prism.languages.xml;1523Prism.languages.atom = Prism.languages.xml;
1452Prism.languages.rss = Prism.languages.xml;1524Prism.languages.rss = Prism.languages.xml;
1453 1525
1454(function (Prism) {1526(function (Prism) {
1455 1527 var string =
1456 var string = /(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;1528 /(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;
1457 1529
1458 Prism.languages.css = {1530 Prism.languages.css = {
1459 'comment': /\/\*[\s\S]*?\*\//,1531 comment: /\/\*[\s\S]*?\*\//,
1460 'atrule': {1532 atrule: {
1461 pattern: RegExp('@[\\w-](?:' + /[^;{\s"']|\s+(?!\s)/.source + '|' + string.source + ')*?' + /(?:;|(?=\s*\{))/.source),1533 pattern: RegExp(
1462 inside: {1534 "@[\\w-](?:" +
1463 'rule': /^@[\w-]+/,1535 /[^;{\s"']|\s+(?!\s)/.source +
1464 'selector-function-argument': {1536 "|" +
1465 pattern: /(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,1537 string.source +
1466 lookbehind: true,1538 ")*?" +
1467 alias: 'selector'1539 /(?:;|(?=\s*\{))/.source,
1468 },1540 ),
1469 'keyword': {1541 inside: {
1470 pattern: /(^|[^\w-])(?:and|not|only|or)(?![\w-])/,1542 rule: /^@[\w-]+/,
1471 lookbehind: true1543 "selector-function-argument": {
1472 }1544 pattern:
1473 // See rest below1545 /(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,
1474 }1546 lookbehind: true,
1475 },1547 alias: "selector",
1476 'url': {1548 },
1477 // https://drafts.csswg.org/css-values-3/#urls1549 keyword: {
1478 pattern: RegExp('\\burl\\((?:' + string.source + '|' + /(?:[^\\\r\n()"']|\\[\s\S])*/.source + ')\\)', 'i'),1550 pattern: /(^|[^\w-])(?:and|not|only|or)(?![\w-])/,
1479 greedy: true,1551 lookbehind: true,
1480 inside: {1552 },
1481 'function': /^url/i,1553 // See rest below
1482 'punctuation': /^\(|\)$/,1554 },
1483 'string': {1555 },
1484 pattern: RegExp('^' + string.source + '$'),1556 url: {
1485 alias: 'url'1557 // https://drafts.csswg.org/css-values-3/#urls
1486 }1558 pattern: RegExp(
1487 }1559 "\\burl\\((?:" +
1488 },1560 string.source +
1489 'selector': {1561 "|" +
1490 pattern: RegExp('(^|[{}\\s])[^{}\\s](?:[^{};"\'\\s]|\\s+(?![\\s{])|' + string.source + ')*(?=\\s*\\{)'),1562 /(?:[^\\\r\n()"']|\\[\s\S])*/.source +
1491 lookbehind: true1563 ")\\)",
1492 },1564 "i",
1493 'string': {1565 ),
1494 pattern: string,1566 greedy: true,
1495 greedy: true1567 inside: {
1496 },1568 function: /^url/i,
1497 'property': {1569 punctuation: /^\(|\)$/,
1498 pattern: /(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,1570 string: {
1499 lookbehind: true1571 pattern: RegExp("^" + string.source + "$"),
1500 },1572 alias: "url",
1501 'important': /!important\b/i,1573 },
1502 'function': {1574 },
1503 pattern: /(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,1575 },
1504 lookbehind: true1576 selector: {
1505 },1577 pattern: RegExp(
1506 'punctuation': /[(){};:,]/1578 "(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|" +
1507 };1579 string.source +
1508 1580 ")*(?=\\s*\\{)",
1509 Prism.languages.css['atrule'].inside.rest = Prism.languages.css;1581 ),
1510 1582 lookbehind: true,
1511 var markup = Prism.languages.markup;1583 },
1512 if (markup) {1584 string: {
1513 markup.tag.addInlined('style', 'css');1585 pattern: string,
1514 markup.tag.addAttribute('style', 'css');1586 greedy: true,
1515 }1587 },
1516 1588 property: {
1517}(Prism));1589 pattern:
1590 /(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,
1591 lookbehind: true,
1592 },
1593 important: /!important\b/i,
1594 function: {
1595 pattern: /(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,
1596 lookbehind: true,
1597 },
1598 punctuation: /[(){};:,]/,
1599 };
1600
1601 Prism.languages.css["atrule"].inside.rest = Prism.languages.css;
1602
1603 var markup = Prism.languages.markup;
1604 if (markup) {
1605 markup.tag.addInlined("style", "css");
1606 markup.tag.addAttribute("style", "css");
1607 }
1608})(Prism);
1518 1609
1519Prism.languages.clike = {1610Prism.languages.clike = {
1520 'comment': [1611 comment: [
1521 {1612 {
1522 pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,1613 pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,
1523 lookbehind: true,1614 lookbehind: true,
1524 greedy: true1615 greedy: true,
1525 },1616 },
1526 {1617 {
1527 pattern: /(^|[^\\:])\/\/.*/,1618 pattern: /(^|[^\\:])\/\/.*/,
1528 lookbehind: true,1619 lookbehind: true,
1529 greedy: true1620 greedy: true,
1530 }1621 },
1531 ],1622 ],
1532 'string': {1623 string: {
1533 pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,1624 pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
1534 greedy: true1625 greedy: true,
1535 },1626 },
1536 'class-name': {1627 "class-name": {
1537 pattern: /(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,1628 pattern:
1538 lookbehind: true,1629 /(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,
1539 inside: {1630 lookbehind: true,
1540 'punctuation': /[.\\]/1631 inside: {
1541 }1632 punctuation: /[.\\]/,
1542 },1633 },
1543 'keyword': /\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,1634 },
1544 'boolean': /\b(?:false|true)\b/,1635 keyword:
1545 'function': /\b\w+(?=\()/,1636 /\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,
1546 'number': /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,1637 boolean: /\b(?:false|true)\b/,
1547 'operator': /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,1638 function: /\b\w+(?=\()/,
1548 'punctuation': /[{}[\];(),.:]/1639 number: /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,
1640 operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,
1641 punctuation: /[{}[\];(),.:]/,
1549};1642};
1550 1643
1551Prism.languages.javascript = Prism.languages.extend('clike', {1644Prism.languages.javascript = Prism.languages.extend("clike", {
1552 'class-name': [1645 "class-name": [
1553 Prism.languages.clike['class-name'],1646 Prism.languages.clike["class-name"],
1554 {1647 {
1555 pattern: /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,1648 pattern:
1556 lookbehind: true1649 /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,
1557 }1650 lookbehind: true,
1558 ],1651 },
1559 'keyword': [1652 ],
1560 {1653 keyword: [
1561 pattern: /((?:^|\})\s*)catch\b/,1654 {
1562 lookbehind: true1655 pattern: /((?:^|\})\s*)catch\b/,
1563 },1656 lookbehind: true,
1564 {1657 },
1565 pattern: /(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,1658 {
1566 lookbehind: true1659 pattern:
1567 },1660 /(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,
1568 ],1661 lookbehind: true,
1569 // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)1662 },
1570 'function': /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,1663 ],
1571 'number': {1664 // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
1572 pattern: RegExp(1665 function:
1573 /(^|[^\w$])/.source +1666 /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,
1574 '(?:' +1667 number: {
1575 (1668 pattern: RegExp(
1576 // constant1669 /(^|[^\w$])/.source +
1577 /NaN|Infinity/.source +1670 "(?:" +
1578 '|' +1671 // constant
1579 // binary integer1672 (/NaN|Infinity/.source +
1580 /0[bB][01]+(?:_[01]+)*n?/.source +1673 "|" +
1581 '|' +1674 // binary integer
1582 // octal integer1675 /0[bB][01]+(?:_[01]+)*n?/.source +
1583 /0[oO][0-7]+(?:_[0-7]+)*n?/.source +1676 "|" +
1584 '|' +1677 // octal integer
1585 // hexadecimal integer1678 /0[oO][0-7]+(?:_[0-7]+)*n?/.source +
1586 /0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source +1679 "|" +
1587 '|' +1680 // hexadecimal integer
1588 // decimal bigint1681 /0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source +
1589 /\d+(?:_\d+)*n/.source +1682 "|" +
1590 '|' +1683 // decimal bigint
1591 // decimal number (integer or float) but no bigint1684 /\d+(?:_\d+)*n/.source +
1592 /(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source1685 "|" +
1593 ) +1686 // decimal number (integer or float) but no bigint
1594 ')' +1687 /(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/
1595 /(?![\w$])/.source1688 .source) +
1596 ),1689 ")" +
1597 lookbehind: true1690 /(?![\w$])/.source,
1598 },1691 ),
1599 'operator': /--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/1692 lookbehind: true,
1693 },
1694 operator:
1695 /--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/,
1600});1696});
1601 1697
1602Prism.languages.javascript['class-name'][0].pattern = /(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/;1698Prism.languages.javascript["class-name"][0].pattern =
1603 1699 /(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/;
1604Prism.languages.insertBefore('javascript', 'keyword', {1700
1605 'regex': {1701Prism.languages.insertBefore("javascript", "keyword", {
1606 pattern: RegExp(1702 regex: {
1607 // lookbehind1703 pattern: RegExp(
1608 // eslint-disable-next-line regexp/no-dupe-characters-character-class1704 // lookbehind
1609 /((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source +1705 // eslint-disable-next-line regexp/no-dupe-characters-character-class
1610 // Regex pattern:1706 /((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source +
1611 // There are 2 regex patterns here. The RegExp set notation proposal added support for nested character1707 // Regex pattern:
1612 // classes if the `v` flag is present. Unfortunately, nested CCs are both context-free and incompatible1708 // There are 2 regex patterns here. The RegExp set notation proposal added support for nested character
1613 // with the only syntax, so we have to define 2 different regex patterns.1709 // classes if the `v` flag is present. Unfortunately, nested CCs are both context-free and incompatible
1614 /\//.source +1710 // with the only syntax, so we have to define 2 different regex patterns.
1615 '(?:' +1711 /\//.source +
1616 /(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source +1712 "(?:" +
1617 '|' +1713 /(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/
1618 // `v` flag syntax. This supports 3 levels of nested character classes.1714 .source +
1619 /(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source +1715 "|" +
1620 ')' +1716 // `v` flag syntax. This supports 3 levels of nested character classes.
1621 // lookahead1717 /(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/
1622 /(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source1718 .source +
1623 ),1719 ")" +
1624 lookbehind: true,1720 // lookahead
1625 greedy: true,1721 /(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/
1626 inside: {1722 .source,
1627 'regex-source': {1723 ),
1628 pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/,1724 lookbehind: true,
1629 lookbehind: true,1725 greedy: true,
1630 alias: 'language-regex',1726 inside: {
1631 inside: Prism.languages.regex1727 "regex-source": {
1632 },1728 pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/,
1633 'regex-delimiter': /^\/|\/$/,1729 lookbehind: true,
1634 'regex-flags': /^[a-z]+$/,1730 alias: "language-regex",
1635 }1731 inside: Prism.languages.regex,
1636 },1732 },
1637 // This must be declared before keyword because we use "function" inside the look-forward1733 "regex-delimiter": /^\/|\/$/,
1638 'function-variable': {1734 "regex-flags": /^[a-z]+$/,
1639 pattern: /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,1735 },
1640 alias: 'function'1736 },
1641 },1737 // This must be declared before keyword because we use "function" inside the look-forward
1642 'parameter': [1738 "function-variable": {
1643 {1739 pattern:
1644 pattern: /(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,1740 /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,
1645 lookbehind: true,1741 alias: "function",
1646 inside: Prism.languages.javascript1742 },
1647 },1743 parameter: [
1648 {1744 {
1649 pattern: /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,1745 pattern:
1650 lookbehind: true,1746 /(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,
1651 inside: Prism.languages.javascript1747 lookbehind: true,
1652 },1748 inside: Prism.languages.javascript,
1653 {1749 },
1654 pattern: /(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,1750 {
1655 lookbehind: true,1751 pattern:
1656 inside: Prism.languages.javascript1752 /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,
1657 },1753 lookbehind: true,
1658 {1754 inside: Prism.languages.javascript,
1659 pattern: /((?:\b|\s|^)(?!(?: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|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,1755 },
1660 lookbehind: true,1756 {
1661 inside: Prism.languages.javascript1757 pattern:
1662 }1758 /(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,
1663 ],1759 lookbehind: true,
1664 'constant': /\b[A-Z](?:[A-Z_]|\dx?)*\b/1760 inside: Prism.languages.javascript,
1761 },
1762 {
1763 pattern:
1764 /((?:\b|\s|^)(?!(?: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|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,
1765 lookbehind: true,
1766 inside: Prism.languages.javascript,
1767 },
1768 ],
1769 constant: /\b[A-Z](?:[A-Z_]|\dx?)*\b/,
1665});1770});
1666 1771
1667Prism.languages.insertBefore('javascript', 'string', {1772Prism.languages.insertBefore("javascript", "string", {
1668 'hashbang': {1773 hashbang: {
1669 pattern: /^#!.*/,1774 pattern: /^#!.*/,
1670 greedy: true,1775 greedy: true,
1671 alias: 'comment'1776 alias: "comment",
1672 },1777 },
1673 'template-string': {1778 "template-string": {
1674 pattern: /`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,1779 pattern:
1675 greedy: true,1780 /`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,
1676 inside: {1781 greedy: true,
1677 'template-punctuation': {1782 inside: {
1678 pattern: /^`|`$/,1783 "template-punctuation": {
1679 alias: 'string'1784 pattern: /^`|`$/,
1680 },1785 alias: "string",
1681 'interpolation': {1786 },
1682 pattern: /((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,1787 interpolation: {
1683 lookbehind: true,1788 pattern:
1684 inside: {1789 /((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,
1685 'interpolation-punctuation': {1790 lookbehind: true,
1686 pattern: /^\$\{|\}$/,1791 inside: {
1687 alias: 'punctuation'1792 "interpolation-punctuation": {
1688 },1793 pattern: /^\$\{|\}$/,
1689 rest: Prism.languages.javascript1794 alias: "punctuation",
1690 }1795 },
1691 },1796 rest: Prism.languages.javascript,
1692 'string': /[\s\S]+/1797 },
1693 }1798 },
1694 },1799 string: /[\s\S]+/,
1695 'string-property': {1800 },
1696 pattern: /((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,1801 },
1697 lookbehind: true,1802 "string-property": {
1698 greedy: true,1803 pattern:
1699 alias: 'property'1804 /((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,
1700 }1805 lookbehind: true,
1806 greedy: true,
1807 alias: "property",
1808 },
1701});1809});
1702 1810
1703Prism.languages.insertBefore('javascript', 'operator', {1811Prism.languages.insertBefore("javascript", "operator", {
1704 'literal-property': {1812 "literal-property": {
1705 pattern: /((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,1813 pattern:
1706 lookbehind: true,1814 /((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,
1707 alias: 'property'1815 lookbehind: true,
1708 },1816 alias: "property",
1817 },
1709});1818});
1710 1819
1711if (Prism.languages.markup) {1820if (Prism.languages.markup) {
1712 Prism.languages.markup.tag.addInlined('script', 'javascript');1821 Prism.languages.markup.tag.addInlined("script", "javascript");
1713 1822
1714 // add attribute support for all DOM events.1823 // add attribute support for all DOM events.
1715 // https://developer.mozilla.org/en-US/docs/Web/Events#Standard_events1824 // https://developer.mozilla.org/en-US/docs/Web/Events#Standard_events
1716 Prism.languages.markup.tag.addAttribute(1825 Prism.languages.markup.tag.addAttribute(
1717 /on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,1826 /on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/
1718 'javascript'1827 .source,
1719 );1828 "javascript",
1829 );
1720}1830}
1721 1831
1722Prism.languages.js = Prism.languages.javascript;1832Prism.languages.js = Prism.languages.javascript;
1723 1833
1724(function (Prism) {1834(function (Prism) {
1725 // $ set | grep '^[A-Z][^[:space:]]*=' | cut -d= -f1 | tr '\n' '|'1835 // $ set | grep '^[A-Z][^[:space:]]*=' | cut -d= -f1 | tr '\n' '|'
1726 // + LC_ALL, RANDOM, REPLY, SECONDS.1836 // + LC_ALL, RANDOM, REPLY, SECONDS.
1727 // + make sure PS1..4 are here as they are not always set,1837 // + make sure PS1..4 are here as they are not always set,
1728 // - some useless things.1838 // - some useless things.
1729 var envVars = '\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b';1839 var envVars =
1730 1840 "\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b";
1731 var commandAfterHeredoc = {1841
1732 pattern: /(^(["']?)\w+\2)[ \t]+\S.*/,1842 var commandAfterHeredoc = {
1733 lookbehind: true,1843 pattern: /(^(["']?)\w+\2)[ \t]+\S.*/,
1734 alias: 'punctuation', // this looks reasonably well in all themes1844 lookbehind: true,
1735 inside: null // see below1845 alias: "punctuation", // this looks reasonably well in all themes
1736 };1846 inside: null, // see below
1737 1847 };
1738 var insideString = {1848
1739 'bash': commandAfterHeredoc,1849 var insideString = {
1740 'environment': {1850 bash: commandAfterHeredoc,
1741 pattern: RegExp('\\$' + envVars),1851 environment: {
1742 alias: 'constant'1852 pattern: RegExp("\\$" + envVars),
1743 },1853 alias: "constant",
1744 'variable': [1854 },
1745 // [0]: Arithmetic Environment1855 variable: [
1746 {1856 // [0]: Arithmetic Environment
1747 pattern: /\$?\(\([\s\S]+?\)\)/,1857 {
1748 greedy: true,1858 pattern: /\$?\(\([\s\S]+?\)\)/,
1749 inside: {1859 greedy: true,
1750 // If there is a $ sign at the beginning highlight $(( and )) as variable1860 inside: {
1751 'variable': [1861 // If there is a $ sign at the beginning highlight $(( and )) as variable
1752 {1862 variable: [
1753 pattern: /(^\$\(\([\s\S]+)\)\)/,1863 {
1754 lookbehind: true1864 pattern: /(^\$\(\([\s\S]+)\)\)/,
1755 },1865 lookbehind: true,
1756 /^\$\(\(/1866 },
1757 ],1867 /^\$\(\(/,
1758 'number': /\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,1868 ],
1759 // Operators according to https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic1869 number: /\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,
1760 'operator': /--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,1870 // Operators according to https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic
1761 // If there is no $ sign at the beginning highlight (( and )) as punctuation1871 operator: /--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,
1762 'punctuation': /\(\(?|\)\)?|,|;/1872 // If there is no $ sign at the beginning highlight (( and )) as punctuation
1763 }1873 punctuation: /\(\(?|\)\)?|,|;/,
1764 },1874 },
1765 // [1]: Command Substitution1875 },
1766 {1876 // [1]: Command Substitution
1767 pattern: /\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,1877 {
1768 greedy: true,1878 pattern: /\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,
1769 inside: {1879 greedy: true,
1770 'variable': /^\$\(|^`|\)$|`$/1880 inside: {
1771 }1881 variable: /^\$\(|^`|\)$|`$/,
1772 },1882 },
1773 // [2]: Brace expansion1883 },
1774 {1884 // [2]: Brace expansion
1775 pattern: /\$\{[^}]+\}/,1885 {
1776 greedy: true,1886 pattern: /\$\{[^}]+\}/,
1777 inside: {1887 greedy: true,
1778 'operator': /:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,1888 inside: {
1779 'punctuation': /[\[\]]/,1889 operator: /:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,
1780 'environment': {1890 punctuation: /[\[\]]/,
1781 pattern: RegExp('(\\{)' + envVars),1891 environment: {
1782 lookbehind: true,1892 pattern: RegExp("(\\{)" + envVars),
1783 alias: 'constant'1893 lookbehind: true,
1784 }1894 alias: "constant",
1785 }1895 },
1786 },1896 },
1787 /\$(?:\w+|[#?*!@$])/1897 },
1788 ],1898 /\$(?:\w+|[#?*!@$])/,
1789 // Escape sequences from echo and printf's manuals, and escaped quotes.1899 ],
1790 'entity': /\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/1900 // Escape sequences from echo and printf's manuals, and escaped quotes.
1791 };1901 entity:
1792 1902 /\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/,
1793 Prism.languages.bash = {1903 };
1794 'shebang': {1904
1795 pattern: /^#!\s*\/.*/,1905 Prism.languages.bash = {
1796 alias: 'important'1906 shebang: {
1797 },1907 pattern: /^#!\s*\/.*/,
1798 'comment': {1908 alias: "important",
1799 pattern: /(^|[^"{\\$])#.*/,1909 },
1800 lookbehind: true1910 comment: {
1801 },1911 pattern: /(^|[^"{\\$])#.*/,
1802 'function-name': [1912 lookbehind: true,
1803 // a) function foo {1913 },
1804 // b) foo() {1914 "function-name": [
1805 // c) function foo() {1915 // a) function foo {
1806 // but not “foo {”1916 // b) foo() {
1807 {1917 // c) function foo() {
1808 // a) and c)1918 // but not “foo {”
1809 pattern: /(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,1919 {
1810 lookbehind: true,1920 // a) and c)
1811 alias: 'function'1921 pattern: /(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,
1812 },1922 lookbehind: true,
1813 {1923 alias: "function",
1814 // b)1924 },
1815 pattern: /\b[\w-]+(?=\s*\(\s*\)\s*\{)/,1925 {
1816 alias: 'function'1926 // b)
1817 }1927 pattern: /\b[\w-]+(?=\s*\(\s*\)\s*\{)/,
1818 ],1928 alias: "function",
1819 // Highlight variable names as variables in for and select beginnings.1929 },
1820 'for-or-select': {1930 ],
1821 pattern: /(\b(?:for|select)\s+)\w+(?=\s+in\s)/,1931 // Highlight variable names as variables in for and select beginnings.
1822 alias: 'variable',1932 "for-or-select": {
1823 lookbehind: true1933 pattern: /(\b(?:for|select)\s+)\w+(?=\s+in\s)/,
1824 },1934 alias: "variable",
1825 // Highlight variable names as variables in the left-hand part1935 lookbehind: true,
1826 // of assignments (“=” and “+=”).1936 },
1827 'assign-left': {1937 // Highlight variable names as variables in the left-hand part
1828 pattern: /(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,1938 // of assignments (“=” and “+=”).
1829 inside: {1939 "assign-left": {
1830 'environment': {1940 pattern: /(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,
1831 pattern: RegExp('(^|[\\s;|&]|[<>]\\()' + envVars),1941 inside: {
1832 lookbehind: true,1942 environment: {
1833 alias: 'constant'1943 pattern: RegExp("(^|[\\s;|&]|[<>]\\()" + envVars),
1834 }1944 lookbehind: true,
1835 },1945 alias: "constant",
1836 alias: 'variable',1946 },
1837 lookbehind: true1947 },
1838 },1948 alias: "variable",
1839 // Highlight parameter names as variables1949 lookbehind: true,
1840 'parameter': {1950 },
1841 pattern: /(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,1951 // Highlight parameter names as variables
1842 alias: 'variable',1952 parameter: {
1843 lookbehind: true1953 pattern: /(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,
1844 },1954 alias: "variable",
1845 'string': [1955 lookbehind: true,
1846 // Support for Here-documents https://en.wikipedia.org/wiki/Here_document1956 },
1847 {1957 string: [
1848 pattern: /((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,1958 // Support for Here-documents https://en.wikipedia.org/wiki/Here_document
1849 lookbehind: true,1959 {
1850 greedy: true,1960 pattern: /((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,
1851 inside: insideString1961 lookbehind: true,
1852 },1962 greedy: true,
1853 // Here-document with quotes around the tag1963 inside: insideString,
1854 // → No expansion (so no “inside”).1964 },
1855 {1965 // Here-document with quotes around the tag
1856 pattern: /((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,1966 // → No expansion (so no “inside”).
1857 lookbehind: true,1967 {
1858 greedy: true,1968 pattern: /((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,
1859 inside: {1969 lookbehind: true,
1860 'bash': commandAfterHeredoc1970 greedy: true,
1861 }1971 inside: {
1862 },1972 bash: commandAfterHeredoc,
1863 // “Normal” string1973 },
1864 {1974 },
1865 // https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html1975 // “Normal” string
1866 pattern: /(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,1976 {
1867 lookbehind: true,1977 // https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html
1868 greedy: true,1978 pattern:
1869 inside: insideString1979 /(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,
1870 },1980 lookbehind: true,
1871 {1981 greedy: true,
1872 // https://www.gnu.org/software/bash/manual/html_node/Single-Quotes.html1982 inside: insideString,
1873 pattern: /(^|[^$\\])'[^']*'/,1983 },
1874 lookbehind: true,1984 {
1875 greedy: true1985 // https://www.gnu.org/software/bash/manual/html_node/Single-Quotes.html
1876 },1986 pattern: /(^|[^$\\])'[^']*'/,
1877 {1987 lookbehind: true,
1878 // https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html1988 greedy: true,
1879 pattern: /\$'(?:[^'\\]|\\[\s\S])*'/,1989 },
1880 greedy: true,1990 {
1881 inside: {1991 // https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
1882 'entity': insideString.entity1992 pattern: /\$'(?:[^'\\]|\\[\s\S])*'/,
1883 }1993 greedy: true,
1884 }1994 inside: {
1885 ],1995 entity: insideString.entity,
1886 'environment': {1996 },
1887 pattern: RegExp('\\$?' + envVars),1997 },
1888 alias: 'constant'1998 ],
1889 },1999 environment: {
1890 'variable': insideString.variable,2000 pattern: RegExp("\\$?" + envVars),
1891 'function': {2001 alias: "constant",
1892 pattern: /(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,2002 },
1893 lookbehind: true2003 variable: insideString.variable,
1894 },2004 function: {
1895 'keyword': {2005 pattern:
1896 pattern: /(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,2006 /(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,
1897 lookbehind: true2007 lookbehind: true,
1898 },2008 },
1899 // https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html2009 keyword: {
1900 'builtin': {2010 pattern:
1901 pattern: /(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,2011 /(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,
1902 lookbehind: true,2012 lookbehind: true,
1903 // Alias added to make those easier to distinguish from strings.2013 },
1904 alias: 'class-name'2014 // https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html
1905 },2015 builtin: {
1906 'boolean': {2016 pattern:
1907 pattern: /(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,2017 /(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,
1908 lookbehind: true2018 lookbehind: true,
1909 },2019 // Alias added to make those easier to distinguish from strings.
1910 'file-descriptor': {2020 alias: "class-name",
1911 pattern: /\B&\d\b/,2021 },
1912 alias: 'important'2022 boolean: {
1913 },2023 pattern: /(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,
1914 'operator': {2024 lookbehind: true,
1915 // Lots of redirections here, but not just that.2025 },
1916 pattern: /\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,2026 "file-descriptor": {
1917 inside: {2027 pattern: /\B&\d\b/,
1918 'file-descriptor': {2028 alias: "important",
1919 pattern: /^\d/,2029 },
1920 alias: 'important'2030 operator: {
1921 }2031 // Lots of redirections here, but not just that.
1922 }2032 pattern:
1923 },2033 /\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,
1924 'punctuation': /\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,2034 inside: {
1925 'number': {2035 "file-descriptor": {
1926 pattern: /(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,2036 pattern: /^\d/,
1927 lookbehind: true2037 alias: "important",
1928 }2038 },
1929 };2039 },
1930 2040 },
1931 commandAfterHeredoc.inside = Prism.languages.bash;2041 punctuation: /\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,
1932 2042 number: {
1933 /* Patterns in command substitution. */2043 pattern: /(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,
1934 var toBeCopied = [2044 lookbehind: true,
1935 'comment',2045 },
1936 'function-name',2046 };
1937 'for-or-select',2047
1938 'assign-left',2048 commandAfterHeredoc.inside = Prism.languages.bash;
1939 'parameter',2049
1940 'string',2050 /* Patterns in command substitution. */
1941 'environment',2051 var toBeCopied = [
1942 'function',2052 "comment",
1943 'keyword',2053 "function-name",
1944 'builtin',2054 "for-or-select",
1945 'boolean',2055 "assign-left",
1946 'file-descriptor',2056 "parameter",
1947 'operator',2057 "string",
1948 'punctuation',2058 "environment",
1949 'number'2059 "function",
1950 ];2060 "keyword",
1951 var inside = insideString.variable[1].inside;2061 "builtin",
1952 for (var i = 0; i < toBeCopied.length; i++) {2062 "boolean",
1953 inside[toBeCopied[i]] = Prism.languages.bash[toBeCopied[i]];2063 "file-descriptor",
1954 }2064 "operator",
1955 2065 "punctuation",
1956 Prism.languages.sh = Prism.languages.bash;2066 "number",
1957 Prism.languages.shell = Prism.languages.bash;2067 ];
1958}(Prism));2068 var inside = insideString.variable[1].inside;
1959 2069 for (var i = 0; i < toBeCopied.length; i++) {
1960Prism.languages.c = Prism.languages.extend('clike', {2070 inside[toBeCopied[i]] = Prism.languages.bash[toBeCopied[i]];
1961 'comment': {2071 }
1962 pattern: /\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,2072
1963 greedy: true2073 Prism.languages.sh = Prism.languages.bash;
1964 },2074 Prism.languages.shell = Prism.languages.bash;
1965 'string': {2075})(Prism);
1966 // https://en.cppreference.com/w/c/language/string_literal2076
1967 pattern: /"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,2077Prism.languages.c = Prism.languages.extend("clike", {
1968 greedy: true2078 comment: {
1969 },2079 pattern:
1970 'class-name': {2080 /\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,
1971 pattern: /(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,2081 greedy: true,
1972 lookbehind: true2082 },
1973 },2083 string: {
1974 'keyword': /\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,2084 // https://en.cppreference.com/w/c/language/string_literal
1975 'function': /\b[a-z_]\w*(?=\s*\()/i,2085 pattern: /"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,
1976 'number': /(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,2086 greedy: true,
1977 'operator': />>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/2087 },
2088 "class-name": {
2089 pattern:
2090 /(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,
2091 lookbehind: true,
2092 },
2093 keyword:
2094 /\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,
2095 function: /\b[a-z_]\w*(?=\s*\()/i,
2096 number:
2097 /(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,
2098 operator: />>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/,
1978});2099});
1979 2100
1980Prism.languages.insertBefore('c', 'string', {2101Prism.languages.insertBefore("c", "string", {
1981 'char': {2102 char: {
1982 // https://en.cppreference.com/w/c/language/character_constant2103 // https://en.cppreference.com/w/c/language/character_constant
1983 pattern: /'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,2104 pattern: /'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,
1984 greedy: true2105 greedy: true,
1985 }2106 },
1986});2107});
1987 2108
1988Prism.languages.insertBefore('c', 'string', {2109Prism.languages.insertBefore("c", "string", {
1989 'macro': {2110 macro: {
1990 // allow for multiline macro definitions2111 // allow for multiline macro definitions
1991 // spaces after the # character compile fine with gcc2112 // spaces after the # character compile fine with gcc
1992 pattern: /(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,2113 pattern:
1993 lookbehind: true,2114 /(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,
1994 greedy: true,2115 lookbehind: true,
1995 alias: 'property',2116 greedy: true,
1996 inside: {2117 alias: "property",
1997 'string': [2118 inside: {
1998 {2119 string: [
1999 // highlight the path of the include statement as a string2120 {
2000 pattern: /^(#\s*include\s*)<[^>]+>/,2121 // highlight the path of the include statement as a string
2001 lookbehind: true2122 pattern: /^(#\s*include\s*)<[^>]+>/,
2002 },2123 lookbehind: true,
2003 Prism.languages.c['string']2124 },
2004 ],2125 Prism.languages.c["string"],
2005 'char': Prism.languages.c['char'],2126 ],
2006 'comment': Prism.languages.c['comment'],2127 char: Prism.languages.c["char"],
2007 'macro-name': [2128 comment: Prism.languages.c["comment"],
2008 {2129 "macro-name": [
2009 pattern: /(^#\s*define\s+)\w+\b(?!\()/i,2130 {
2010 lookbehind: true2131 pattern: /(^#\s*define\s+)\w+\b(?!\()/i,
2011 },2132 lookbehind: true,
2012 {2133 },
2013 pattern: /(^#\s*define\s+)\w+\b(?=\()/i,2134 {
2014 lookbehind: true,2135 pattern: /(^#\s*define\s+)\w+\b(?=\()/i,
2015 alias: 'function'2136 lookbehind: true,
2016 }2137 alias: "function",
2017 ],2138 },
2018 // highlight macro directives as keywords2139 ],
2019 'directive': {2140 // highlight macro directives as keywords
2020 pattern: /^(#\s*)[a-z]+/,2141 directive: {
2021 lookbehind: true,2142 pattern: /^(#\s*)[a-z]+/,
2022 alias: 'keyword'2143 lookbehind: true,
2023 },2144 alias: "keyword",
2024 'directive-hash': /^#/,2145 },
2025 'punctuation': /##|\\(?=[\r\n])/,2146 "directive-hash": /^#/,
2026 'expression': {2147 punctuation: /##|\\(?=[\r\n])/,
2027 pattern: /\S[\s\S]*/,2148 expression: {
2028 inside: Prism.languages.c2149 pattern: /\S[\s\S]*/,
2029 }2150 inside: Prism.languages.c,
2030 }2151 },
2031 }2152 },
2153 },
2032});2154});
2033 2155
2034Prism.languages.insertBefore('c', 'function', {2156Prism.languages.insertBefore("c", "function", {
2035 // highlight predefined macros as constants2157 // highlight predefined macros as constants
2036 'constant': /\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/2158 constant:
2159 /\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/,
2037});2160});
2038 2161
2039delete Prism.languages.c['boolean'];2162delete Prism.languages.c["boolean"];
2040 2163
2041(function (Prism) {2164(function (Prism) {
2042 2165 var keyword =
2043 var keyword = /\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/;2166 /\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/;
2044 var modName = /\b(?!<keyword>)\w+(?:\s*\.\s*\w+)*\b/.source.replace(/<keyword>/g, function () { return keyword.source; });2167 var modName = /\b(?!<keyword>)\w+(?:\s*\.\s*\w+)*\b/.source.replace(
2045 2168 /<keyword>/g,
2046 Prism.languages.cpp = Prism.languages.extend('c', {2169 function () {
2047 'class-name': [2170 return keyword.source;
2048 {2171 },
2049 pattern: RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!<keyword>)\w+/.source2172 );
2050 .replace(/<keyword>/g, function () { return keyword.source; })),2173
2051 lookbehind: true2174 Prism.languages.cpp = Prism.languages.extend("c", {
2052 },2175 "class-name": [
2053 // This is intended to capture the class name of method implementations like:2176 {
2054 // void foo::bar() const {}2177 pattern: RegExp(
2055 // However! The `foo` in the above example could also be a namespace, so we only capture the class name if2178 /(\b(?:class|concept|enum|struct|typename)\s+)(?!<keyword>)\w+/.source.replace(
2056 // it starts with an uppercase letter. This approximation should give decent results.2179 /<keyword>/g,
2057 /\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,2180 function () {
2058 // This will capture the class name before destructors like:2181 return keyword.source;
2059 // Foo::~Foo() {}2182 },
2060 /\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,2183 ),
2061 // This also intends to capture the class name of method implementations but here the class has template2184 ),
2062 // parameters, so it can't be a namespace (until C++ adds generic namespaces).2185 lookbehind: true,
2063 /\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/2186 },
2064 ],2187 // This is intended to capture the class name of method implementations like:
2065 'keyword': keyword,2188 // void foo::bar() const {}
2066 'number': {2189 // However! The `foo` in the above example could also be a namespace, so we only capture the class name if
2067 pattern: /(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,2190 // it starts with an uppercase letter. This approximation should give decent results.
2068 greedy: true2191 /\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,
2069 },2192 // This will capture the class name before destructors like:
2070 'operator': />>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,2193 // Foo::~Foo() {}
2071 'boolean': /\b(?:false|true)\b/2194 /\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,
2072 });2195 // This also intends to capture the class name of method implementations but here the class has template
2073 2196 // parameters, so it can't be a namespace (until C++ adds generic namespaces).
2074 Prism.languages.insertBefore('cpp', 'string', {2197 /\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/,
2075 'module': {2198 ],
2076 // https://en.cppreference.com/w/cpp/language/modules2199 keyword: keyword,
2077 pattern: RegExp(2200 number: {
2078 /(\b(?:import|module)\s+)/.source +2201 pattern:
2079 '(?:' +2202 /(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,
2080 // header-name2203 greedy: true,
2081 /"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source +2204 },
2082 '|' +2205 operator:
2083 // module name or partition or both2206 />>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,
2084 /<mod-name>(?:\s*:\s*<mod-name>)?|:\s*<mod-name>/.source.replace(/<mod-name>/g, function () { return modName; }) +2207 boolean: /\b(?:false|true)\b/,
2085 ')'2208 });
2086 ),2209
2087 lookbehind: true,2210 Prism.languages.insertBefore("cpp", "string", {
2088 greedy: true,2211 module: {
2089 inside: {2212 // https://en.cppreference.com/w/cpp/language/modules
2090 'string': /^[<"][\s\S]+/,2213 pattern: RegExp(
2091 'operator': /:/,2214 /(\b(?:import|module)\s+)/.source +
2092 'punctuation': /\./2215 "(?:" +
2093 }2216 // header-name
2094 },2217 /"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source +
2095 'raw-string': {2218 "|" +
2096 pattern: /R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,2219 // module name or partition or both
2097 alias: 'string',2220 /<mod-name>(?:\s*:\s*<mod-name>)?|:\s*<mod-name>/.source.replace(
2098 greedy: true2221 /<mod-name>/g,
2099 }2222 function () {
2100 });2223 return modName;
2101 2224 },
2102 Prism.languages.insertBefore('cpp', 'keyword', {2225 ) +
2103 'generic-function': {2226 ")",
2104 pattern: /\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,2227 ),
2105 inside: {2228 lookbehind: true,
2106 'function': /^\w+/,2229 greedy: true,
2107 'generic': {2230 inside: {
2108 pattern: /<[\s\S]+/,2231 string: /^[<"][\s\S]+/,
2109 alias: 'class-name',2232 operator: /:/,
2110 inside: Prism.languages.cpp2233 punctuation: /\./,
2111 }2234 },
2112 }2235 },
2113 }2236 "raw-string": {
2114 });2237 pattern: /R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,
2115 2238 alias: "string",
2116 Prism.languages.insertBefore('cpp', 'operator', {2239 greedy: true,
2117 'double-colon': {2240 },
2118 pattern: /::/,2241 });
2119 alias: 'punctuation'2242
2120 }2243 Prism.languages.insertBefore("cpp", "keyword", {
2121 });2244 "generic-function": {
2122 2245 pattern: /\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,
2123 Prism.languages.insertBefore('cpp', 'class-name', {2246 inside: {
2124 // the base clause is an optional list of parent classes2247 function: /^\w+/,
2125 // https://en.cppreference.com/w/cpp/language/class2248 generic: {
2126 'base-clause': {2249 pattern: /<[\s\S]+/,
2127 pattern: /(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,2250 alias: "class-name",
2128 lookbehind: true,2251 inside: Prism.languages.cpp,
2129 greedy: true,2252 },
2130 inside: Prism.languages.extend('cpp', {})2253 },
2131 }2254 },
2132 });2255 });
2133 2256
2134 Prism.languages.insertBefore('inside', 'double-colon', {2257 Prism.languages.insertBefore("cpp", "operator", {
2135 // All untokenized words that are not namespaces should be class names2258 "double-colon": {
2136 'class-name': /\b[a-z_]\w*\b(?!\s*::)/i2259 pattern: /::/,
2137 }, Prism.languages.cpp['base-clause']);2260 alias: "punctuation",
2138 2261 },
2139}(Prism));2262 });
2263
2264 Prism.languages.insertBefore("cpp", "class-name", {
2265 // the base clause is an optional list of parent classes
2266 // https://en.cppreference.com/w/cpp/language/class
2267 "base-clause": {
2268 pattern:
2269 /(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,
2270 lookbehind: true,
2271 greedy: true,
2272 inside: Prism.languages.extend("cpp", {}),
2273 },
2274 });
2275
2276 Prism.languages.insertBefore(
2277 "inside",
2278 "double-colon",
2279 {
2280 // All untokenized words that are not namespaces should be class names
2281 "class-name": /\b[a-z_]\w*\b(?!\s*::)/i,
2282 },
2283 Prism.languages.cpp["base-clause"],
2284 );
2285})(Prism);
2140 2286
2141Prism.languages.cmake = {2287Prism.languages.cmake = {
2142 'comment': /#.*/,2288 comment: /#.*/,
2143 'string': {2289 string: {
2144 pattern: /"(?:[^\\"]|\\.)*"/,2290 pattern: /"(?:[^\\"]|\\.)*"/,
2145 greedy: true,2291 greedy: true,
2146 inside: {2292 inside: {
2147 'interpolation': {2293 interpolation: {
2148 pattern: /\$\{(?:[^{}$]|\$\{[^{}$]*\})*\}/,2294 pattern: /\$\{(?:[^{}$]|\$\{[^{}$]*\})*\}/,
2149 inside: {2295 inside: {
2150 'punctuation': /\$\{|\}/,2296 punctuation: /\$\{|\}/,
2151 'variable': /\w+/2297 variable: /\w+/,
2152 }2298 },
2153 }2299 },
2154 }2300 },
2155 },2301 },
2156 'variable': /\b(?:CMAKE_\w+|\w+_(?:(?:BINARY|SOURCE)_DIR|DESCRIPTION|HOMEPAGE_URL|ROOT|VERSION(?:_MAJOR|_MINOR|_PATCH|_TWEAK)?)|(?:ANDROID|APPLE|BORLAND|BUILD_SHARED_LIBS|CACHE|CPACK_(?:ABSOLUTE_DESTINATION_FILES|COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY|ERROR_ON_ABSOLUTE_INSTALL_DESTINATION|INCLUDE_TOPLEVEL_DIRECTORY|INSTALL_DEFAULT_DIRECTORY_PERMISSIONS|INSTALL_SCRIPT|PACKAGING_INSTALL_PREFIX|SET_DESTDIR|WARN_ON_ABSOLUTE_INSTALL_DESTINATION)|CTEST_(?:BINARY_DIRECTORY|BUILD_COMMAND|BUILD_NAME|BZR_COMMAND|BZR_UPDATE_OPTIONS|CHANGE_ID|CHECKOUT_COMMAND|CONFIGURATION_TYPE|CONFIGURE_COMMAND|COVERAGE_COMMAND|COVERAGE_EXTRA_FLAGS|CURL_OPTIONS|CUSTOM_(?:COVERAGE_EXCLUDE|ERROR_EXCEPTION|ERROR_MATCH|ERROR_POST_CONTEXT|ERROR_PRE_CONTEXT|MAXIMUM_FAILED_TEST_OUTPUT_SIZE|MAXIMUM_NUMBER_OF_(?:ERRORS|WARNINGS)|MAXIMUM_PASSED_TEST_OUTPUT_SIZE|MEMCHECK_IGNORE|POST_MEMCHECK|POST_TEST|PRE_MEMCHECK|PRE_TEST|TESTS_IGNORE|WARNING_EXCEPTION|WARNING_MATCH)|CVS_CHECKOUT|CVS_COMMAND|CVS_UPDATE_OPTIONS|DROP_LOCATION|DROP_METHOD|DROP_SITE|DROP_SITE_CDASH|DROP_SITE_PASSWORD|DROP_SITE_USER|EXTRA_COVERAGE_GLOB|GIT_COMMAND|GIT_INIT_SUBMODULES|GIT_UPDATE_CUSTOM|GIT_UPDATE_OPTIONS|HG_COMMAND|HG_UPDATE_OPTIONS|LABELS_FOR_SUBPROJECTS|MEMORYCHECK_(?:COMMAND|COMMAND_OPTIONS|SANITIZER_OPTIONS|SUPPRESSIONS_FILE|TYPE)|NIGHTLY_START_TIME|P4_CLIENT|P4_COMMAND|P4_OPTIONS|P4_UPDATE_OPTIONS|RUN_CURRENT_SCRIPT|SCP_COMMAND|SITE|SOURCE_DIRECTORY|SUBMIT_URL|SVN_COMMAND|SVN_OPTIONS|SVN_UPDATE_OPTIONS|TEST_LOAD|TEST_TIMEOUT|TRIGGER_SITE|UPDATE_COMMAND|UPDATE_OPTIONS|UPDATE_VERSION_ONLY|USE_LAUNCHERS)|CYGWIN|ENV|EXECUTABLE_OUTPUT_PATH|GHS-MULTI|IOS|LIBRARY_OUTPUT_PATH|MINGW|MSVC(?:10|11|12|14|60|70|71|80|90|_IDE|_TOOLSET_VERSION|_VERSION)?|MSYS|PROJECT_NAME|UNIX|WIN32|WINCE|WINDOWS_PHONE|WINDOWS_STORE|XCODE))\b/,2302 variable:
2157 'property': /\b(?:cxx_\w+|(?:ARCHIVE_OUTPUT_(?:DIRECTORY|NAME)|COMPILE_DEFINITIONS|COMPILE_PDB_NAME|COMPILE_PDB_OUTPUT_DIRECTORY|EXCLUDE_FROM_DEFAULT_BUILD|IMPORTED_(?:IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_LANGUAGES|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|NO_SONAME|OBJECTS|SONAME)|INTERPROCEDURAL_OPTIMIZATION|LIBRARY_OUTPUT_DIRECTORY|LIBRARY_OUTPUT_NAME|LINK_FLAGS|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|MAP_IMPORTED_CONFIG|OSX_ARCHITECTURES|OUTPUT_NAME|PDB_NAME|PDB_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_NAME|STATIC_LIBRARY_FLAGS|VS_CSHARP|VS_DOTNET_REFERENCEPROP|VS_DOTNET_REFERENCE|VS_GLOBAL_SECTION_POST|VS_GLOBAL_SECTION_PRE|VS_GLOBAL|XCODE_ATTRIBUTE)_\w+|\w+_(?:CLANG_TIDY|COMPILER_LAUNCHER|CPPCHECK|CPPLINT|INCLUDE_WHAT_YOU_USE|OUTPUT_NAME|POSTFIX|VISIBILITY_PRESET)|ABSTRACT|ADDITIONAL_MAKE_CLEAN_FILES|ADVANCED|ALIASED_TARGET|ALLOW_DUPLICATE_CUSTOM_TARGETS|ANDROID_(?:ANT_ADDITIONAL_OPTIONS|API|API_MIN|ARCH|ASSETS_DIRECTORIES|GUI|JAR_DEPENDENCIES|NATIVE_LIB_DEPENDENCIES|NATIVE_LIB_DIRECTORIES|PROCESS_MAX|PROGUARD|PROGUARD_CONFIG_PATH|SECURE_PROPS_PATH|SKIP_ANT_STEP|STL_TYPE)|ARCHIVE_OUTPUT_DIRECTORY|ATTACHED_FILES|ATTACHED_FILES_ON_FAIL|AUTOGEN_(?:BUILD_DIR|ORIGIN_DEPENDS|PARALLEL|SOURCE_GROUP|TARGETS_FOLDER|TARGET_DEPENDS)|AUTOMOC|AUTOMOC_(?:COMPILER_PREDEFINES|DEPEND_FILTERS|EXECUTABLE|MACRO_NAMES|MOC_OPTIONS|SOURCE_GROUP|TARGETS_FOLDER)|AUTORCC|AUTORCC_EXECUTABLE|AUTORCC_OPTIONS|AUTORCC_SOURCE_GROUP|AUTOUIC|AUTOUIC_EXECUTABLE|AUTOUIC_OPTIONS|AUTOUIC_SEARCH_PATHS|BINARY_DIR|BUILDSYSTEM_TARGETS|BUILD_RPATH|BUILD_RPATH_USE_ORIGIN|BUILD_WITH_INSTALL_NAME_DIR|BUILD_WITH_INSTALL_RPATH|BUNDLE|BUNDLE_EXTENSION|CACHE_VARIABLES|CLEAN_NO_CUSTOM|COMMON_LANGUAGE_RUNTIME|COMPATIBLE_INTERFACE_(?:BOOL|NUMBER_MAX|NUMBER_MIN|STRING)|COMPILE_(?:DEFINITIONS|FEATURES|FLAGS|OPTIONS|PDB_NAME|PDB_OUTPUT_DIRECTORY)|COST|CPACK_DESKTOP_SHORTCUTS|CPACK_NEVER_OVERWRITE|CPACK_PERMANENT|CPACK_STARTUP_SHORTCUTS|CPACK_START_MENU_SHORTCUTS|CPACK_WIX_ACL|CROSSCOMPILING_EMULATOR|CUDA_EXTENSIONS|CUDA_PTX_COMPILATION|CUDA_RESOLVE_DEVICE_SYMBOLS|CUDA_SEPARABLE_COMPILATION|CUDA_STANDARD|CUDA_STANDARD_REQUIRED|CXX_EXTENSIONS|CXX_STANDARD|CXX_STANDARD_REQUIRED|C_EXTENSIONS|C_STANDARD|C_STANDARD_REQUIRED|DEBUG_CONFIGURATIONS|DEFINE_SYMBOL|DEFINITIONS|DEPENDS|DEPLOYMENT_ADDITIONAL_FILES|DEPLOYMENT_REMOTE_DIRECTORY|DISABLED|DISABLED_FEATURES|ECLIPSE_EXTRA_CPROJECT_CONTENTS|ECLIPSE_EXTRA_NATURES|ENABLED_FEATURES|ENABLED_LANGUAGES|ENABLE_EXPORTS|ENVIRONMENT|EXCLUDE_FROM_ALL|EXCLUDE_FROM_DEFAULT_BUILD|EXPORT_NAME|EXPORT_PROPERTIES|EXTERNAL_OBJECT|EchoString|FAIL_REGULAR_EXPRESSION|FIND_LIBRARY_USE_LIB32_PATHS|FIND_LIBRARY_USE_LIB64_PATHS|FIND_LIBRARY_USE_LIBX32_PATHS|FIND_LIBRARY_USE_OPENBSD_VERSIONING|FIXTURES_CLEANUP|FIXTURES_REQUIRED|FIXTURES_SETUP|FOLDER|FRAMEWORK|Fortran_FORMAT|Fortran_MODULE_DIRECTORY|GENERATED|GENERATOR_FILE_NAME|GENERATOR_IS_MULTI_CONFIG|GHS_INTEGRITY_APP|GHS_NO_SOURCE_GROUP_FILE|GLOBAL_DEPENDS_DEBUG_MODE|GLOBAL_DEPENDS_NO_CYCLES|GNUtoMS|HAS_CXX|HEADER_FILE_ONLY|HELPSTRING|IMPLICIT_DEPENDS_INCLUDE_TRANSFORM|IMPORTED|IMPORTED_(?:COMMON_LANGUAGE_RUNTIME|CONFIGURATIONS|GLOBAL|IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_(?:LANGUAGES|LIBRARIES|MULTIPLICITY)|LOCATION|NO_SONAME|OBJECTS|SONAME)|IMPORT_PREFIX|IMPORT_SUFFIX|INCLUDE_DIRECTORIES|INCLUDE_REGULAR_EXPRESSION|INSTALL_NAME_DIR|INSTALL_RPATH|INSTALL_RPATH_USE_LINK_PATH|INTERFACE_(?:AUTOUIC_OPTIONS|COMPILE_DEFINITIONS|COMPILE_FEATURES|COMPILE_OPTIONS|INCLUDE_DIRECTORIES|LINK_DEPENDS|LINK_DIRECTORIES|LINK_LIBRARIES|LINK_OPTIONS|POSITION_INDEPENDENT_CODE|SOURCES|SYSTEM_INCLUDE_DIRECTORIES)|INTERPROCEDURAL_OPTIMIZATION|IN_TRY_COMPILE|IOS_INSTALL_COMBINED|JOB_POOLS|JOB_POOL_COMPILE|JOB_POOL_LINK|KEEP_EXTENSION|LABELS|LANGUAGE|LIBRARY_OUTPUT_DIRECTORY|LINKER_LANGUAGE|LINK_(?:DEPENDS|DEPENDS_NO_SHARED|DIRECTORIES|FLAGS|INTERFACE_LIBRARIES|INTERFACE_MULTIPLICITY|LIBRARIES|OPTIONS|SEARCH_END_STATIC|SEARCH_START_STATIC|WHAT_YOU_USE)|LISTFILE_STACK|LOCATION|MACOSX_BUNDLE|MACOSX_BUNDLE_INFO_PLIST|MACOSX_FRAMEWORK_INFO_PLIST|MACOSX_PACKAGE_LOCATION|MACOSX_RPATH|MACROS|MANUALLY_ADDED_DEPENDENCIES|MEASUREMENT|MODIFIED|NAME|NO_SONAME|NO_SYSTEM_FROM_IMPORTED|OBJECT_DEPENDS|OBJECT_OUTPUTS|OSX_ARCHITECTURES|OUTPUT_NAME|PACKAGES_FOUND|PACKAGES_NOT_FOUND|PARENT_DIRECTORY|PASS_REGULAR_EXPRESSION|PDB_NAME|PDB_OUTPUT_DIRECTORY|POSITION_INDEPENDENT_CODE|POST_INSTALL_SCRIPT|PREDEFINED_TARGETS_FOLDER|PREFIX|PRE_INSTALL_SCRIPT|PRIVATE_HEADER|PROCESSORS|PROCESSOR_AFFINITY|PROJECT_LABEL|PUBLIC_HEADER|REPORT_UNDEFINED_PROPERTIES|REQUIRED_FILES|RESOURCE|RESOURCE_LOCK|RULE_LAUNCH_COMPILE|RULE_LAUNCH_CUSTOM|RULE_LAUNCH_LINK|RULE_MESSAGES|RUNTIME_OUTPUT_DIRECTORY|RUN_SERIAL|SKIP_AUTOGEN|SKIP_AUTOMOC|SKIP_AUTORCC|SKIP_AUTOUIC|SKIP_BUILD_RPATH|SKIP_RETURN_CODE|SOURCES|SOURCE_DIR|SOVERSION|STATIC_LIBRARY_FLAGS|STATIC_LIBRARY_OPTIONS|STRINGS|SUBDIRECTORIES|SUFFIX|SYMBOLIC|TARGET_ARCHIVES_MAY_BE_SHARED_LIBS|TARGET_MESSAGES|TARGET_SUPPORTS_SHARED_LIBS|TESTS|TEST_INCLUDE_FILE|TEST_INCLUDE_FILES|TIMEOUT|TIMEOUT_AFTER_MATCH|TYPE|USE_FOLDERS|VALUE|VARIABLES|VERSION|VISIBILITY_INLINES_HIDDEN|VS_(?:CONFIGURATION_TYPE|COPY_TO_OUT_DIR|DEBUGGER_(?:COMMAND|COMMAND_ARGUMENTS|ENVIRONMENT|WORKING_DIRECTORY)|DEPLOYMENT_CONTENT|DEPLOYMENT_LOCATION|DOTNET_REFERENCES|DOTNET_REFERENCES_COPY_LOCAL|INCLUDE_IN_VSIX|IOT_STARTUP_TASK|KEYWORD|RESOURCE_GENERATOR|SCC_AUXPATH|SCC_LOCALPATH|SCC_PROJECTNAME|SCC_PROVIDER|SDK_REFERENCES|SHADER_(?:DISABLE_OPTIMIZATIONS|ENABLE_DEBUG|ENTRYPOINT|FLAGS|MODEL|OBJECT_FILE_NAME|OUTPUT_HEADER_FILE|TYPE|VARIABLE_NAME)|STARTUP_PROJECT|TOOL_OVERRIDE|USER_PROPS|WINRT_COMPONENT|WINRT_EXTENSIONS|WINRT_REFERENCES|XAML_TYPE)|WILL_FAIL|WIN32_EXECUTABLE|WINDOWS_EXPORT_ALL_SYMBOLS|WORKING_DIRECTORY|WRAP_EXCLUDE|XCODE_(?:EMIT_EFFECTIVE_PLATFORM_NAME|EXPLICIT_FILE_TYPE|FILE_ATTRIBUTES|LAST_KNOWN_FILE_TYPE|PRODUCT_TYPE|SCHEME_(?:ADDRESS_SANITIZER|ADDRESS_SANITIZER_USE_AFTER_RETURN|ARGUMENTS|DISABLE_MAIN_THREAD_CHECKER|DYNAMIC_LIBRARY_LOADS|DYNAMIC_LINKER_API_USAGE|ENVIRONMENT|EXECUTABLE|GUARD_MALLOC|MAIN_THREAD_CHECKER_STOP|MALLOC_GUARD_EDGES|MALLOC_SCRIBBLE|MALLOC_STACK|THREAD_SANITIZER(?:_STOP)?|UNDEFINED_BEHAVIOUR_SANITIZER(?:_STOP)?|ZOMBIE_OBJECTS))|XCTEST)\b/,2303 /\b(?:CMAKE_\w+|\w+_(?:(?:BINARY|SOURCE)_DIR|DESCRIPTION|HOMEPAGE_URL|ROOT|VERSION(?:_MAJOR|_MINOR|_PATCH|_TWEAK)?)|(?:ANDROID|APPLE|BORLAND|BUILD_SHARED_LIBS|CACHE|CPACK_(?:ABSOLUTE_DESTINATION_FILES|COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY|ERROR_ON_ABSOLUTE_INSTALL_DESTINATION|INCLUDE_TOPLEVEL_DIRECTORY|INSTALL_DEFAULT_DIRECTORY_PERMISSIONS|INSTALL_SCRIPT|PACKAGING_INSTALL_PREFIX|SET_DESTDIR|WARN_ON_ABSOLUTE_INSTALL_DESTINATION)|CTEST_(?:BINARY_DIRECTORY|BUILD_COMMAND|BUILD_NAME|BZR_COMMAND|BZR_UPDATE_OPTIONS|CHANGE_ID|CHECKOUT_COMMAND|CONFIGURATION_TYPE|CONFIGURE_COMMAND|COVERAGE_COMMAND|COVERAGE_EXTRA_FLAGS|CURL_OPTIONS|CUSTOM_(?:COVERAGE_EXCLUDE|ERROR_EXCEPTION|ERROR_MATCH|ERROR_POST_CONTEXT|ERROR_PRE_CONTEXT|MAXIMUM_FAILED_TEST_OUTPUT_SIZE|MAXIMUM_NUMBER_OF_(?:ERRORS|WARNINGS)|MAXIMUM_PASSED_TEST_OUTPUT_SIZE|MEMCHECK_IGNORE|POST_MEMCHECK|POST_TEST|PRE_MEMCHECK|PRE_TEST|TESTS_IGNORE|WARNING_EXCEPTION|WARNING_MATCH)|CVS_CHECKOUT|CVS_COMMAND|CVS_UPDATE_OPTIONS|DROP_LOCATION|DROP_METHOD|DROP_SITE|DROP_SITE_CDASH|DROP_SITE_PASSWORD|DROP_SITE_USER|EXTRA_COVERAGE_GLOB|GIT_COMMAND|GIT_INIT_SUBMODULES|GIT_UPDATE_CUSTOM|GIT_UPDATE_OPTIONS|HG_COMMAND|HG_UPDATE_OPTIONS|LABELS_FOR_SUBPROJECTS|MEMORYCHECK_(?:COMMAND|COMMAND_OPTIONS|SANITIZER_OPTIONS|SUPPRESSIONS_FILE|TYPE)|NIGHTLY_START_TIME|P4_CLIENT|P4_COMMAND|P4_OPTIONS|P4_UPDATE_OPTIONS|RUN_CURRENT_SCRIPT|SCP_COMMAND|SITE|SOURCE_DIRECTORY|SUBMIT_URL|SVN_COMMAND|SVN_OPTIONS|SVN_UPDATE_OPTIONS|TEST_LOAD|TEST_TIMEOUT|TRIGGER_SITE|UPDATE_COMMAND|UPDATE_OPTIONS|UPDATE_VERSION_ONLY|USE_LAUNCHERS)|CYGWIN|ENV|EXECUTABLE_OUTPUT_PATH|GHS-MULTI|IOS|LIBRARY_OUTPUT_PATH|MINGW|MSVC(?:10|11|12|14|60|70|71|80|90|_IDE|_TOOLSET_VERSION|_VERSION)?|MSYS|PROJECT_NAME|UNIX|WIN32|WINCE|WINDOWS_PHONE|WINDOWS_STORE|XCODE))\b/,
2158 'keyword': /\b(?:add_compile_definitions|add_compile_options|add_custom_command|add_custom_target|add_definitions|add_dependencies|add_executable|add_library|add_link_options|add_subdirectory|add_test|aux_source_directory|break|build_command|build_name|cmake_host_system_information|cmake_minimum_required|cmake_parse_arguments|cmake_policy|configure_file|continue|create_test_sourcelist|ctest_build|ctest_configure|ctest_coverage|ctest_empty_binary_directory|ctest_memcheck|ctest_read_custom_files|ctest_run_script|ctest_sleep|ctest_start|ctest_submit|ctest_test|ctest_update|ctest_upload|define_property|else|elseif|enable_language|enable_testing|endforeach|endfunction|endif|endmacro|endwhile|exec_program|execute_process|export|export_library_dependencies|file|find_file|find_library|find_package|find_path|find_program|fltk_wrap_ui|foreach|function|get_cmake_property|get_directory_property|get_filename_component|get_property|get_source_file_property|get_target_property|get_test_property|if|include|include_directories|include_external_msproject|include_guard|include_regular_expression|install|install_files|install_programs|install_targets|link_directories|link_libraries|list|load_cache|load_command|macro|make_directory|mark_as_advanced|math|message|option|output_required_files|project|qt_wrap_cpp|qt_wrap_ui|remove|remove_definitions|return|separate_arguments|set|set_directory_properties|set_property|set_source_files_properties|set_target_properties|set_tests_properties|site_name|source_group|string|subdir_depends|subdirs|target_compile_definitions|target_compile_features|target_compile_options|target_include_directories|target_link_directories|target_link_libraries|target_link_options|target_sources|try_compile|try_run|unset|use_mangled_mesa|utility_source|variable_requires|variable_watch|while|write_file)(?=\s*\()\b/,2304 property:
2159 'boolean': /\b(?:FALSE|OFF|ON|TRUE)\b/,2305 /\b(?:cxx_\w+|(?:ARCHIVE_OUTPUT_(?:DIRECTORY|NAME)|COMPILE_DEFINITIONS|COMPILE_PDB_NAME|COMPILE_PDB_OUTPUT_DIRECTORY|EXCLUDE_FROM_DEFAULT_BUILD|IMPORTED_(?:IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_LANGUAGES|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|NO_SONAME|OBJECTS|SONAME)|INTERPROCEDURAL_OPTIMIZATION|LIBRARY_OUTPUT_DIRECTORY|LIBRARY_OUTPUT_NAME|LINK_FLAGS|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|MAP_IMPORTED_CONFIG|OSX_ARCHITECTURES|OUTPUT_NAME|PDB_NAME|PDB_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_NAME|STATIC_LIBRARY_FLAGS|VS_CSHARP|VS_DOTNET_REFERENCEPROP|VS_DOTNET_REFERENCE|VS_GLOBAL_SECTION_POST|VS_GLOBAL_SECTION_PRE|VS_GLOBAL|XCODE_ATTRIBUTE)_\w+|\w+_(?:CLANG_TIDY|COMPILER_LAUNCHER|CPPCHECK|CPPLINT|INCLUDE_WHAT_YOU_USE|OUTPUT_NAME|POSTFIX|VISIBILITY_PRESET)|ABSTRACT|ADDITIONAL_MAKE_CLEAN_FILES|ADVANCED|ALIASED_TARGET|ALLOW_DUPLICATE_CUSTOM_TARGETS|ANDROID_(?:ANT_ADDITIONAL_OPTIONS|API|API_MIN|ARCH|ASSETS_DIRECTORIES|GUI|JAR_DEPENDENCIES|NATIVE_LIB_DEPENDENCIES|NATIVE_LIB_DIRECTORIES|PROCESS_MAX|PROGUARD|PROGUARD_CONFIG_PATH|SECURE_PROPS_PATH|SKIP_ANT_STEP|STL_TYPE)|ARCHIVE_OUTPUT_DIRECTORY|ATTACHED_FILES|ATTACHED_FILES_ON_FAIL|AUTOGEN_(?:BUILD_DIR|ORIGIN_DEPENDS|PARALLEL|SOURCE_GROUP|TARGETS_FOLDER|TARGET_DEPENDS)|AUTOMOC|AUTOMOC_(?:COMPILER_PREDEFINES|DEPEND_FILTERS|EXECUTABLE|MACRO_NAMES|MOC_OPTIONS|SOURCE_GROUP|TARGETS_FOLDER)|AUTORCC|AUTORCC_EXECUTABLE|AUTORCC_OPTIONS|AUTORCC_SOURCE_GROUP|AUTOUIC|AUTOUIC_EXECUTABLE|AUTOUIC_OPTIONS|AUTOUIC_SEARCH_PATHS|BINARY_DIR|BUILDSYSTEM_TARGETS|BUILD_RPATH|BUILD_RPATH_USE_ORIGIN|BUILD_WITH_INSTALL_NAME_DIR|BUILD_WITH_INSTALL_RPATH|BUNDLE|BUNDLE_EXTENSION|CACHE_VARIABLES|CLEAN_NO_CUSTOM|COMMON_LANGUAGE_RUNTIME|COMPATIBLE_INTERFACE_(?:BOOL|NUMBER_MAX|NUMBER_MIN|STRING)|COMPILE_(?:DEFINITIONS|FEATURES|FLAGS|OPTIONS|PDB_NAME|PDB_OUTPUT_DIRECTORY)|COST|CPACK_DESKTOP_SHORTCUTS|CPACK_NEVER_OVERWRITE|CPACK_PERMANENT|CPACK_STARTUP_SHORTCUTS|CPACK_START_MENU_SHORTCUTS|CPACK_WIX_ACL|CROSSCOMPILING_EMULATOR|CUDA_EXTENSIONS|CUDA_PTX_COMPILATION|CUDA_RESOLVE_DEVICE_SYMBOLS|CUDA_SEPARABLE_COMPILATION|CUDA_STANDARD|CUDA_STANDARD_REQUIRED|CXX_EXTENSIONS|CXX_STANDARD|CXX_STANDARD_REQUIRED|C_EXTENSIONS|C_STANDARD|C_STANDARD_REQUIRED|DEBUG_CONFIGURATIONS|DEFINE_SYMBOL|DEFINITIONS|DEPENDS|DEPLOYMENT_ADDITIONAL_FILES|DEPLOYMENT_REMOTE_DIRECTORY|DISABLED|DISABLED_FEATURES|ECLIPSE_EXTRA_CPROJECT_CONTENTS|ECLIPSE_EXTRA_NATURES|ENABLED_FEATURES|ENABLED_LANGUAGES|ENABLE_EXPORTS|ENVIRONMENT|EXCLUDE_FROM_ALL|EXCLUDE_FROM_DEFAULT_BUILD|EXPORT_NAME|EXPORT_PROPERTIES|EXTERNAL_OBJECT|EchoString|FAIL_REGULAR_EXPRESSION|FIND_LIBRARY_USE_LIB32_PATHS|FIND_LIBRARY_USE_LIB64_PATHS|FIND_LIBRARY_USE_LIBX32_PATHS|FIND_LIBRARY_USE_OPENBSD_VERSIONING|FIXTURES_CLEANUP|FIXTURES_REQUIRED|FIXTURES_SETUP|FOLDER|FRAMEWORK|Fortran_FORMAT|Fortran_MODULE_DIRECTORY|GENERATED|GENERATOR_FILE_NAME|GENERATOR_IS_MULTI_CONFIG|GHS_INTEGRITY_APP|GHS_NO_SOURCE_GROUP_FILE|GLOBAL_DEPENDS_DEBUG_MODE|GLOBAL_DEPENDS_NO_CYCLES|GNUtoMS|HAS_CXX|HEADER_FILE_ONLY|HELPSTRING|IMPLICIT_DEPENDS_INCLUDE_TRANSFORM|IMPORTED|IMPORTED_(?:COMMON_LANGUAGE_RUNTIME|CONFIGURATIONS|GLOBAL|IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_(?:LANGUAGES|LIBRARIES|MULTIPLICITY)|LOCATION|NO_SONAME|OBJECTS|SONAME)|IMPORT_PREFIX|IMPORT_SUFFIX|INCLUDE_DIRECTORIES|INCLUDE_REGULAR_EXPRESSION|INSTALL_NAME_DIR|INSTALL_RPATH|INSTALL_RPATH_USE_LINK_PATH|INTERFACE_(?:AUTOUIC_OPTIONS|COMPILE_DEFINITIONS|COMPILE_FEATURES|COMPILE_OPTIONS|INCLUDE_DIRECTORIES|LINK_DEPENDS|LINK_DIRECTORIES|LINK_LIBRARIES|LINK_OPTIONS|POSITION_INDEPENDENT_CODE|SOURCES|SYSTEM_INCLUDE_DIRECTORIES)|INTERPROCEDURAL_OPTIMIZATION|IN_TRY_COMPILE|IOS_INSTALL_COMBINED|JOB_POOLS|JOB_POOL_COMPILE|JOB_POOL_LINK|KEEP_EXTENSION|LABELS|LANGUAGE|LIBRARY_OUTPUT_DIRECTORY|LINKER_LANGUAGE|LINK_(?:DEPENDS|DEPENDS_NO_SHARED|DIRECTORIES|FLAGS|INTERFACE_LIBRARIES|INTERFACE_MULTIPLICITY|LIBRARIES|OPTIONS|SEARCH_END_STATIC|SEARCH_START_STATIC|WHAT_YOU_USE)|LISTFILE_STACK|LOCATION|MACOSX_BUNDLE|MACOSX_BUNDLE_INFO_PLIST|MACOSX_FRAMEWORK_INFO_PLIST|MACOSX_PACKAGE_LOCATION|MACOSX_RPATH|MACROS|MANUALLY_ADDED_DEPENDENCIES|MEASUREMENT|MODIFIED|NAME|NO_SONAME|NO_SYSTEM_FROM_IMPORTED|OBJECT_DEPENDS|OBJECT_OUTPUTS|OSX_ARCHITECTURES|OUTPUT_NAME|PACKAGES_FOUND|PACKAGES_NOT_FOUND|PARENT_DIRECTORY|PASS_REGULAR_EXPRESSION|PDB_NAME|PDB_OUTPUT_DIRECTORY|POSITION_INDEPENDENT_CODE|POST_INSTALL_SCRIPT|PREDEFINED_TARGETS_FOLDER|PREFIX|PRE_INSTALL_SCRIPT|PRIVATE_HEADER|PROCESSORS|PROCESSOR_AFFINITY|PROJECT_LABEL|PUBLIC_HEADER|REPORT_UNDEFINED_PROPERTIES|REQUIRED_FILES|RESOURCE|RESOURCE_LOCK|RULE_LAUNCH_COMPILE|RULE_LAUNCH_CUSTOM|RULE_LAUNCH_LINK|RULE_MESSAGES|RUNTIME_OUTPUT_DIRECTORY|RUN_SERIAL|SKIP_AUTOGEN|SKIP_AUTOMOC|SKIP_AUTORCC|SKIP_AUTOUIC|SKIP_BUILD_RPATH|SKIP_RETURN_CODE|SOURCES|SOURCE_DIR|SOVERSION|STATIC_LIBRARY_FLAGS|STATIC_LIBRARY_OPTIONS|STRINGS|SUBDIRECTORIES|SUFFIX|SYMBOLIC|TARGET_ARCHIVES_MAY_BE_SHARED_LIBS|TARGET_MESSAGES|TARGET_SUPPORTS_SHARED_LIBS|TESTS|TEST_INCLUDE_FILE|TEST_INCLUDE_FILES|TIMEOUT|TIMEOUT_AFTER_MATCH|TYPE|USE_FOLDERS|VALUE|VARIABLES|VERSION|VISIBILITY_INLINES_HIDDEN|VS_(?:CONFIGURATION_TYPE|COPY_TO_OUT_DIR|DEBUGGER_(?:COMMAND|COMMAND_ARGUMENTS|ENVIRONMENT|WORKING_DIRECTORY)|DEPLOYMENT_CONTENT|DEPLOYMENT_LOCATION|DOTNET_REFERENCES|DOTNET_REFERENCES_COPY_LOCAL|INCLUDE_IN_VSIX|IOT_STARTUP_TASK|KEYWORD|RESOURCE_GENERATOR|SCC_AUXPATH|SCC_LOCALPATH|SCC_PROJECTNAME|SCC_PROVIDER|SDK_REFERENCES|SHADER_(?:DISABLE_OPTIMIZATIONS|ENABLE_DEBUG|ENTRYPOINT|FLAGS|MODEL|OBJECT_FILE_NAME|OUTPUT_HEADER_FILE|TYPE|VARIABLE_NAME)|STARTUP_PROJECT|TOOL_OVERRIDE|USER_PROPS|WINRT_COMPONENT|WINRT_EXTENSIONS|WINRT_REFERENCES|XAML_TYPE)|WILL_FAIL|WIN32_EXECUTABLE|WINDOWS_EXPORT_ALL_SYMBOLS|WORKING_DIRECTORY|WRAP_EXCLUDE|XCODE_(?:EMIT_EFFECTIVE_PLATFORM_NAME|EXPLICIT_FILE_TYPE|FILE_ATTRIBUTES|LAST_KNOWN_FILE_TYPE|PRODUCT_TYPE|SCHEME_(?:ADDRESS_SANITIZER|ADDRESS_SANITIZER_USE_AFTER_RETURN|ARGUMENTS|DISABLE_MAIN_THREAD_CHECKER|DYNAMIC_LIBRARY_LOADS|DYNAMIC_LINKER_API_USAGE|ENVIRONMENT|EXECUTABLE|GUARD_MALLOC|MAIN_THREAD_CHECKER_STOP|MALLOC_GUARD_EDGES|MALLOC_SCRIBBLE|MALLOC_STACK|THREAD_SANITIZER(?:_STOP)?|UNDEFINED_BEHAVIOUR_SANITIZER(?:_STOP)?|ZOMBIE_OBJECTS))|XCTEST)\b/,
2160 'namespace': /\b(?:INTERFACE|PRIVATE|PROPERTIES|PUBLIC|SHARED|STATIC|TARGET_OBJECTS)\b/,2306 keyword:
2161 'operator': /\b(?:AND|DEFINED|EQUAL|GREATER|LESS|MATCHES|NOT|OR|STREQUAL|STRGREATER|STRLESS|VERSION_EQUAL|VERSION_GREATER|VERSION_LESS)\b/,2307 /\b(?:add_compile_definitions|add_compile_options|add_custom_command|add_custom_target|add_definitions|add_dependencies|add_executable|add_library|add_link_options|add_subdirectory|add_test|aux_source_directory|break|build_command|build_name|cmake_host_system_information|cmake_minimum_required|cmake_parse_arguments|cmake_policy|configure_file|continue|create_test_sourcelist|ctest_build|ctest_configure|ctest_coverage|ctest_empty_binary_directory|ctest_memcheck|ctest_read_custom_files|ctest_run_script|ctest_sleep|ctest_start|ctest_submit|ctest_test|ctest_update|ctest_upload|define_property|else|elseif|enable_language|enable_testing|endforeach|endfunction|endif|endmacro|endwhile|exec_program|execute_process|export|export_library_dependencies|file|find_file|find_library|find_package|find_path|find_program|fltk_wrap_ui|foreach|function|get_cmake_property|get_directory_property|get_filename_component|get_property|get_source_file_property|get_target_property|get_test_property|if|include|include_directories|include_external_msproject|include_guard|include_regular_expression|install|install_files|install_programs|install_targets|link_directories|link_libraries|list|load_cache|load_command|macro|make_directory|mark_as_advanced|math|message|option|output_required_files|project|qt_wrap_cpp|qt_wrap_ui|remove|remove_definitions|return|separate_arguments|set|set_directory_properties|set_property|set_source_files_properties|set_target_properties|set_tests_properties|site_name|source_group|string|subdir_depends|subdirs|target_compile_definitions|target_compile_features|target_compile_options|target_include_directories|target_link_directories|target_link_libraries|target_link_options|target_sources|try_compile|try_run|unset|use_mangled_mesa|utility_source|variable_requires|variable_watch|while|write_file)(?=\s*\()\b/,
2162 'inserted': {2308 boolean: /\b(?:FALSE|OFF|ON|TRUE)\b/,
2163 pattern: /\b\w+::\w+\b/,2309 namespace:
2164 alias: 'class-name'2310 /\b(?:INTERFACE|PRIVATE|PROPERTIES|PUBLIC|SHARED|STATIC|TARGET_OBJECTS)\b/,
2165 },2311 operator:
2166 'number': /\b\d+(?:\.\d+)*\b/,2312 /\b(?:AND|DEFINED|EQUAL|GREATER|LESS|MATCHES|NOT|OR|STREQUAL|STRGREATER|STRLESS|VERSION_EQUAL|VERSION_GREATER|VERSION_LESS)\b/,
2167 'function': /\b[a-z_]\w*(?=\s*\()\b/i,2313 inserted: {
2168 'punctuation': /[()>}]|\$[<{]/2314 pattern: /\b\w+::\w+\b/,
2315 alias: "class-name",
2316 },
2317 number: /\b\d+(?:\.\d+)*\b/,
2318 function: /\b[a-z_]\w*(?=\s*\()\b/i,
2319 punctuation: /[()>}]|\$[<{]/,
2169};2320};
2170 2321
2171(function (Prism) {2322(function (Prism) {
2172 2323 // Ignore comments starting with { to privilege string interpolation highlighting
2173 // Ignore comments starting with { to privilege string interpolation highlighting2324 var comment = /#(?!\{).+/;
2174 var comment = /#(?!\{).+/;2325 var interpolation = {
2175 var interpolation = {2326 pattern: /#\{[^}]+\}/,
2176 pattern: /#\{[^}]+\}/,2327 alias: "variable",
2177 alias: 'variable'2328 };
2178 };2329
2179 2330 Prism.languages.coffeescript = Prism.languages.extend("javascript", {
2180 Prism.languages.coffeescript = Prism.languages.extend('javascript', {2331 comment: comment,
2181 'comment': comment,2332 string: [
2182 'string': [2333 // Strings are multiline
2183 2334 {
2184 // Strings are multiline2335 pattern: /'(?:\\[\s\S]|[^\\'])*'/,
2185 {2336 greedy: true,
2186 pattern: /'(?:\\[\s\S]|[^\\'])*'/,2337 },
2187 greedy: true2338
2188 },2339 {
2189 2340 // Strings are multiline
2190 {2341 pattern: /"(?:\\[\s\S]|[^\\"])*"/,
2191 // Strings are multiline2342 greedy: true,
2192 pattern: /"(?:\\[\s\S]|[^\\"])*"/,2343 inside: {
2193 greedy: true,2344 interpolation: interpolation,
2194 inside: {2345 },
2195 'interpolation': interpolation2346 },
2196 }2347 ],
2197 }2348 keyword:
2198 ],2349 /\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/,
2199 '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/,2350 "class-member": {
2200 'class-member': {2351 pattern: /@(?!\d)\w+/,
2201 pattern: /@(?!\d)\w+/,2352 alias: "variable",
2202 alias: 'variable'2353 },
2203 }2354 });
2204 });2355
2205 2356 Prism.languages.insertBefore("coffeescript", "comment", {
2206 Prism.languages.insertBefore('coffeescript', 'comment', {2357 "multiline-comment": {
2207 'multiline-comment': {2358 pattern: /###[\s\S]+?###/,
2208 pattern: /###[\s\S]+?###/,2359 alias: "comment",
2209 alias: 'comment'2360 },
2210 },2361
2211 2362 // Block regexp can contain comments and interpolation
2212 // Block regexp can contain comments and interpolation2363 "block-regex": {
2213 'block-regex': {2364 pattern: /\/{3}[\s\S]*?\/{3}/,
2214 pattern: /\/{3}[\s\S]*?\/{3}/,2365 alias: "regex",
2215 alias: 'regex',2366 inside: {
2216 inside: {2367 comment: comment,
2217 'comment': comment,2368 interpolation: interpolation,
2218 'interpolation': interpolation2369 },
2219 }2370 },
2220 }2371 });
2221 });2372
2222 2373 Prism.languages.insertBefore("coffeescript", "string", {
2223 Prism.languages.insertBefore('coffeescript', 'string', {2374 "inline-javascript": {
2224 'inline-javascript': {2375 pattern: /`(?:\\[\s\S]|[^\\`])*`/,
2225 pattern: /`(?:\\[\s\S]|[^\\`])*`/,2376 inside: {
2226 inside: {2377 delimiter: {
2227 'delimiter': {2378 pattern: /^`|`$/,
2228 pattern: /^`|`$/,2379 alias: "punctuation",
2229 alias: 'punctuation'2380 },
2230 },2381 script: {
2231 'script': {2382 pattern: /[\s\S]+/,
2232 pattern: /[\s\S]+/,2383 alias: "language-javascript",
2233 alias: 'language-javascript',2384 inside: Prism.languages.javascript,
2234 inside: Prism.languages.javascript2385 },
2235 }2386 },
2236 }2387 },
2237 },2388
2238 2389 // Block strings
2239 // Block strings2390 "multiline-string": [
2240 'multiline-string': [2391 {
2241 {2392 pattern: /'''[\s\S]*?'''/,
2242 pattern: /'''[\s\S]*?'''/,2393 greedy: true,
2243 greedy: true,2394 alias: "string",
2244 alias: 'string'2395 },
2245 },2396 {
2246 {2397 pattern: /"""[\s\S]*?"""/,
2247 pattern: /"""[\s\S]*?"""/,2398 greedy: true,
2248 greedy: true,2399 alias: "string",
2249 alias: 'string',2400 inside: {
2250 inside: {2401 interpolation: interpolation,
2251 interpolation: interpolation2402 },
2252 }2403 },
2253 }2404 ],
2254 ]2405 });
2255 2406
2256 });2407 Prism.languages.insertBefore("coffeescript", "keyword", {
2257 2408 // Object property
2258 Prism.languages.insertBefore('coffeescript', 'keyword', {2409 property: /(?!\d)\w+(?=\s*:(?!:))/,
2259 // Object property2410 });
2260 'property': /(?!\d)\w+(?=\s*:(?!:))/2411
2261 });2412 delete Prism.languages.coffeescript["template-string"];
2262 2413
2263 delete Prism.languages.coffeescript['template-string'];2414 Prism.languages.coffee = Prism.languages.coffeescript;
2264 2415})(Prism);
2265 Prism.languages.coffee = Prism.languages.coffeescript;
2266}(Prism));
2267 2416
2268/**2417/**
2269 * Original by Samuel Flores2418 * Original by Samuel Flores
@@ -2272,1833 +2421,1968 @@ Prism.languages.cmake = { · 2 unmodified lines
2272 * constant, builtin, variable, symbol, regex2421 * constant, builtin, variable, symbol, regex
2273 */2422 */
2274(function (Prism) {2423(function (Prism) {
2275 Prism.languages.ruby = Prism.languages.extend('clike', {2424 Prism.languages.ruby = Prism.languages.extend("clike", {
2276 'comment': {2425 comment: {
2277 pattern: /#.*|^=begin\s[\s\S]*?^=end/m,2426 pattern: /#.*|^=begin\s[\s\S]*?^=end/m,
2278 greedy: true2427 greedy: true,
2279 },2428 },
2280 'class-name': {2429 "class-name": {
2281 pattern: /(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,2430 pattern:
2282 lookbehind: true,2431 /(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,
2283 inside: {2432 lookbehind: true,
2284 'punctuation': /[.\\]/2433 inside: {
2285 }2434 punctuation: /[.\\]/,
2286 },2435 },
2287 'keyword': /\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,2436 },
2288 'operator': /\.{2,3}|&\.|===|<?=>|[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,2437 keyword:
2289 'punctuation': /[(){}[\].,;]/,2438 /\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,
2290 });2439 operator:
2291 2440 /\.{2,3}|&\.|===|<?=>|[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,
2292 Prism.languages.insertBefore('ruby', 'operator', {2441 punctuation: /[(){}[\].,;]/,
2293 'double-colon': {2442 });
2294 pattern: /::/,2443
2295 alias: 'punctuation'2444 Prism.languages.insertBefore("ruby", "operator", {
2296 },2445 "double-colon": {
2297 });2446 pattern: /::/,
2298 2447 alias: "punctuation",
2299 var interpolation = {2448 },
2300 pattern: /((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,2449 });
2301 lookbehind: true,2450
2302 inside: {2451 var interpolation = {
2303 'content': {2452 pattern: /((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,
2304 pattern: /^(#\{)[\s\S]+(?=\}$)/,2453 lookbehind: true,
2305 lookbehind: true,2454 inside: {
2306 inside: Prism.languages.ruby2455 content: {
2307 },2456 pattern: /^(#\{)[\s\S]+(?=\}$)/,
2308 'delimiter': {2457 lookbehind: true,
2309 pattern: /^#\{|\}$/,2458 inside: Prism.languages.ruby,
2310 alias: 'punctuation'2459 },
2311 }2460 delimiter: {
2312 }2461 pattern: /^#\{|\}$/,
2313 };2462 alias: "punctuation",
2314 2463 },
2315 delete Prism.languages.ruby.function;2464 },
2316 2465 };
2317 var percentExpression = '(?:' + [2466
2318 /([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,2467 delete Prism.languages.ruby.function;
2319 /\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,2468
2320 /\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,2469 var percentExpression =
2321 /\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,2470 "(?:" +
2322 /<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source2471 [
2323 ].join('|') + ')';2472 /([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,
2324 2473 /\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,
2325 var symbolName = /(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;2474 /\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,
2326 2475 /\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,
2327 Prism.languages.insertBefore('ruby', 'keyword', {2476 /<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source,
2328 'regex-literal': [2477 ].join("|") +
2329 {2478 ")";
2330 pattern: RegExp(/%r/.source + percentExpression + /[egimnosux]{0,6}/.source),2479
2331 greedy: true,2480 var symbolName =
2332 inside: {2481 /(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/
2333 'interpolation': interpolation,2482 .source;
2334 'regex': /[\s\S]+/2483
2335 }2484 Prism.languages.insertBefore("ruby", "keyword", {
2336 },2485 "regex-literal": [
2337 {2486 {
2338 pattern: /(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,2487 pattern: RegExp(
2339 lookbehind: true,2488 /%r/.source + percentExpression + /[egimnosux]{0,6}/.source,
2340 greedy: true,2489 ),
2341 inside: {2490 greedy: true,
2342 'interpolation': interpolation,2491 inside: {
2343 'regex': /[\s\S]+/2492 interpolation: interpolation,
2344 }2493 regex: /[\s\S]+/,
2345 }2494 },
2346 ],2495 },
2347 'variable': /[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,2496 {
2348 'symbol': [2497 pattern:
2349 {2498 /(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,
2350 pattern: RegExp(/(^|[^:]):/.source + symbolName),2499 lookbehind: true,
2351 lookbehind: true,2500 greedy: true,
2352 greedy: true2501 inside: {
2353 },2502 interpolation: interpolation,
2354 {2503 regex: /[\s\S]+/,
2355 pattern: RegExp(/([\r\n{(,][ \t]*)/.source + symbolName + /(?=:(?!:))/.source),2504 },
2356 lookbehind: true,2505 },
2357 greedy: true2506 ],
2358 },2507 variable: /[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,
2359 ],2508 symbol: [
2360 'method-definition': {2509 {
2361 pattern: /(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,2510 pattern: RegExp(/(^|[^:]):/.source + symbolName),
2362 lookbehind: true,2511 lookbehind: true,
2363 inside: {2512 greedy: true,
2364 'function': /\b\w+$/,2513 },
2365 'keyword': /^self\b/,2514 {
2366 'class-name': /^\w+/,2515 pattern: RegExp(
2367 'punctuation': /\./2516 /([\r\n{(,][ \t]*)/.source + symbolName + /(?=:(?!:))/.source,
2368 }2517 ),
2369 }2518 lookbehind: true,
2370 });2519 greedy: true,
2371 2520 },
2372 Prism.languages.insertBefore('ruby', 'string', {2521 ],
2373 'string-literal': [2522 "method-definition": {
2374 {2523 pattern: /(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,
2375 pattern: RegExp(/%[qQiIwWs]?/.source + percentExpression),2524 lookbehind: true,
2376 greedy: true,2525 inside: {
2377 inside: {2526 function: /\b\w+$/,
2378 'interpolation': interpolation,2527 keyword: /^self\b/,
2379 'string': /[\s\S]+/2528 "class-name": /^\w+/,
2380 }2529 punctuation: /\./,
2381 },2530 },
2382 {2531 },
2383 pattern: /("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,2532 });
2384 greedy: true,2533
2385 inside: {2534 Prism.languages.insertBefore("ruby", "string", {
2386 'interpolation': interpolation,2535 "string-literal": [
2387 'string': /[\s\S]+/2536 {
2388 }2537 pattern: RegExp(/%[qQiIwWs]?/.source + percentExpression),
2389 },2538 greedy: true,
2390 {2539 inside: {
2391 pattern: /<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,2540 interpolation: interpolation,
2392 alias: 'heredoc-string',2541 string: /[\s\S]+/,
2393 greedy: true,2542 },
2394 inside: {2543 },
2395 'delimiter': {2544 {
2396 pattern: /^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,2545 pattern:
2397 inside: {2546 /("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,
2398 'symbol': /\b\w+/,2547 greedy: true,
2399 'punctuation': /^<<[-~]?/2548 inside: {
2400 }2549 interpolation: interpolation,
2401 },2550 string: /[\s\S]+/,
2402 'interpolation': interpolation,2551 },
2403 'string': /[\s\S]+/2552 },
2404 }2553 {
2405 },2554 pattern: /<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,
2406 {2555 alias: "heredoc-string",
2407 pattern: /<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,2556 greedy: true,
2408 alias: 'heredoc-string',2557 inside: {
2409 greedy: true,2558 delimiter: {
2410 inside: {2559 pattern: /^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,
2411 'delimiter': {2560 inside: {
2412 pattern: /^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,2561 symbol: /\b\w+/,
2413 inside: {2562 punctuation: /^<<[-~]?/,
2414 'symbol': /\b\w+/,2563 },
2415 'punctuation': /^<<[-~]?'|'$/,2564 },
2416 }2565 interpolation: interpolation,
2417 },2566 string: /[\s\S]+/,
2418 'string': /[\s\S]+/2567 },
2419 }2568 },
2420 }2569 {
2421 ],2570 pattern: /<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,
2422 'command-literal': [2571 alias: "heredoc-string",
2423 {2572 greedy: true,
2424 pattern: RegExp(/%x/.source + percentExpression),2573 inside: {
2425 greedy: true,2574 delimiter: {
2426 inside: {2575 pattern: /^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,
2427 'interpolation': interpolation,2576 inside: {
2428 'command': {2577 symbol: /\b\w+/,
2429 pattern: /[\s\S]+/,2578 punctuation: /^<<[-~]?'|'$/,
2430 alias: 'string'2579 },
2431 }2580 },
2432 }2581 string: /[\s\S]+/,
2433 },2582 },
2434 {2583 },
2435 pattern: /`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,2584 ],
2436 greedy: true,2585 "command-literal": [
2437 inside: {2586 {
2438 'interpolation': interpolation,2587 pattern: RegExp(/%x/.source + percentExpression),
2439 'command': {2588 greedy: true,
2440 pattern: /[\s\S]+/,2589 inside: {
2441 alias: 'string'2590 interpolation: interpolation,
2442 }2591 command: {
2443 }2592 pattern: /[\s\S]+/,
2444 }2593 alias: "string",
2445 ]2594 },
2446 });2595 },
2447 2596 },
2448 delete Prism.languages.ruby.string;2597 {
2449 2598 pattern: /`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,
2450 Prism.languages.insertBefore('ruby', 'number', {2599 greedy: true,
2451 'builtin': /\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,2600 inside: {
2452 'constant': /\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/2601 interpolation: interpolation,
2453 });2602 command: {
2454 2603 pattern: /[\s\S]+/,
2455 Prism.languages.rb = Prism.languages.ruby;2604 alias: "string",
2456}(Prism));2605 },
2606 },
2607 },
2608 ],
2609 });
2610
2611 delete Prism.languages.ruby.string;
2612
2613 Prism.languages.insertBefore("ruby", "number", {
2614 builtin:
2615 /\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,
2616 constant: /\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/,
2617 });
2618
2619 Prism.languages.rb = Prism.languages.ruby;
2620})(Prism);
2457 2621
2458(function (Prism) {2622(function (Prism) {
2459 Prism.languages.crystal = Prism.languages.extend('ruby', {
2460 'keyword': [
2461 /\b(?:__DIR__|__END_LINE__|__FILE__|__LINE__|abstract|alias|annotation|as|asm|begin|break|case|class|def|do|else|elsif|end|ensure|enum|extend|for|fun|if|ifdef|include|instance_sizeof|lib|macro|module|next|of|out|pointerof|private|protected|ptr|require|rescue|return|select|self|sizeof|struct|super|then|type|typeof|undef|uninitialized|union|unless|until|when|while|with|yield)\b/,
2462 {
2463 pattern: /(\.\s*)(?:is_a|responds_to)\?/,
2464 lookbehind: true
2465 }
2466 ],
2467 '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/,
2468 'operator': [
2469 /->/,
2470 Prism.languages.ruby.operator,
2471 ],
2472 'punctuation': /[(){}[\].,;\\]/,
2473 });
2474
2475 Prism.languages.insertBefore('crystal', 'string-literal', {
2476 'attribute': {
2477 pattern: /@\[.*?\]/,
2478 inside: {
2479 'delimiter': {
2480 pattern: /^@\[|\]$/,
2481 alias: 'punctuation'
2482 },
2483 'attribute': {
2484 pattern: /^(\s*)\w+/,
2485 lookbehind: true,
2486 alias: 'class-name'
2487 },
2488 'args': {
2489 pattern: /\S(?:[\s\S]*\S)?/,
2490 inside: Prism.languages.crystal
2491 },
2492 }
2493 },
2494 'expansion': {
2495 pattern: /\{(?:\{.*?\}|%.*?%)\}/,
2496 inside: {
2497 'content': {
2498 pattern: /^(\{.)[\s\S]+(?=.\}$)/,
2499 lookbehind: true,
2500 inside: Prism.languages.crystal
2501 },
2502 'delimiter': {
2503 pattern: /^\{[\{%]|[\}%]\}$/,
2504 alias: 'operator'
2505 }
2506 }
2507 },
2508 'char': {
2509 pattern: /'(?:[^\\\r\n]{1,2}|\\(?:.|u(?:[A-Fa-f0-9]{1,4}|\{[A-Fa-f0-9]{1,6}\})))'/,
2510 greedy: true
2511 }
2512 });
2513
2514}(Prism));
2515
2516Prism.languages.d = Prism.languages.extend('clike', {
2517 'comment': [
2518 {
2519 // Shebang
2520 pattern: /^\s*#!.+/,
2521 greedy: true
2522 },
2523 {
2524 pattern: RegExp(/(^|[^\\])/.source + '(?:' + [
2525 // /+ comment +/
2526 // Allow one level of nesting
2527 /\/\+(?:\/\+(?:[^+]|\+(?!\/))*\+\/|(?!\/\+)[\s\S])*?\+\//.source,
2528 // // comment
2529 /\/\/.*/.source,
2530 // /* comment */
2531 /\/\*[\s\S]*?\*\//.source
2532 ].join('|') + ')'),
2533 lookbehind: true,
2534 greedy: true
2535 }
2536 ],
2537 'string': [
2538 {
2539 pattern: RegExp([
2540 // r"", x""
2541 /\b[rx]"(?:\\[\s\S]|[^\\"])*"[cwd]?/.source,
2542
2543 // q"[]", q"()", q"<>", q"{}"
2544 /\bq"(?:\[[\s\S]*?\]|\([\s\S]*?\)|<[\s\S]*?>|\{[\s\S]*?\})"/.source,
2545
2546 // q"IDENT
2547 // ...
2548 // IDENT"
2549 /\bq"((?!\d)\w+)$[\s\S]*?^\1"/.source,
2550
2551 // q"//", q"||", etc.
2552 // eslint-disable-next-line regexp/strict
2553 /\bq"(.)[\s\S]*?\2"/.source,
2554

6302 more lines aren’t shown here.

assets/javascripts/vendor/raven.js+3290−2911
@@ -10,2457 +10,2776 @@ · 9 unmodified lines
10 *10 *
11 */11 */
12 12
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){13(function (f) {
14function RavenConfigError(message) {14 if (typeof exports === "object" && typeof module !== "undefined") {
15 this.name = 'RavenConfigError';15 module.exports = f();
16 this.message = message;16 } else if (typeof define === "function" && define.amd) {
17}17 define([], f);
18RavenConfigError.prototype = new Error();18 } else {
19RavenConfigError.prototype.constructor = RavenConfigError;19 var g;
20 20 if (typeof window !== "undefined") {
21module.exports = RavenConfigError;21 g = window;
22 22 } else if (typeof global !== "undefined") {
23},{}],2:[function(_dereq_,module,exports){23 g = global;
24var wrapMethod = function(console, level, callback) {24 } else if (typeof self !== "undefined") {
25 var originalConsoleLevel = console[level];25 g = self;
26 var originalConsole = console;26 } else {
27 27 g = this;
28 if (!(level in console)) {28 }
29 return;29 g.Raven = f();
30 }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;
31 73
32 var sentryLevel = level === 'warn' ? 'warning' : level;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 }
33 87
34 console[level] = function() {88 var sentryLevel = level === "warn" ? "warning" : level;
35 var args = [].slice.call(arguments);
36 89
37 var msg = '' + args.join(' ');90 console[level] = function () {
38 var data = {level: sentryLevel, logger: 'console', extra: {arguments: args}};91 var args = [].slice.call(arguments);
39 92
40 if (level === 'assert') {93 var msg = "" + args.join(" ");
41 if (args[0] === false) {94 var data = {
42 // Default browsers message95 level: sentryLevel,
43 msg = 'Assertion failed: ' + (args.slice(1).join(' ') || 'console.assert');96 logger: "console",
44 data.extra.arguments = args.slice(1);97 extra: { arguments: args },
45 callback && callback(msg, data);98 };
46 }
47 } else {
48 callback && callback(msg, data);
49 }
50 99
51 // this fails for some browsers. :(100 if (level === "assert") {
52 if (originalConsoleLevel) {101 if (args[0] === false) {
53 // IE9 doesn't allow calling apply on console functions directly102 // Default browsers message
54 // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193103 msg =
55 Function.prototype.apply.call(originalConsoleLevel, originalConsole, args);104 "Assertion failed: " +
56 }105 (args.slice(1).join(" ") || "console.assert");
57 };106 data.extra.arguments = args.slice(1);
58};107 callback && callback(msg, data);
59 108 }
60module.exports = {109 } else {
61 wrapMethod: wrapMethod110 callback && callback(msg, data);
62};111 }
63
64},{}],3:[function(_dereq_,module,exports){
65(function (global){
66/*global XDomainRequest:false */
67
68var TraceKit = _dereq_(6);
69var stringify = _dereq_(7);
70var RavenConfigError = _dereq_(1);
71
72var utils = _dereq_(5);
73var isError = utils.isError;
74var isObject = utils.isObject;
75var isErrorEvent = utils.isErrorEvent;
76var isUndefined = utils.isUndefined;
77var isFunction = utils.isFunction;
78var isString = utils.isString;
79var isArray = utils.isArray;
80var isEmptyObject = utils.isEmptyObject;
81var each = utils.each;
82var objectMerge = utils.objectMerge;
83var truncate = utils.truncate;
84var objectFrozen = utils.objectFrozen;
85var hasKey = utils.hasKey;
86var joinRegExp = utils.joinRegExp;
87var urlencode = utils.urlencode;
88var uuid4 = utils.uuid4;
89var htmlTreeAsString = utils.htmlTreeAsString;
90var isSameException = utils.isSameException;
91var isSameStacktrace = utils.isSameStacktrace;
92var parseUrl = utils.parseUrl;
93var fill = utils.fill;
94
95var wrapConsoleMethod = _dereq_(2).wrapMethod;
96
97var dsnKeys = 'source protocol user pass host port path'.split(' '),
98 dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;
99
100function now() {
101 return +new Date();
102}
103
104// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
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}
173 112
174/*113 // this fails for some browsers. :(
175 * The core Raven singleton114 if (originalConsoleLevel) {
176 *115 // IE9 doesn't allow calling apply on console functions directly
177 * @this {Raven}116 // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193
178 */117 Function.prototype.apply.call(
118 originalConsoleLevel,
119 originalConsole,
120 args,
121 );
122 }
123 };
124 };
179 125
180Raven.prototype = {126 module.exports = {
181 // Hardcode version string so that raven source can be loaded directly via127 wrapMethod: wrapMethod,
182 // webpack (using a build step causes webpack #1617). Grunt verifies that128 };
183 // this value matches package.json during build.129 },
184 // See: https://github.com/getsentry/raven-js/issues/465130 {},
185 VERSION: '3.20.1',131 ],
186 132 3: [
187 debug: false,133 function (_dereq_, module, exports) {
188 134 (function (global) {
189 TraceKit: TraceKit, // alias to TraceKit135 /*global XDomainRequest:false */
190 136
191 /*137 var TraceKit = _dereq_(6);
192 * Configure Raven with a DSN and extra options138 var stringify = _dereq_(7);
193 *139 var RavenConfigError = _dereq_(1);
194 * @param {string} dsn The public Sentry DSN140
195 * @param {object} options Set of global options [optional]141 var utils = _dereq_(5);
196 * @return {Raven}142 var isError = utils.isError;
197 */143 var isObject = utils.isObject;
198 config: function(dsn, options) {144 var isErrorEvent = utils.isErrorEvent;
199 var self = this;145 var isUndefined = utils.isUndefined;
200 146 var isFunction = utils.isFunction;
201 if (self._globalServer) {147 var isString = utils.isString;
202 this._logDebug('error', 'Error: Raven has already been configured');148 var isArray = utils.isArray;
203 return self;149 var isEmptyObject = utils.isEmptyObject;
204 }150 var each = utils.each;
205 if (!dsn) return self;151 var objectMerge = utils.objectMerge;
206 152 var truncate = utils.truncate;
207 var globalOptions = self._globalOptions;153 var objectFrozen = utils.objectFrozen;
208 154 var hasKey = utils.hasKey;
209 // merge in options155 var joinRegExp = utils.joinRegExp;
210 if (options) {156 var urlencode = utils.urlencode;
211 each(options, function(key, value) {157 var uuid4 = utils.uuid4;
212 // tags and extra are special and need to be put into context158 var htmlTreeAsString = utils.htmlTreeAsString;
213 if (key === 'tags' || key === 'extra' || key === 'user') {159 var isSameException = utils.isSameException;
214 self._globalContext[key] = value;160 var isSameStacktrace = utils.isSameStacktrace;
215 } else {161 var parseUrl = utils.parseUrl;
216 globalOptions[key] = value;162 var fill = utils.fill;
217 }163
218 });164 var wrapConsoleMethod = _dereq_(2).wrapMethod;
219 }165
166 var dsnKeys = "source protocol user pass host port path".split(" "),
167 dsnPattern =
168 /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;
169
170 function now() {
171 return +new Date();
172 }
220 173
221 self.setDSN(dsn);174 // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
222 175 var _window =
223 // "Script error." is hard coded into browsers for errors that it can't read.176 typeof window !== "undefined"
224 // this is the result of a script being pulled in from an external domain and CORS.177 ? window
225 globalOptions.ignoreErrors.push(/^Script error\.?$/);178 : typeof global !== "undefined"
226 globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/);179 ? global
227 180 : typeof self !== "undefined"
228 // join regexp rules into one big rule181 ? self
229 globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors);182 : {};
230 globalOptions.ignoreUrls = globalOptions.ignoreUrls.length183 var _document = _window.document;
231 ? joinRegExp(globalOptions.ignoreUrls)184 var _navigator = _window.navigator;
232 : false;185
233 globalOptions.whitelistUrls = globalOptions.whitelistUrls.length186 function keepOriginalCallback(original, callback) {
234 ? joinRegExp(globalOptions.whitelistUrls)187 return isFunction(callback)
235 : false;188 ? function (data) {
236 globalOptions.includePaths = joinRegExp(globalOptions.includePaths);189 return callback(data, original);
237 globalOptions.maxBreadcrumbs = Math.max(190 }
238 0,191 : callback;
239 Math.min(globalOptions.maxBreadcrumbs || 100, 100)192 }
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;
257 193
258 var instrumentDefaults = {194 // First, check for JSON support
259 tryCatch: true195 // If there is no JSON, we no-op the core features of Raven
260 };196 // since JSON is required to encode the payload
197 function Raven() {
198 this._hasJSON = !!(typeof JSON === "object" && JSON.stringify);
199 // Raven can run in contexts where there's no document (react-native)
200 this._hasDocument = !isUndefined(_document);
201 this._hasNavigator = !isUndefined(_navigator);
202 this._lastCapturedException = null;
203 this._lastData = null;
204 this._lastEventId = null;
205 this._globalServer = null;
206 this._globalKey = null;
207 this._globalProject = null;
208 this._globalContext = {};
209 this._globalOptions = {
210 logger: "javascript",
211 ignoreErrors: [],
212 ignoreUrls: [],
213 whitelistUrls: [],
214 includePaths: [],
215 collectWindowErrors: true,
216 maxMessageLength: 0,
217
218 // By default, truncates URL values to 250 chars
219 maxUrlLength: 250,
220 stackTraceLimit: 50,
221 autoBreadcrumbs: true,
222 instrument: true,
223 sampleRate: 1,
224 };
225 this._ignoreOnError = 0;
226 this._isRavenInstalled = false;
227 this._originalErrorStackTraceLimit = Error.stackTraceLimit;
228 // capture references to window.console *and* all its methods first
229 // before the console plugin has a chance to monkey patch
230 this._originalConsole = _window.console || {};
231 this._originalConsoleMethods = {};
232 this._plugins = [];
233 this._startTime = now();
234 this._wrappedBuiltIns = [];
235 this._breadcrumbs = [];
236 this._lastCapturedEvent = null;
237 this._keypressTimeout;
238 this._location = _window.location;
239 this._lastHref = this._location && this._location.href;
240 this._resetBackoff();
241
242 // eslint-disable-next-line guard-for-in
243 for (var method in this._originalConsole) {
244 this._originalConsoleMethods[method] =
245 this._originalConsole[method];
246 }
247 }
261 248
262 var instrument = globalOptions.instrument;249 /*
263 if ({}.toString.call(instrument) === '[object Object]') {250 * The core Raven singleton
264 instrument = objectMerge(instrumentDefaults, instrument);251 *
265 } else if (instrument !== false) {252 * @this {Raven}
266 instrument = instrumentDefaults;253 */
267 }254
268 globalOptions.instrument = instrument;255 Raven.prototype = {
269 256 // Hardcode version string so that raven source can be loaded directly via
270 TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors;257 // webpack (using a build step causes webpack #1617). Grunt verifies that
271 258 // this value matches package.json during build.
272 // return for chaining259 // See: https://github.com/getsentry/raven-js/issues/465
273 return self;260 VERSION: "3.20.1",
274 },261
275 262 debug: false,
276 /*263
277 * Installs a global window.onerror error handler264 TraceKit: TraceKit, // alias to TraceKit
278 * to capture and report uncaught exceptions.265
279 * At this point, install() is required to be called due266 /*
280 * to the way TraceKit is set up.267 * Configure Raven with a DSN and extra options
281 *268 *
282 * @return {Raven}269 * @param {string} dsn The public Sentry DSN
283 */270 * @param {object} options Set of global options [optional]
284 install: function() {271 * @return {Raven}
285 var self = this;272 */
286 if (self.isSetup() && !self._isRavenInstalled) {273 config: function (dsn, options) {
287 TraceKit.report.subscribe(function() {274 var self = this;
288 self._handleOnErrorStackInfo.apply(self, arguments);275
289 });276 if (self._globalServer) {
290 277 this._logDebug(
291 self._patchFunctionToString();278 "error",
292 279 "Error: Raven has already been configured",
293 if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch) {280 );
294 self._instrumentTryCatch();281 return self;
295 }282 }
283 if (!dsn) return self;
284
285 var globalOptions = self._globalOptions;
286
287 // merge in options
288 if (options) {
289 each(options, function (key, value) {
290 // tags and extra are special and need to be put into context
291 if (key === "tags" || key === "extra" || key === "user") {
292 self._globalContext[key] = value;
293 } else {
294 globalOptions[key] = value;
295 }
296 });
297 }
296 298
297 if (self._globalOptions.autoBreadcrumbs) self._instrumentBreadcrumbs();299 self.setDSN(dsn);
300
301 // "Script error." is hard coded into browsers for errors that it can't read.
302 // this is the result of a script being pulled in from an external domain and CORS.
303 globalOptions.ignoreErrors.push(/^Script error\.?$/);
304 globalOptions.ignoreErrors.push(
305 /^Javascript error: Script error\.? on line 0$/,
306 );
307
308 // join regexp rules into one big rule
309 globalOptions.ignoreErrors = joinRegExp(
310 globalOptions.ignoreErrors,
311 );
312 globalOptions.ignoreUrls = globalOptions.ignoreUrls.length
313 ? joinRegExp(globalOptions.ignoreUrls)
314 : false;
315 globalOptions.whitelistUrls = globalOptions.whitelistUrls.length
316 ? joinRegExp(globalOptions.whitelistUrls)
317 : false;
318 globalOptions.includePaths = joinRegExp(
319 globalOptions.includePaths,
320 );
321 globalOptions.maxBreadcrumbs = Math.max(
322 0,
323 Math.min(globalOptions.maxBreadcrumbs || 100, 100),
324 ); // default and hard limit is 100
325
326 var autoBreadcrumbDefaults = {
327 xhr: true,
328 console: true,
329 dom: true,
330 location: true,
331 sentry: true,
332 };
298 333
299 // Install all of the plugins334 var autoBreadcrumbs = globalOptions.autoBreadcrumbs;
300 self._drainPlugins();335 if ({}.toString.call(autoBreadcrumbs) === "[object Object]") {
336 autoBreadcrumbs = objectMerge(
337 autoBreadcrumbDefaults,
338 autoBreadcrumbs,
339 );
340 } else if (autoBreadcrumbs !== false) {
341 autoBreadcrumbs = autoBreadcrumbDefaults;
342 }
343 globalOptions.autoBreadcrumbs = autoBreadcrumbs;
301 344
302 self._isRavenInstalled = true;345 var instrumentDefaults = {
303 }346 tryCatch: true,
347 };
304 348
305 Error.stackTraceLimit = self._globalOptions.stackTraceLimit;349 var instrument = globalOptions.instrument;
306 return this;350 if ({}.toString.call(instrument) === "[object Object]") {
307 },351 instrument = objectMerge(instrumentDefaults, instrument);
308 352 } else if (instrument !== false) {
309 /*353 instrument = instrumentDefaults;
310 * Set the DSN (can be called multiple time unlike config)354 }
311 *355 globalOptions.instrument = instrument;
312 * @param {string} dsn The public Sentry DSN356
313 */357 TraceKit.collectWindowErrors =
314 setDSN: function(dsn) {358 !!globalOptions.collectWindowErrors;
315 var self = this,359
316 uri = self._parseDSN(dsn),360 // return for chaining
317 lastSlash = uri.path.lastIndexOf('/'),361 return self;
318 path = uri.path.substr(1, lastSlash);362 },
319 363
320 self._dsn = dsn;364 /*
321 self._globalKey = uri.user;365 * Installs a global window.onerror error handler
322 self._globalSecret = uri.pass && uri.pass.substr(1);366 * to capture and report uncaught exceptions.
323 self._globalProject = uri.path.substr(lastSlash + 1);367 * At this point, install() is required to be called due
324 368 * to the way TraceKit is set up.
325 self._globalServer = self._getGlobalServer(uri);369 *
326 370 * @return {Raven}
327 self._globalEndpoint =371 */
328 self._globalServer + '/' + path + 'api/' + self._globalProject + '/store/';372 install: function () {
329 373 var self = this;
330 // Reset backoff state since we may be pointing at a374 if (self.isSetup() && !self._isRavenInstalled) {
331 // new project/server375 TraceKit.report.subscribe(function () {
332 this._resetBackoff();376 self._handleOnErrorStackInfo.apply(self, arguments);
333 },377 });
334 378
335 /*379 self._patchFunctionToString();
336 * Wrap code within a context so Raven can capture errors380
337 * reliably across domains that is executed immediately.381 if (
338 *382 self._globalOptions.instrument &&
339 * @param {object} options A specific set of options for this context [optional]383 self._globalOptions.instrument.tryCatch
340 * @param {function} func The callback to be immediately executed within the context384 ) {
341 * @param {array} args An array of arguments to be called with the callback [optional]385 self._instrumentTryCatch();
342 */386 }
343 context: function(options, func, args) {
344 if (isFunction(options)) {
345 args = func || [];
346 func = options;
347 options = undefined;
348 }
349 387
350 return this.wrap(options, func).apply(this, args);388 if (self._globalOptions.autoBreadcrumbs)
351 },389 self._instrumentBreadcrumbs();
352
353 /*
354 * Wrap code within a context and returns back a new function to be executed
355 *
356 * @param {object} options A specific set of options for this context [optional]
357 * @param {function} func The function to be wrapped in a new context
358 * @param {function} func A function to call before the try/catch wrapper [optional, private]
359 * @return {function} The newly wrapped functions with a context
360 */
361 wrap: function(options, func, _before) {
362 var self = this;
363 // 1 argument has been passed, and it's not a function
364 // so just return it
365 if (isUndefined(func) && !isFunction(options)) {
366 return options;
367 }
368 390
369 // options is optional391 // Install all of the plugins
370 if (isFunction(options)) {392 self._drainPlugins();
371 func = options;
372 options = undefined;
373 }
374 393
375 // At this point, we've passed along 2 arguments, and the second one394 self._isRavenInstalled = true;
376 // is not a function either, so we'll just return the second argument.395 }
377 if (!isFunction(func)) {
378 return func;
379 }
380 396
381 // We don't wanna wrap it twice!397 Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
382 try {398 return this;
383 if (func.__raven__) {399 },
384 return func;400
385 }401 /*
402 * Set the DSN (can be called multiple time unlike config)
403 *
404 * @param {string} dsn The public Sentry DSN
405 */
406 setDSN: function (dsn) {
407 var self = this,
408 uri = self._parseDSN(dsn),
409 lastSlash = uri.path.lastIndexOf("/"),
410 path = uri.path.substr(1, lastSlash);
411
412 self._dsn = dsn;
413 self._globalKey = uri.user;
414 self._globalSecret = uri.pass && uri.pass.substr(1);
415 self._globalProject = uri.path.substr(lastSlash + 1);
416
417 self._globalServer = self._getGlobalServer(uri);
418
419 self._globalEndpoint =
420 self._globalServer +
421 "/" +
422 path +
423 "api/" +
424 self._globalProject +
425 "/store/";
426
427 // Reset backoff state since we may be pointing at a
428 // new project/server
429 this._resetBackoff();
430 },
431
432 /*
433 * Wrap code within a context so Raven can capture errors
434 * reliably across domains that is executed immediately.
435 *
436 * @param {object} options A specific set of options for this context [optional]
437 * @param {function} func The callback to be immediately executed within the context
438 * @param {array} args An array of arguments to be called with the callback [optional]
439 */
440 context: function (options, func, args) {
441 if (isFunction(options)) {
442 args = func || [];
443 func = options;
444 options = undefined;
445 }
386 446
387 // If this has already been wrapped in the past, return that447 return this.wrap(options, func).apply(this, args);
388 if (func.__raven_wrapper__) {448 },
389 return func.__raven_wrapper__;449
390 }450 /*
391 } catch (e) {451 * Wrap code within a context and returns back a new function to be executed
392 // Just accessing custom props in some Selenium environments452 *
393 // can cause a "Permission denied" exception (see raven-js#495).453 * @param {object} options A specific set of options for this context [optional]
394 // Bail on wrapping and return the function as-is (defers to window.onerror).454 * @param {function} func The function to be wrapped in a new context
395 return func;455 * @param {function} func A function to call before the try/catch wrapper [optional, private]
396 }456 * @return {function} The newly wrapped functions with a context
457 */
458 wrap: function (options, func, _before) {
459 var self = this;
460 // 1 argument has been passed, and it's not a function
461 // so just return it
462 if (isUndefined(func) && !isFunction(options)) {
463 return options;
464 }
397 465
398 function wrapped() {466 // options is optional
399 var args = [],467 if (isFunction(options)) {
400 i = arguments.length,468 func = options;
401 deep = !options || (options && options.deep !== false);469 options = undefined;
470 }
402 471
403 if (_before && isFunction(_before)) {472 // At this point, we've passed along 2 arguments, and the second one
404 _before.apply(this, arguments);473 // is not a function either, so we'll just return the second argument.
405 }474 if (!isFunction(func)) {
475 return func;
476 }
406 477
407 // Recursively wrap all of a function's arguments that are478 // We don't wanna wrap it twice!
408 // functions themselves.479 try {
409 while (i--) args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i];480 if (func.__raven__) {
410 481 return func;
411 try {482 }
412 // Attempt to invoke user-land function
413 // NOTE: If you are a Sentry user, and you are seeing this stack frame, it
414 // means Raven caught an error invoking your application code. This is
415 // expected behavior and NOT indicative of a bug with Raven.js.
416 return func.apply(this, args);
417 } catch (e) {
418 self._ignoreNextOnError();
419 self.captureException(e, options);
420 throw e;
421 }
422 }
423 483
424 // copy over properties of the old function484 // If this has already been wrapped in the past, return that
425 for (var property in func) {485 if (func.__raven_wrapper__) {
426 if (hasKey(func, property)) {486 return func.__raven_wrapper__;
427 wrapped[property] = func[property];487 }
428 }488 } catch (e) {
429 }489 // Just accessing custom props in some Selenium environments
430 wrapped.prototype = func.prototype;490 // can cause a "Permission denied" exception (see raven-js#495).
431 491 // Bail on wrapping and return the function as-is (defers to window.onerror).
432 func.__raven_wrapper__ = wrapped;492 return func;
433 // Signal that this function has been wrapped/filled already493 }
434 // for both debugging and to prevent it to being wrapped/filled twice
435 wrapped.__raven__ = true;
436 wrapped.__orig__ = func;
437
438 return wrapped;
439 },
440
441 /*
442 * Uninstalls the global error handler.
443 *
444 * @return {Raven}
445 */
446 uninstall: function() {
447 TraceKit.report.uninstall();
448
449 this._unpatchFunctionToString();
450 this._restoreBuiltIns();
451
452 Error.stackTraceLimit = this._originalErrorStackTraceLimit;
453 this._isRavenInstalled = false;
454
455 return this;
456 },
457
458 /*
459 * Manually capture an exception and send it over to Sentry
460 *
461 * @param {error} ex An exception to be logged
462 * @param {object} options A specific set of options for this error [optional]
463 * @return {Raven}
464 */
465 captureException: function(ex, options) {
466 // Cases for sending ex as a message, rather than an exception
467 var isNotError = !isError(ex);
468 var isNotErrorEvent = !isErrorEvent(ex);
469 var isErrorEventWithoutError = isErrorEvent(ex) && !ex.error;
470
471 if ((isNotError && isNotErrorEvent) || isErrorEventWithoutError) {
472 return this.captureMessage(
473 ex,
474 objectMerge(
475 {
476 trimHeadFrames: 1,
477 stacktrace: true // if we fall back to captureMessage, default to attempting a new trace
478 },
479 options
480 )
481 );
482 }
483 494
484 // Get actual Error from ErrorEvent495 function wrapped() {
485 if (isErrorEvent(ex)) ex = ex.error;496 var args = [],
486 497 i = arguments.length,
487 // Store the raw exception object for potential debugging and introspection498 deep = !options || (options && options.deep !== false);
488 this._lastCapturedException = ex;
489
490 // TraceKit.report will re-raise any exception passed to it,
491 // which means you have to wrap it in try/catch. Instead, we
492 // can wrap it here and only re-raise if TraceKit.report
493 // raises an exception different from the one we asked to
494 // report on.
495 try {
496 var stack = TraceKit.computeStackTrace(ex);
497 this._handleStackInfo(stack, options);
498 } catch (ex1) {
499 if (ex !== ex1) {
500 throw ex1;
501 }
502 }
503 499
504 return this;500 if (_before && isFunction(_before)) {
505 },501 _before.apply(this, arguments);
506 502 }
507 /*
508 * Manually send a message to Sentry
509 *
510 * @param {string} msg A plain message to be captured in Sentry
511 * @param {object} options A specific set of options for this message [optional]
512 * @return {Raven}
513 */
514 captureMessage: function(msg, options) {
515 // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an
516 // early call; we'll error on the side of logging anything called before configuration since it's
517 // probably something you should see:
518 if (
519 !!this._globalOptions.ignoreErrors.test &&
520 this._globalOptions.ignoreErrors.test(msg)
521 ) {
522 return;
523 }
524 503
525 options = options || {};504 // Recursively wrap all of a function's arguments that are
526 505 // functions themselves.
527 var data = objectMerge(506 while (i--)
528 {507 args[i] = deep
529 message: msg + '' // Make sure it's actually a string508 ? self.wrap(options, arguments[i])
530 },509 : arguments[i];
531 options
532 );
533
534 var ex;
535 // Generate a "synthetic" stack trace from this point.
536 // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative
537 // of a bug with Raven.js. Sentry generates synthetic traces either by configuration,
538 // or if it catches a thrown object without a "stack" property.
539 try {
540 throw new Error(msg);
541 } catch (ex1) {
542 ex = ex1;
543 }
544 510
545 // null exception name so `Error` isn't prefixed to msg511 try {
546 ex.name = null;512 // Attempt to invoke user-land function
547 var stack = TraceKit.computeStackTrace(ex);513 // NOTE: If you are a Sentry user, and you are seeing this stack frame, it
514 // means Raven caught an error invoking your application code. This is
515 // expected behavior and NOT indicative of a bug with Raven.js.
516 return func.apply(this, args);
517 } catch (e) {
518 self._ignoreNextOnError();
519 self.captureException(e, options);
520 throw e;
521 }
522 }
548 523
549 // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1]524 // copy over properties of the old function
550 var initialCall = isArray(stack.stack) && stack.stack[1];525 for (var property in func) {
551 var fileurl = (initialCall && initialCall.url) || '';526 if (hasKey(func, property)) {
527 wrapped[property] = func[property];
528 }
529 }
530 wrapped.prototype = func.prototype;
531
532 func.__raven_wrapper__ = wrapped;
533 // Signal that this function has been wrapped/filled already
534 // for both debugging and to prevent it to being wrapped/filled twice
535 wrapped.__raven__ = true;
536 wrapped.__orig__ = func;
537
538 return wrapped;
539 },
540
541 /*
542 * Uninstalls the global error handler.
543 *
544 * @return {Raven}
545 */
546 uninstall: function () {
547 TraceKit.report.uninstall();
548
549 this._unpatchFunctionToString();
550 this._restoreBuiltIns();
551
552 Error.stackTraceLimit = this._originalErrorStackTraceLimit;
553 this._isRavenInstalled = false;
554
555 return this;
556 },
557
558 /*
559 * Manually capture an exception and send it over to Sentry
560 *
561 * @param {error} ex An exception to be logged
562 * @param {object} options A specific set of options for this error [optional]
563 * @return {Raven}
564 */
565 captureException: function (ex, options) {
566 // Cases for sending ex as a message, rather than an exception
567 var isNotError = !isError(ex);
568 var isNotErrorEvent = !isErrorEvent(ex);
569 var isErrorEventWithoutError = isErrorEvent(ex) && !ex.error;
570
571 if (
572 (isNotError && isNotErrorEvent) ||
573 isErrorEventWithoutError
574 ) {
575 return this.captureMessage(
576 ex,
577 objectMerge(
578 {
579 trimHeadFrames: 1,
580 stacktrace: true, // if we fall back to captureMessage, default to attempting a new trace
581 },
582 options,
583 ),
584 );
585 }
552 586
553 if (587 // Get actual Error from ErrorEvent
554 !!this._globalOptions.ignoreUrls.test &&588 if (isErrorEvent(ex)) ex = ex.error;
555 this._globalOptions.ignoreUrls.test(fileurl)
556 ) {
557 return;
558 }
559 589
560 if (590 // Store the raw exception object for potential debugging and introspection
561 !!this._globalOptions.whitelistUrls.test &&591 this._lastCapturedException = ex;
562 !this._globalOptions.whitelistUrls.test(fileurl)
563 ) {
564 return;
565 }
566 592
567 if (this._globalOptions.stacktrace || (options && options.stacktrace)) {593 // TraceKit.report will re-raise any exception passed to it,
568 options = objectMerge(594 // which means you have to wrap it in try/catch. Instead, we
569 {595 // can wrap it here and only re-raise if TraceKit.report
570 // fingerprint on msg, not stack trace (legacy behavior, could be596 // raises an exception different from the one we asked to
571 // revisited)597 // report on.
572 fingerprint: msg,598 try {
573 // since we know this is a synthetic trace, the top N-most frames599 var stack = TraceKit.computeStackTrace(ex);
574 // MUST be from Raven.js, so mark them as in_app later by setting600 this._handleStackInfo(stack, options);
575 // trimHeadFrames601 } catch (ex1) {
576 trimHeadFrames: (options.trimHeadFrames || 0) + 1602 if (ex !== ex1) {
577 },603 throw ex1;
578 options604 }
579 );605 }
580
581 var frames = this._prepareFrames(stack, options);
582 data.stacktrace = {
583 // Sentry expects frames oldest to newest
584 frames: frames.reverse()
585 };
586 }
587 606
588 // Fire away!607 return this;
589 this._send(data);608 },
609
610 /*
611 * Manually send a message to Sentry
612 *
613 * @param {string} msg A plain message to be captured in Sentry
614 * @param {object} options A specific set of options for this message [optional]
615 * @return {Raven}
616 */
617 captureMessage: function (msg, options) {
618 // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an
619 // early call; we'll error on the side of logging anything called before configuration since it's
620 // probably something you should see:
621 if (
622 !!this._globalOptions.ignoreErrors.test &&
623 this._globalOptions.ignoreErrors.test(msg)
624 ) {
625 return;
626 }
590 627
591 return this;628 options = options || {};
592 },
593 629
594 captureBreadcrumb: function(obj) {630 var data = objectMerge(
595 var crumb = objectMerge(631 {
596 {632 message: msg + "", // Make sure it's actually a string
597 timestamp: now() / 1000633 },
598 },634 options,
599 obj635 );
600 );
601 636
602 if (isFunction(this._globalOptions.breadcrumbCallback)) {637 var ex;
603 var result = this._globalOptions.breadcrumbCallback(crumb);638 // Generate a "synthetic" stack trace from this point.
639 // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative
640 // of a bug with Raven.js. Sentry generates synthetic traces either by configuration,
641 // or if it catches a thrown object without a "stack" property.
642 try {
643 throw new Error(msg);
644 } catch (ex1) {
645 ex = ex1;
646 }
604 647
605 if (isObject(result) && !isEmptyObject(result)) {648 // null exception name so `Error` isn't prefixed to msg
606 crumb = result;649 ex.name = null;
607 } else if (result === false) {650 var stack = TraceKit.computeStackTrace(ex);
608 return this;
609 }
610 }
611 651
612 this._breadcrumbs.push(crumb);652 // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1]
613 if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) {653 var initialCall = isArray(stack.stack) && stack.stack[1];
614 this._breadcrumbs.shift();654 var fileurl = (initialCall && initialCall.url) || "";
615 }
616 return this;
617 },
618 655
619 addPlugin: function(plugin /*arg1, arg2, ... argN*/) {656 if (
620 var pluginArgs = [].slice.call(arguments, 1);657 !!this._globalOptions.ignoreUrls.test &&
658 this._globalOptions.ignoreUrls.test(fileurl)
659 ) {
660 return;
661 }
621 662
622 this._plugins.push([plugin, pluginArgs]);663 if (
623 if (this._isRavenInstalled) {664 !!this._globalOptions.whitelistUrls.test &&
624 this._drainPlugins();665 !this._globalOptions.whitelistUrls.test(fileurl)
625 }666 ) {
667 return;
668 }
626 669
627 return this;670 if (
628 },671 this._globalOptions.stacktrace ||
629 672 (options && options.stacktrace)
630 /*673 ) {
631 * Set/clear a user to be sent along with the payload.674 options = objectMerge(
632 *675 {
633 * @param {object} user An object representing user data [optional]676 // fingerprint on msg, not stack trace (legacy behavior, could be
634 * @return {Raven}677 // revisited)
635 */678 fingerprint: msg,
636 setUserContext: function(user) {679 // since we know this is a synthetic trace, the top N-most frames
637 // Intentionally do not merge here since that's an unexpected behavior.680 // MUST be from Raven.js, so mark them as in_app later by setting
638 this._globalContext.user = user;681 // trimHeadFrames
639 682 trimHeadFrames: (options.trimHeadFrames || 0) + 1,
640 return this;683 },
641 },684 options,
642 685 );
643 /*686
644 * Merge extra attributes to be sent along with the payload.687 var frames = this._prepareFrames(stack, options);
645 *688 data.stacktrace = {
646 * @param {object} extra An object representing extra data [optional]689 // Sentry expects frames oldest to newest
647 * @return {Raven}690 frames: frames.reverse(),
648 */691 };
649 setExtraContext: function(extra) {692 }
650 this._mergeContext('extra', extra);
651
652 return this;
653 },
654
655 /*
656 * Merge tags to be sent along with the payload.
657 *
658 * @param {object} tags An object representing tags [optional]
659 * @return {Raven}
660 */
661 setTagsContext: function(tags) {
662 this._mergeContext('tags', tags);
663
664 return this;
665 },
666
667 /*
668 * Clear all of the context.
669 *
670 * @return {Raven}
671 */
672 clearContext: function() {
673 this._globalContext = {};
674
675 return this;
676 },
677
678 /*
679 * Get a copy of the current context. This cannot be mutated.
680 *
681 * @return {object} copy of context
682 */
683 getContext: function() {
684 // lol javascript
685 return JSON.parse(stringify(this._globalContext));
686 },
687
688 /*
689 * Set environment of application
690 *
691 * @param {string} environment Typically something like 'production'.
692 * @return {Raven}
693 */
694 setEnvironment: function(environment) {
695 this._globalOptions.environment = environment;
696
697 return this;
698 },
699
700 /*
701 * Set release version of application
702 *
703 * @param {string} release Typically something like a git SHA to identify version
704 * @return {Raven}
705 */
706 setRelease: function(release) {
707 this._globalOptions.release = release;
708
709 return this;
710 },
711
712 /*
713 * Set the dataCallback option
714 *
715 * @param {function} callback The callback to run which allows the
716 * data blob to be mutated before sending
717 * @return {Raven}
718 */
719 setDataCallback: function(callback) {
720 var original = this._globalOptions.dataCallback;
721 this._globalOptions.dataCallback = keepOriginalCallback(original, callback);
722 return this;
723 },
724
725 /*
726 * Set the breadcrumbCallback option
727 *
728 * @param {function} callback The callback to run which allows filtering
729 * or mutating breadcrumbs
730 * @return {Raven}
731 */
732 setBreadcrumbCallback: function(callback) {
733 var original = this._globalOptions.breadcrumbCallback;
734 this._globalOptions.breadcrumbCallback = keepOriginalCallback(original, callback);
735 return this;
736 },
737
738 /*
739 * Set the shouldSendCallback option
740 *
741 * @param {function} callback The callback to run which allows
742 * introspecting the blob before sending
743 * @return {Raven}
744 */
745 setShouldSendCallback: function(callback) {
746 var original = this._globalOptions.shouldSendCallback;
747 this._globalOptions.shouldSendCallback = keepOriginalCallback(original, callback);
748 return this;
749 },
750
751 /**
752 * Override the default HTTP transport mechanism that transmits data
753 * to the Sentry server.
754 *
755 * @param {function} transport Function invoked instead of the default
756 * `makeRequest` handler.
757 *
758 * @return {Raven}
759 */
760 setTransport: function(transport) {
761 this._globalOptions.transport = transport;
762
763 return this;
764 },
765
766 /*
767 * Get the latest raw exception that was captured by Raven.
768 *
769 * @return {error}
770 */
771 lastException: function() {
772 return this._lastCapturedException;
773 },
774
775 /*
776 * Get the last event id
777 *
778 * @return {string}
779 */
780 lastEventId: function() {
781 return this._lastEventId;
782 },
783
784 /*
785 * Determine if Raven is setup and ready to go.
786 *
787 * @return {boolean}
788 */
789 isSetup: function() {
790 if (!this._hasJSON) return false; // needs JSON support
791 if (!this._globalServer) {
792 if (!this.ravenNotConfiguredError) {
793 this.ravenNotConfiguredError = true;
794 this._logDebug('error', 'Error: Raven has not been configured.');
795 }
796 return false;
797 }
798 return true;
799 },
800 693
801 afterLoad: function() {694 // Fire away!
802 // TODO: remove window dependence?695 this._send(data);
803 696
804 // Attempt to initialize Raven on load697 return this;
805 var RavenConfig = _window.RavenConfig;698 },
806 if (RavenConfig) {
807 this.config(RavenConfig.dsn, RavenConfig.config).install();
808 }
809 },
810 699
811 showReportDialog: function(options) {700 captureBreadcrumb: function (obj) {
812 if (701 var crumb = objectMerge(
813 !_document // doesn't work without a document (React native)702 {
814 )703 timestamp: now() / 1000,
815 return;704 },
705 obj,
706 );
816 707
817 options = options || {};708 if (isFunction(this._globalOptions.breadcrumbCallback)) {
709 var result = this._globalOptions.breadcrumbCallback(crumb);
818 710
819 var lastEventId = options.eventId || this.lastEventId();711 if (isObject(result) && !isEmptyObject(result)) {
820 if (!lastEventId) {712 crumb = result;
821 throw new RavenConfigError('Missing eventId');713 } else if (result === false) {
822 }714 return this;
715 }
716 }
823 717
824 var dsn = options.dsn || this._dsn;718 this._breadcrumbs.push(crumb);
825 if (!dsn) {719 if (
826 throw new RavenConfigError('Missing DSN');720 this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs
827 }721 ) {
722 this._breadcrumbs.shift();
723 }
724 return this;
725 },
828 726
829 var encode = encodeURIComponent;727 addPlugin: function (plugin /*arg1, arg2, ... argN*/) {
830 var qs = '';728 var pluginArgs = [].slice.call(arguments, 1);
831 qs += '?eventId=' + encode(lastEventId);
832 qs += '&dsn=' + encode(dsn);
833 729
834 var user = options.user || this._globalContext.user;730 this._plugins.push([plugin, pluginArgs]);
835 if (user) {731 if (this._isRavenInstalled) {
836 if (user.name) qs += '&name=' + encode(user.name);732 this._drainPlugins();
837 if (user.email) qs += '&email=' + encode(user.email);733 }
838 }
839 734
840 var globalServer = this._getGlobalServer(this._parseDSN(dsn));735 return this;
736 },
737
738 /*
739 * Set/clear a user to be sent along with the payload.
740 *
741 * @param {object} user An object representing user data [optional]
742 * @return {Raven}
743 */
744 setUserContext: function (user) {
745 // Intentionally do not merge here since that's an unexpected behavior.
746 this._globalContext.user = user;
747
748 return this;
749 },
750
751 /*
752 * Merge extra attributes to be sent along with the payload.
753 *
754 * @param {object} extra An object representing extra data [optional]
755 * @return {Raven}
756 */
757 setExtraContext: function (extra) {
758 this._mergeContext("extra", extra);
759
760 return this;
761 },
762
763 /*
764 * Merge tags to be sent along with the payload.
765 *
766 * @param {object} tags An object representing tags [optional]
767 * @return {Raven}
768 */
769 setTagsContext: function (tags) {
770 this._mergeContext("tags", tags);
771
772 return this;
773 },
774
775 /*
776 * Clear all of the context.
777 *
778 * @return {Raven}
779 */
780 clearContext: function () {
781 this._globalContext = {};
782
783 return this;
784 },
785
786 /*
787 * Get a copy of the current context. This cannot be mutated.
788 *
789 * @return {object} copy of context
790 */
791 getContext: function () {
792 // lol javascript
793 return JSON.parse(stringify(this._globalContext));
794 },
795
796 /*
797 * Set environment of application
798 *
799 * @param {string} environment Typically something like 'production'.
800 * @return {Raven}
801 */
802 setEnvironment: function (environment) {
803 this._globalOptions.environment = environment;
804
805 return this;
806 },
807
808 /*
809 * Set release version of application
810 *
811 * @param {string} release Typically something like a git SHA to identify version
812 * @return {Raven}
813 */
814 setRelease: function (release) {
815 this._globalOptions.release = release;
816
817 return this;
818 },
819
820 /*
821 * Set the dataCallback option
822 *
823 * @param {function} callback The callback to run which allows the
824 * data blob to be mutated before sending
825 * @return {Raven}
826 */
827 setDataCallback: function (callback) {
828 var original = this._globalOptions.dataCallback;
829 this._globalOptions.dataCallback = keepOriginalCallback(
830 original,
831 callback,
832 );
833 return this;
834 },
835
836 /*
837 * Set the breadcrumbCallback option
838 *
839 * @param {function} callback The callback to run which allows filtering
840 * or mutating breadcrumbs
841 * @return {Raven}
842 */
843 setBreadcrumbCallback: function (callback) {
844 var original = this._globalOptions.breadcrumbCallback;
845 this._globalOptions.breadcrumbCallback = keepOriginalCallback(
846 original,
847 callback,
848 );
849 return this;
850 },
851
852 /*
853 * Set the shouldSendCallback option
854 *
855 * @param {function} callback The callback to run which allows
856 * introspecting the blob before sending
857 * @return {Raven}
858 */
859 setShouldSendCallback: function (callback) {
860 var original = this._globalOptions.shouldSendCallback;
861 this._globalOptions.shouldSendCallback = keepOriginalCallback(
862 original,
863 callback,
864 );
865 return this;
866 },
867
868 /**
869 * Override the default HTTP transport mechanism that transmits data
870 * to the Sentry server.
871 *
872 * @param {function} transport Function invoked instead of the default
873 * `makeRequest` handler.
874 *
875 * @return {Raven}
876 */
877 setTransport: function (transport) {
878 this._globalOptions.transport = transport;
879
880 return this;
881 },
882
883 /*
884 * Get the latest raw exception that was captured by Raven.
885 *
886 * @return {error}
887 */
888 lastException: function () {
889 return this._lastCapturedException;
890 },
891
892 /*
893 * Get the last event id
894 *
895 * @return {string}
896 */
897 lastEventId: function () {
898 return this._lastEventId;
899 },
900
901 /*
902 * Determine if Raven is setup and ready to go.
903 *
904 * @return {boolean}
905 */
906 isSetup: function () {
907 if (!this._hasJSON) return false; // needs JSON support
908 if (!this._globalServer) {
909 if (!this.ravenNotConfiguredError) {
910 this.ravenNotConfiguredError = true;
911 this._logDebug(
912 "error",
913 "Error: Raven has not been configured.",
914 );
915 }
916 return false;
917 }
918 return true;
919 },
841 920
842 var script = _document.createElement('script');921 afterLoad: function () {
843 script.async = true;922 // TODO: remove window dependence?
844 script.src = globalServer + '/api/embed/error-page/' + qs;
845 (_document.head || _document.body).appendChild(script);
846 },
847 923
848 /**** Private functions ****/924 // Attempt to initialize Raven on load
849 _ignoreNextOnError: function() {925 var RavenConfig = _window.RavenConfig;
850 var self = this;926 if (RavenConfig) {
851 this._ignoreOnError += 1;927 this.config(RavenConfig.dsn, RavenConfig.config).install();
852 setTimeout(function() {928 }
853 // onerror should trigger before setTimeout929 },
854 self._ignoreOnError -= 1;
855 });
856 },
857 930
858 _triggerEvent: function(eventType, options) {931 showReportDialog: function (options) {
859 // NOTE: `event` is a native browser thing, so let's avoid conflicting with it932 if (
860 var evt, key;933 !_document // doesn't work without a document (React native)
934 )
935 return;
861 936
862 if (!this._hasDocument) return;937 options = options || {};
863 938
864 options = options || {};939 var lastEventId = options.eventId || this.lastEventId();
940 if (!lastEventId) {
941 throw new RavenConfigError("Missing eventId");
942 }
865 943
866 eventType = 'raven' + eventType.substr(0, 1).toUpperCase() + eventType.substr(1);944 var dsn = options.dsn || this._dsn;
945 if (!dsn) {
946 throw new RavenConfigError("Missing DSN");
947 }
867 948
868 if (_document.createEvent) {949 var encode = encodeURIComponent;
869 evt = _document.createEvent('HTMLEvents');950 var qs = "";
870 evt.initEvent(eventType, true, true);951 qs += "?eventId=" + encode(lastEventId);
871 } else {952 qs += "&dsn=" + encode(dsn);
872 evt = _document.createEventObject();
873 evt.eventType = eventType;
874 }
875 953
876 for (key in options)954 var user = options.user || this._globalContext.user;
877 if (hasKey(options, key)) {955 if (user) {
878 evt[key] = options[key];956 if (user.name) qs += "&name=" + encode(user.name);
879 }957 if (user.email) qs += "&email=" + encode(user.email);
958 }
880 959
881 if (_document.createEvent) {960 var globalServer = this._getGlobalServer(this._parseDSN(dsn));
882 // IE9 if standards961
883 _document.dispatchEvent(evt);962 var script = _document.createElement("script");
884 } else {963 script.async = true;
885 // IE8 regardless of Quirks or Standards964 script.src = globalServer + "/api/embed/error-page/" + qs;
886 // IE9 if quirks965 (_document.head || _document.body).appendChild(script);
887 try {966 },
888 _document.fireEvent('on' + evt.eventType.toLowerCase(), evt);967
889 } catch (e) {968 /**** Private functions ****/
890 // Do nothing969 _ignoreNextOnError: function () {
891 }970 var self = this;
892 }971 this._ignoreOnError += 1;
893 },972 setTimeout(function () {
894 973 // onerror should trigger before setTimeout
895 /**974 self._ignoreOnError -= 1;
896 * Wraps addEventListener to capture UI breadcrumbs975 });
897 * @param evtName the event name (e.g. "click")976 },
898 * @returns {Function}
899 * @private
900 */
901 _breadcrumbEventHandler: function(evtName) {
902 var self = this;
903 return function(evt) {
904 // reset keypress timeout; e.g. triggering a 'click' after
905 // a 'keypress' will reset the keypress debounce so that a new
906 // set of keypresses can be recorded
907 self._keypressTimeout = null;
908
909 // It's possible this handler might trigger multiple times for the same
910 // event (e.g. event propagation through node ancestors). Ignore if we've
911 // already captured the event.
912 if (self._lastCapturedEvent === evt) return;
913
914 self._lastCapturedEvent = evt;
915
916 // try/catch both:
917 // - accessing evt.target (see getsentry/raven-js#838, #768)
918 // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly
919 // can throw an exception in some circumstances.
920 var target;
921 try {
922 target = htmlTreeAsString(evt.target);
923 } catch (e) {
924 target = '<unknown>';
925 }
926 977
927 self.captureBreadcrumb({978 _triggerEvent: function (eventType, options) {
928 category: 'ui.' + evtName, // e.g. ui.click, ui.input979 // NOTE: `event` is a native browser thing, so let's avoid conflicting with it
929 message: target980 var evt, key;
930 });
931 };
932 },
933
934 /**
935 * Wraps addEventListener to capture keypress UI events
936 * @returns {Function}
937 * @private
938 */
939 _keypressEventHandler: function() {
940 var self = this,
941 debounceDuration = 1000; // milliseconds
942
943 // TODO: if somehow user switches keypress target before
944 // debounce timeout is triggered, we will only capture
945 // a single breadcrumb from the FIRST target (acceptable?)
946 return function(evt) {
947 var target;
948 try {
949 target = evt.target;
950 } catch (e) {
951 // just accessing event properties can throw an exception in some rare circumstances
952 // see: https://github.com/getsentry/raven-js/issues/838
953 return;
954 }
955 var tagName = target && target.tagName;
956
957 // only consider keypress events on actual input elements
958 // this will disregard keypresses targeting body (e.g. tabbing
959 // through elements, hotkeys, etc)
960 if (
961 !tagName ||
962 (tagName !== 'INPUT' && tagName !== 'TEXTAREA' && !target.isContentEditable)
963 )
964 return;
965
966 // record first keypress in a series, but ignore subsequent
967 // keypresses until debounce clears
968 var timeout = self._keypressTimeout;
969 if (!timeout) {
970 self._breadcrumbEventHandler('input')(evt);
971 }
972 clearTimeout(timeout);
973 self._keypressTimeout = setTimeout(function() {
974 self._keypressTimeout = null;
975 }, debounceDuration);
976 };
977 },
978
979 /**
980 * Captures a breadcrumb of type "navigation", normalizing input URLs
981 * @param to the originating URL
982 * @param from the target URL
983 * @private
984 */
985 _captureUrlChange: function(from, to) {
986 var parsedLoc = parseUrl(this._location.href);
987 var parsedTo = parseUrl(to);
988 var parsedFrom = parseUrl(from);
989
990 // because onpopstate only tells you the "new" (to) value of location.href, and
991 // not the previous (from) value, we need to track the value of the current URL
992 // state ourselves
993 this._lastHref = to;
994
995 // Use only the path component of the URL if the URL matches the current
996 // document (almost all the time when using pushState)
997 if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host)
998 to = parsedTo.relative;
999 if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host)
1000 from = parsedFrom.relative;
1001
1002 this.captureBreadcrumb({
1003 category: 'navigation',
1004 data: {
1005 to: to,
1006 from: from
1007 }
1008 });
1009 },
1010
1011 _patchFunctionToString: function() {
1012 var self = this;
1013 self._originalFunctionToString = Function.prototype.toString;
1014 // eslint-disable-next-line no-extend-native
1015 Function.prototype.toString = function() {
1016 if (typeof this === 'function' && this.__raven__) {
1017 return self._originalFunctionToString.apply(this.__orig__, arguments);
1018 }
1019 return self._originalFunctionToString.apply(this, arguments);
1020 };
1021 },
1022
1023 _unpatchFunctionToString: function() {
1024 if (this._originalFunctionToString) {
1025 // eslint-disable-next-line no-extend-native
1026 Function.prototype.toString = this._originalFunctionToString;
1027 }
1028 },
1029
1030 /**
1031 * Wrap timer functions and event targets to catch errors and provide
1032 * better metadata.
1033 */
1034 _instrumentTryCatch: function() {
1035 var self = this;
1036
1037 var wrappedBuiltIns = self._wrappedBuiltIns;
1038
1039 function wrapTimeFn(orig) {
1040 return function(fn, t) {
1041 // preserve arity
1042 // Make a copy of the arguments to prevent deoptimization
1043 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
1044 var args = new Array(arguments.length);
1045 for (var i = 0; i < args.length; ++i) {
1046 args[i] = arguments[i];
1047 }
1048 var originalCallback = args[0];
1049 if (isFunction(originalCallback)) {
1050 args[0] = self.wrap(originalCallback);
1051 }
1052 981
1053 // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it982 if (!this._hasDocument) return;
1054 // also supports only two arguments and doesn't care what this is, so we
1055 // can just call the original function directly.
1056 if (orig.apply) {
1057 return orig.apply(this, args);
1058 } else {
1059 return orig(args[0], args[1]);
1060 }
1061 };
1062 }
1063 983
1064 var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;984 options = options || {};
1065 985
1066 function wrapEventTarget(global) {986 eventType =
1067 var proto = _window[global] && _window[global].prototype;987 "raven" +
1068 if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) {988 eventType.substr(0, 1).toUpperCase() +
1069 fill(989 eventType.substr(1);
1070 proto,990
1071 'addEventListener',991 if (_document.createEvent) {
1072 function(orig) {992 evt = _document.createEvent("HTMLEvents");
1073 return function(evtName, fn, capture, secure) {993 evt.initEvent(eventType, true, true);
1074 // preserve arity994 } else {
1075 try {995 evt = _document.createEventObject();
1076 if (fn && fn.handleEvent) {996 evt.eventType = eventType;
1077 fn.handleEvent = self.wrap(fn.handleEvent);
1078 }997 }
1079 } catch (err) {
1080 // can sometimes get 'Permission denied to access property "handle Event'
1081 }
1082 998
1083 // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`999 for (key in options)
1084 // so that we don't have more than one wrapper function1000 if (hasKey(options, key)) {
1085 var before, clickHandler, keypressHandler;1001 evt[key] = options[key];
1002 }
1086 1003
1087 if (1004 if (_document.createEvent) {
1088 autoBreadcrumbs &&1005 // IE9 if standards
1089 autoBreadcrumbs.dom &&1006 _document.dispatchEvent(evt);
1090 (global === 'EventTarget' || global === 'Node')1007 } else {
1091 ) {1008 // IE8 regardless of Quirks or Standards
1092 // NOTE: generating multiple handlers per addEventListener invocation, should1009 // IE9 if quirks
1093 // revisit and verify we can just use one (almost certainly)1010 try {
1094 clickHandler = self._breadcrumbEventHandler('click');1011 _document.fireEvent(
1095 keypressHandler = self._keypressEventHandler();1012 "on" + evt.eventType.toLowerCase(),
1096 before = function(evt) {1013 evt,
1097 // need to intercept every DOM event in `before` argument, in case that1014 );
1098 // same wrapped method is re-used for different events (e.g. mousemove THEN click)1015 } catch (e) {
1099 // see #7241016 // Do nothing
1100 if (!evt) return;1017 }
1101 1018 }
1102 var eventType;1019 },
1020
1021 /**
1022 * Wraps addEventListener to capture UI breadcrumbs
1023 * @param evtName the event name (e.g. "click")
1024 * @returns {Function}
1025 * @private
1026 */
1027 _breadcrumbEventHandler: function (evtName) {
1028 var self = this;
1029 return function (evt) {
1030 // reset keypress timeout; e.g. triggering a 'click' after
1031 // a 'keypress' will reset the keypress debounce so that a new
1032 // set of keypresses can be recorded
1033 self._keypressTimeout = null;
1034
1035 // It's possible this handler might trigger multiple times for the same
1036 // event (e.g. event propagation through node ancestors). Ignore if we've
1037 // already captured the event.
1038 if (self._lastCapturedEvent === evt) return;
1039
1040 self._lastCapturedEvent = evt;
1041
1042 // try/catch both:
1043 // - accessing evt.target (see getsentry/raven-js#838, #768)
1044 // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly
1045 // can throw an exception in some circumstances.
1046 var target;
1047 try {
1048 target = htmlTreeAsString(evt.target);
1049 } catch (e) {
1050 target = "<unknown>";
1051 }
1052
1053 self.captureBreadcrumb({
1054 category: "ui." + evtName, // e.g. ui.click, ui.input
1055 message: target,
1056 });
1057 };
1058 },
1059
1060 /**
1061 * Wraps addEventListener to capture keypress UI events
1062 * @returns {Function}
1063 * @private
1064 */
1065 _keypressEventHandler: function () {
1066 var self = this,
1067 debounceDuration = 1000; // milliseconds
1068
1069 // TODO: if somehow user switches keypress target before
1070 // debounce timeout is triggered, we will only capture
1071 // a single breadcrumb from the FIRST target (acceptable?)
1072 return function (evt) {
1073 var target;
1103 try {1074 try {
1104 eventType = evt.type;1075 target = evt.target;
1105 } catch (e) {1076 } catch (e) {
1106 // just accessing event properties can throw an exception in some rare circumstances1077 // just accessing event properties can throw an exception in some rare circumstances
1107 // see: https://github.com/getsentry/raven-js/issues/8381078 // see: https://github.com/getsentry/raven-js/issues/838
1108 return;1079 return;
1109 }1080 }
1110 if (eventType === 'click') return clickHandler(evt);1081 var tagName = target && target.tagName;
1111 else if (eventType === 'keypress') return keypressHandler(evt);1082
1083 // only consider keypress events on actual input elements
1084 // this will disregard keypresses targeting body (e.g. tabbing
1085 // through elements, hotkeys, etc)
1086 if (
1087 !tagName ||
1088 (tagName !== "INPUT" &&
1089 tagName !== "TEXTAREA" &&
1090 !target.isContentEditable)
1091 )
1092 return;
1093
1094 // record first keypress in a series, but ignore subsequent
1095 // keypresses until debounce clears
1096 var timeout = self._keypressTimeout;
1097 if (!timeout) {
1098 self._breadcrumbEventHandler("input")(evt);
1099 }
1100 clearTimeout(timeout);
1101 self._keypressTimeout = setTimeout(function () {
1102 self._keypressTimeout = null;
1103 }, debounceDuration);
1112 };1104 };
1113 }1105 },
1114 return orig.call(1106
1115 this,1107 /**
1116 evtName,1108 * Captures a breadcrumb of type "navigation", normalizing input URLs
1117 self.wrap(fn, undefined, before),1109 * @param to the originating URL
1118 capture,1110 * @param from the target URL
1119 secure1111 * @private
1120 );1112 */
1121 };1113 _captureUrlChange: function (from, to) {
1122 },1114 var parsedLoc = parseUrl(this._location.href);
1123 wrappedBuiltIns1115 var parsedTo = parseUrl(to);
1124 );1116 var parsedFrom = parseUrl(from);
1125 fill(1117
1126 proto,1118 // because onpopstate only tells you the "new" (to) value of location.href, and
1127 'removeEventListener',1119 // not the previous (from) value, we need to track the value of the current URL
1128 function(orig) {1120 // state ourselves
1129 return function(evt, fn, capture, secure) {1121 this._lastHref = to;
1130 try {1122
1131 fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn);1123 // Use only the path component of the URL if the URL matches the current
1132 } catch (e) {1124 // document (almost all the time when using pushState)
1133 // ignore, accessing __raven_wrapper__ will throw in some Selenium environments1125 if (
1134 }1126 parsedLoc.protocol === parsedTo.protocol &&
1135 return orig.call(this, evt, fn, capture, secure);1127 parsedLoc.host === parsedTo.host
1136 };1128 )
1137 },1129 to = parsedTo.relative;
1138 wrappedBuiltIns1130 if (
1139 );1131 parsedLoc.protocol === parsedFrom.protocol &&
1140 }1132 parsedLoc.host === parsedFrom.host
1141 }1133 )
1134 from = parsedFrom.relative;
1135
1136 this.captureBreadcrumb({
1137 category: "navigation",
1138 data: {
1139 to: to,
1140 from: from,
1141 },
1142 });
1143 },
1144
1145 _patchFunctionToString: function () {
1146 var self = this;
1147 self._originalFunctionToString = Function.prototype.toString;
1148 // eslint-disable-next-line no-extend-native
1149 Function.prototype.toString = function () {
1150 if (typeof this === "function" && this.__raven__) {
1151 return self._originalFunctionToString.apply(
1152 this.__orig__,
1153 arguments,
1154 );
1155 }
1156 return self._originalFunctionToString.apply(this, arguments);
1157 };
1158 },
1142 1159
1143 fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns);1160 _unpatchFunctionToString: function () {
1144 fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns);1161 if (this._originalFunctionToString) {
1145 if (_window.requestAnimationFrame) {1162 // eslint-disable-next-line no-extend-native
1146 fill(1163 Function.prototype.toString = this._originalFunctionToString;
1147 _window,1164 }
1148 'requestAnimationFrame',1165 },
1149 function(orig) {1166
1150 return function(cb) {1167 /**
1151 return orig(self.wrap(cb));1168 * Wrap timer functions and event targets to catch errors and provide
1152 };1169 * better metadata.
1153 },1170 */
1154 wrappedBuiltIns1171 _instrumentTryCatch: function () {
1155 );1172 var self = this;
1156 }1173
1174 var wrappedBuiltIns = self._wrappedBuiltIns;
1175
1176 function wrapTimeFn(orig) {
1177 return function (fn, t) {
1178 // preserve arity
1179 // Make a copy of the arguments to prevent deoptimization
1180 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
1181 var args = new Array(arguments.length);
1182 for (var i = 0; i < args.length; ++i) {
1183 args[i] = arguments[i];
1184 }
1185 var originalCallback = args[0];
1186 if (isFunction(originalCallback)) {
1187 args[0] = self.wrap(originalCallback);
1188 }
1189
1190 // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
1191 // also supports only two arguments and doesn't care what this is, so we
1192 // can just call the original function directly.
1193 if (orig.apply) {
1194 return orig.apply(this, args);
1195 } else {
1196 return orig(args[0], args[1]);
1197 }
1198 };
1199 }
1157 1200
1158 // event targets borrowed from bugsnag-js:1201 var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
1159 // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L6661202
1160 var eventTargets = [1203 function wrapEventTarget(global) {
1161 'EventTarget',1204 var proto = _window[global] && _window[global].prototype;
1162 'Window',1205 if (
1163 'Node',1206 proto &&
1164 'ApplicationCache',1207 proto.hasOwnProperty &&
1165 'AudioTrackList',1208 proto.hasOwnProperty("addEventListener")
1166 'ChannelMergerNode',1209 ) {
1167 'CryptoOperation',1210 fill(
1168 'EventSource',1211 proto,
1169 'FileReader',1212 "addEventListener",
1170 'HTMLUnknownElement',1213 function (orig) {
1171 'IDBDatabase',1214 return function (evtName, fn, capture, secure) {
1172 'IDBRequest',1215 // preserve arity
1173 'IDBTransaction',1216 try {
1174 'KeyOperation',1217 if (fn && fn.handleEvent) {
1175 'MediaController',1218 fn.handleEvent = self.wrap(fn.handleEvent);
1176 'MessagePort',1219 }
1177 'ModalWindow',1220 } catch (err) {
1178 'Notification',1221 // can sometimes get 'Permission denied to access property "handle Event'
1179 'SVGElementInstance',1222 }
1180 'Screen',1223
1181 'TextTrack',1224 // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`
1182 'TextTrackCue',1225 // so that we don't have more than one wrapper function
1183 'TextTrackList',1226 var before, clickHandler, keypressHandler;
1184 'WebSocket',1227
1185 'WebSocketWorker',1228 if (
1186 'Worker',1229 autoBreadcrumbs &&
1187 'XMLHttpRequest',1230 autoBreadcrumbs.dom &&
1188 'XMLHttpRequestEventTarget',1231 (global === "EventTarget" || global === "Node")
1189 'XMLHttpRequestUpload'1232 ) {
1190 ];1233 // NOTE: generating multiple handlers per addEventListener invocation, should
1191 for (var i = 0; i < eventTargets.length; i++) {1234 // revisit and verify we can just use one (almost certainly)
1192 wrapEventTarget(eventTargets[i]);1235 clickHandler =
1193 }1236 self._breadcrumbEventHandler("click");
1194 },1237 keypressHandler = self._keypressEventHandler();
1195 1238 before = function (evt) {
1196 /**1239 // need to intercept every DOM event in `before` argument, in case that
1197 * Instrument browser built-ins w/ breadcrumb capturing1240 // same wrapped method is re-used for different events (e.g. mousemove THEN click)
1198 * - XMLHttpRequests1241 // see #724
1199 * - DOM interactions (click/typing)1242 if (!evt) return;
1200 * - window.location changes1243
1201 * - console1244 var eventType;
1202 *1245 try {
1203 * Can be disabled or individually configured via the `autoBreadcrumbs` config option1246 eventType = evt.type;
1204 */1247 } catch (e) {
1205 _instrumentBreadcrumbs: function() {1248 // just accessing event properties can throw an exception in some rare circumstances
1206 var self = this;1249 // see: https://github.com/getsentry/raven-js/issues/838
1207 var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;1250 return;
1208 1251 }
1209 var wrappedBuiltIns = self._wrappedBuiltIns;1252 if (eventType === "click")
1210 1253 return clickHandler(evt);
1211 function wrapProp(prop, xhr) {1254 else if (eventType === "keypress")
1212 if (prop in xhr && isFunction(xhr[prop])) {1255 return keypressHandler(evt);
1213 fill(xhr, prop, function(orig) {1256 };
1214 return self.wrap(orig);1257 }
1215 }); // intentionally don't track filled methods on XHR instances1258 return orig.call(
1216 }1259 this,
1217 }1260 evtName,
1261 self.wrap(fn, undefined, before),
1262 capture,
1263 secure,
1264 );
1265 };
1266 },
1267 wrappedBuiltIns,
1268 );
1269 fill(
1270 proto,
1271 "removeEventListener",
1272 function (orig) {
1273 return function (evt, fn, capture, secure) {
1274 try {
1275 fn =
1276 fn &&
1277 (fn.__raven_wrapper__
1278 ? fn.__raven_wrapper__
1279 : fn);
1280 } catch (e) {
1281 // ignore, accessing __raven_wrapper__ will throw in some Selenium environments
1282 }
1283 return orig.call(this, evt, fn, capture, secure);
1284 };
1285 },
1286 wrappedBuiltIns,
1287 );
1288 }
1289 }
1218 1290
1219 if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) {1291 fill(_window, "setTimeout", wrapTimeFn, wrappedBuiltIns);
1220 var xhrproto = XMLHttpRequest.prototype;1292 fill(_window, "setInterval", wrapTimeFn, wrappedBuiltIns);
1221 fill(1293 if (_window.requestAnimationFrame) {
1222 xhrproto,1294 fill(
1223 'open',1295 _window,
1224 function(origOpen) {1296 "requestAnimationFrame",
1225 return function(method, url) {1297 function (orig) {
1226 // preserve arity1298 return function (cb) {
1227 1299 return orig(self.wrap(cb));
1228 // if Sentry key appears in URL, don't capture1300 };
1229 if (isString(url) && url.indexOf(self._globalKey) === -1) {1301 },
1230 this.__raven_xhr = {1302 wrappedBuiltIns,
1231 method: method,1303 );
1232 url: url,1304 }
1233 status_code: null
1234 };
1235 }
1236 1305
1237 return origOpen.apply(this, arguments);1306 // event targets borrowed from bugsnag-js:
1238 };1307 // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666
1239 },1308 var eventTargets = [
1240 wrappedBuiltIns1309 "EventTarget",
1241 );1310 "Window",
1242 1311 "Node",
1243 fill(1312 "ApplicationCache",
1244 xhrproto,1313 "AudioTrackList",
1245 'send',1314 "ChannelMergerNode",
1246 function(origSend) {1315 "CryptoOperation",
1247 return function(data) {1316 "EventSource",
1248 // preserve arity1317 "FileReader",
1249 var xhr = this;1318 "HTMLUnknownElement",
1250 1319 "IDBDatabase",
1251 function onreadystatechangeHandler() {1320 "IDBRequest",
1252 if (xhr.__raven_xhr && xhr.readyState === 4) {1321 "IDBTransaction",
1253 try {1322 "KeyOperation",
1254 // touching statusCode in some platforms throws1323 "MediaController",
1255 // an exception1324 "MessagePort",
1256 xhr.__raven_xhr.status_code = xhr.status;1325 "ModalWindow",
1257 } catch (e) {1326 "Notification",
1258 /* do nothing */1327 "SVGElementInstance",
1328 "Screen",
1329 "TextTrack",
1330 "TextTrackCue",
1331 "TextTrackList",
1332 "WebSocket",
1333 "WebSocketWorker",
1334 "Worker",
1335 "XMLHttpRequest",
1336 "XMLHttpRequestEventTarget",
1337 "XMLHttpRequestUpload",
1338 ];
1339 for (var i = 0; i < eventTargets.length; i++) {
1340 wrapEventTarget(eventTargets[i]);
1341 }
1342 },
1343
1344 /**
1345 * Instrument browser built-ins w/ breadcrumb capturing
1346 * - XMLHttpRequests
1347 * - DOM interactions (click/typing)
1348 * - window.location changes
1349 * - console
1350 *
1351 * Can be disabled or individually configured via the `autoBreadcrumbs` config option
1352 */
1353 _instrumentBreadcrumbs: function () {
1354 var self = this;
1355 var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;
1356
1357 var wrappedBuiltIns = self._wrappedBuiltIns;
1358
1359 function wrapProp(prop, xhr) {
1360 if (prop in xhr && isFunction(xhr[prop])) {
1361 fill(xhr, prop, function (orig) {
1362 return self.wrap(orig);
1363 }); // intentionally don't track filled methods on XHR instances
1364 }
1259 }1365 }
1260 1366
1261 self.captureBreadcrumb({1367 if (autoBreadcrumbs.xhr && "XMLHttpRequest" in _window) {
1262 type: 'http',1368 var xhrproto = XMLHttpRequest.prototype;
1263 category: 'xhr',1369 fill(
1264 data: xhr.__raven_xhr1370 xhrproto,
1265 });1371 "open",
1266 }1372 function (origOpen) {
1267 }1373 return function (method, url) {
1374 // preserve arity
1375
1376 // if Sentry key appears in URL, don't capture
1377 if (
1378 isString(url) &&
1379 url.indexOf(self._globalKey) === -1
1380 ) {
1381 this.__raven_xhr = {
1382 method: method,
1383 url: url,
1384 status_code: null,
1385 };
1386 }
1387
1388 return origOpen.apply(this, arguments);
1389 };
1390 },
1391 wrappedBuiltIns,
1392 );
1393
1394 fill(
1395 xhrproto,
1396 "send",
1397 function (origSend) {
1398 return function (data) {
1399 // preserve arity
1400 var xhr = this;
1401
1402 function onreadystatechangeHandler() {
1403 if (xhr.__raven_xhr && xhr.readyState === 4) {
1404 try {
1405 // touching statusCode in some platforms throws
1406 // an exception
1407 xhr.__raven_xhr.status_code = xhr.status;
1408 } catch (e) {
1409 /* do nothing */
1410 }
1411
1412 self.captureBreadcrumb({
1413 type: "http",
1414 category: "xhr",
1415 data: xhr.__raven_xhr,
1416 });
1417 }
1418 }
1419
1420 var props = ["onload", "onerror", "onprogress"];
1421 for (var j = 0; j < props.length; j++) {
1422 wrapProp(props[j], xhr);
1423 }
1424
1425 if (
1426 "onreadystatechange" in xhr &&
1427 isFunction(xhr.onreadystatechange)
1428 ) {
1429 fill(
1430 xhr,
1431 "onreadystatechange",
1432 function (orig) {
1433 return self.wrap(
1434 orig,
1435 undefined,
1436 onreadystatechangeHandler,
1437 );
1438 } /* intentionally don't track this instrumentation */,
1439 );
1440 } else {
1441 // if onreadystatechange wasn't actually set by the page on this xhr, we
1442 // are free to set our own and capture the breadcrumb
1443 xhr.onreadystatechange = onreadystatechangeHandler;
1444 }
1445
1446 return origSend.apply(this, arguments);
1447 };
1448 },
1449 wrappedBuiltIns,
1450 );
1451 }
1268 1452
1269 var props = ['onload', 'onerror', 'onprogress'];1453 if (autoBreadcrumbs.xhr && "fetch" in _window) {
1270 for (var j = 0; j < props.length; j++) {1454 fill(
1271 wrapProp(props[j], xhr);1455 _window,
1272 }1456 "fetch",
1457 function (origFetch) {
1458 return function (fn, t) {
1459 // preserve arity
1460 // Make a copy of the arguments to prevent deoptimization
1461 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
1462 var args = new Array(arguments.length);
1463 for (var i = 0; i < args.length; ++i) {
1464 args[i] = arguments[i];
1465 }
1466
1467 var fetchInput = args[0];
1468 var method = "GET";
1469 var url;
1470
1471 if (typeof fetchInput === "string") {
1472 url = fetchInput;
1473 } else if (
1474 "Request" in _window &&
1475 fetchInput instanceof _window.Request
1476 ) {
1477 url = fetchInput.url;
1478 if (fetchInput.method) {
1479 method = fetchInput.method;
1480 }
1481 } else {
1482 url = "" + fetchInput;
1483 }
1484
1485 if (args[1] && args[1].method) {
1486 method = args[1].method;
1487 }
1488
1489 var fetchData = {
1490 method: method,
1491 url: url,
1492 status_code: null,
1493 };
1494
1495 self.captureBreadcrumb({
1496 type: "http",
1497 category: "fetch",
1498 data: fetchData,
1499 });
1500
1501 return origFetch
1502 .apply(this, args)
1503 .then(function (response) {
1504 fetchData.status_code = response.status;
1505
1506 return response;
1507 });
1508 };
1509 },
1510 wrappedBuiltIns,
1511 );
1512 }
1273 1513
1274 if ('onreadystatechange' in xhr && isFunction(xhr.onreadystatechange)) {1514 // Capture breadcrumbs from any click that is unhandled / bubbled up all the way
1275 fill(1515 // to the document. Do this before we instrument addEventListener.
1276 xhr,1516 if (autoBreadcrumbs.dom && this._hasDocument) {
1277 'onreadystatechange',1517 if (_document.addEventListener) {
1278 function(orig) {1518 _document.addEventListener(
1279 return self.wrap(orig, undefined, onreadystatechangeHandler);1519 "click",
1280 } /* intentionally don't track this instrumentation */1520 self._breadcrumbEventHandler("click"),
1281 );1521 false,
1282 } else {1522 );
1283 // if onreadystatechange wasn't actually set by the page on this xhr, we1523 _document.addEventListener(
1284 // are free to set our own and capture the breadcrumb1524 "keypress",
1285 xhr.onreadystatechange = onreadystatechangeHandler;1525 self._keypressEventHandler(),
1286 }1526 false,
1527 );
1528 } else {
1529 // IE8 Compatibility
1530 _document.attachEvent(
1531 "onclick",
1532 self._breadcrumbEventHandler("click"),
1533 );
1534 _document.attachEvent(
1535 "onkeypress",
1536 self._keypressEventHandler(),
1537 );
1538 }
1539 }
1287 1540
1288 return origSend.apply(this, arguments);1541 // record navigation (URL) changes
1289 };1542 // NOTE: in Chrome App environment, touching history.pushState, *even inside
1290 },1543 // a try/catch block*, will cause Chrome to output an error to console.error
1291 wrappedBuiltIns1544 // borrowed from: https://github.com/angular/angular.js/pull/13945/files
1292 );1545 var chrome = _window.chrome;
1293 }1546 var isChromePackagedApp =
1547 chrome && chrome.app && chrome.app.runtime;
1548 var hasPushAndReplaceState =
1549 !isChromePackagedApp &&
1550 _window.history &&
1551 history.pushState &&
1552 history.replaceState;
1553 if (autoBreadcrumbs.location && hasPushAndReplaceState) {
1554 // TODO: remove onpopstate handler on uninstall()
1555 var oldOnPopState = _window.onpopstate;
1556 _window.onpopstate = function () {
1557 var currentHref = self._location.href;
1558 self._captureUrlChange(self._lastHref, currentHref);
1559
1560 if (oldOnPopState) {
1561 return oldOnPopState.apply(this, arguments);
1562 }
1563 };
1564
1565 var historyReplacementFunction = function (origHistFunction) {
1566 // note history.pushState.length is 0; intentionally not declaring
1567 // params to preserve 0 arity
1568 return function (/* state, title, url */) {
1569 var url = arguments.length > 2 ? arguments[2] : undefined;
1570
1571 // url argument is optional
1572 if (url) {
1573 // coerce to string (this is what pushState does)
1574 self._captureUrlChange(self._lastHref, url + "");
1575 }
1576
1577 return origHistFunction.apply(this, arguments);
1578 };
1579 };
1580
1581 fill(
1582 history,
1583 "pushState",
1584 historyReplacementFunction,
1585 wrappedBuiltIns,
1586 );
1587 fill(
1588 history,
1589 "replaceState",
1590 historyReplacementFunction,
1591 wrappedBuiltIns,
1592 );
1593 }
1294 1594
1295 if (autoBreadcrumbs.xhr && 'fetch' in _window) {1595 if (
1296 fill(1596 autoBreadcrumbs.console &&
1297 _window,1597 "console" in _window &&
1298 'fetch',1598 console.log
1299 function(origFetch) {1599 ) {
1300 return function(fn, t) {1600 // console
1301 // preserve arity1601 var consoleMethodCallback = function (msg, data) {
1302 // Make a copy of the arguments to prevent deoptimization1602 self.captureBreadcrumb({
1303 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments1603 message: msg,
1304 var args = new Array(arguments.length);1604 level: data.level,
1305 for (var i = 0; i < args.length; ++i) {1605 category: "console",
1306 args[i] = arguments[i];1606 });
1307 }1607 };
1608
1609 each(
1610 ["debug", "info", "warn", "error", "log"],
1611 function (_, level) {
1612 wrapConsoleMethod(console, level, consoleMethodCallback);
1613 },
1614 );
1615 }
1616 },
1308 1617
1309 var fetchInput = args[0];1618 _restoreBuiltIns: function () {
1310 var method = 'GET';1619 // restore any wrapped builtins
1311 var url;1620 var builtin;
1621 while (this._wrappedBuiltIns.length) {
1622 builtin = this._wrappedBuiltIns.shift();
1312 1623
1313 if (typeof fetchInput === 'string') {1624 var obj = builtin[0],
1314 url = fetchInput;1625 name = builtin[1],
1315 } else if ('Request' in _window && fetchInput instanceof _window.Request) {1626 orig = builtin[2];
1316 url = fetchInput.url;
1317 if (fetchInput.method) {
1318 method = fetchInput.method;
1319 }
1320 } else {
1321 url = '' + fetchInput;
1322 }
1323 1627
1324 if (args[1] && args[1].method) {1628 obj[name] = orig;
1325 method = args[1].method;1629 }
1326 }1630 },
1327 1631
1328 var fetchData = {1632 _drainPlugins: function () {
1329 method: method,1633 var self = this;
1330 url: url,
1331 status_code: null
1332 };
1333 1634
1334 self.captureBreadcrumb({1635 // FIX ME TODO
1335 type: 'http',1636 each(this._plugins, function (_, plugin) {
1336 category: 'fetch',1637 var installer = plugin[0];
1337 data: fetchData1638 var args = plugin[1];
1338 });1639 installer.apply(self, [self].concat(args));
1640 });
1641 },
1339 1642
1340 return origFetch.apply(this, args).then(function(response) {1643 _parseDSN: function (str) {
1341 fetchData.status_code = response.status;1644 var m = dsnPattern.exec(str),
1645 dsn = {},
1646 i = 7;
1342 1647
1343 return response;1648 try {
1344 });1649 while (i--) dsn[dsnKeys[i]] = m[i] || "";
1345 };1650 } catch (e) {
1346 },1651 throw new RavenConfigError("Invalid DSN: " + str);
1347 wrappedBuiltIns1652 }
1348 );
1349 }
1350 1653
1351 // Capture breadcrumbs from any click that is unhandled / bubbled up all the way1654 if (dsn.pass && !this._globalOptions.allowSecretKey) {
1352 // to the document. Do this before we instrument addEventListener.1655 throw new RavenConfigError(
1353 if (autoBreadcrumbs.dom && this._hasDocument) {1656 "Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key",
1354 if (_document.addEventListener) {1657 );
1355 _document.addEventListener('click', self._breadcrumbEventHandler('click'), false);1658 }
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 }
1363 1659
1364 // record navigation (URL) changes1660 return dsn;
1365 // NOTE: in Chrome App environment, touching history.pushState, *even inside1661 },
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 }
1398 1662
1399 return origHistFunction.apply(this, arguments);1663 _getGlobalServer: function (uri) {
1400 };1664 // assemble the endpoint from the uri pieces
1401 };1665 var globalServer =
1666 "//" + uri.host + (uri.port ? ":" + uri.port : "");
1402 1667
1403 fill(history, 'pushState', historyReplacementFunction, wrappedBuiltIns);1668 if (uri.protocol) {
1404 fill(history, 'replaceState', historyReplacementFunction, wrappedBuiltIns);1669 globalServer = uri.protocol + ":" + globalServer;
1405 }1670 }
1671 return globalServer;
1672 },
1406 1673
1407 if (autoBreadcrumbs.console && 'console' in _window && console.log) {1674 _handleOnErrorStackInfo: function () {
1408 // console1675 // if we are intentionally ignoring errors via onerror, bail out
1409 var consoleMethodCallback = function(msg, data) {1676 if (!this._ignoreOnError) {
1410 self.captureBreadcrumb({1677 this._handleStackInfo.apply(this, arguments);
1411 message: msg,1678 }
1412 level: data.level,1679 },
1413 category: 'console'
1414 });
1415 };
1416
1417 each(['debug', 'info', 'warn', 'error', 'log'], function(_, level) {
1418 wrapConsoleMethod(console, level, consoleMethodCallback);
1419 });
1420 }
1421 },
1422 1680
1423 _restoreBuiltIns: function() {1681 _handleStackInfo: function (stackInfo, options) {
1424 // restore any wrapped builtins1682 var frames = this._prepareFrames(stackInfo, options);
1425 var builtin;
1426 while (this._wrappedBuiltIns.length) {
1427 builtin = this._wrappedBuiltIns.shift();
1428 1683
1429 var obj = builtin[0],1684 this._triggerEvent("handle", {
1430 name = builtin[1],1685 stackInfo: stackInfo,
1431 orig = builtin[2];1686 options: options,
1687 });
1432 1688
1433 obj[name] = orig;1689 this._processException(
1434 }1690 stackInfo.name,
1435 },1691 stackInfo.message,
1436 1692 stackInfo.url,
1437 _drainPlugins: function() {1693 stackInfo.lineno,
1438 var self = this;1694 frames,
1439 1695 options,
1440 // FIX ME TODO1696 );
1441 each(this._plugins, function(_, plugin) {1697 },
1442 var installer = plugin[0];1698
1443 var args = plugin[1];1699 _prepareFrames: function (stackInfo, options) {
1444 installer.apply(self, [self].concat(args));1700 var self = this;
1445 });1701 var frames = [];
1446 },1702 if (stackInfo.stack && stackInfo.stack.length) {
1447 1703 each(stackInfo.stack, function (i, stack) {
1448 _parseDSN: function(str) {1704 var frame = self._normalizeFrame(stack, stackInfo.url);
1449 var m = dsnPattern.exec(str),1705 if (frame) {
1450 dsn = {},1706 frames.push(frame);
1451 i = 7;1707 }
1452 1708 });
1453 try {1709
1454 while (i--) dsn[dsnKeys[i]] = m[i] || '';1710 // e.g. frames captured via captureMessage throw
1455 } catch (e) {1711 if (options && options.trimHeadFrames) {
1456 throw new RavenConfigError('Invalid DSN: ' + str);1712 for (
1457 }1713 var j = 0;
1714 j < options.trimHeadFrames && j < frames.length;
1715 j++
1716 ) {
1717 frames[j].in_app = false;
1718 }
1719 }
1720 }
1721 frames = frames.slice(0, this._globalOptions.stackTraceLimit);
1722 return frames;
1723 },
1724
1725 _normalizeFrame: function (frame, stackInfoUrl) {
1726 // normalize the frames data
1727 var normalized = {
1728 filename: frame.url,
1729 lineno: frame.line,
1730 colno: frame.column,
1731 function: frame.func || "?",
1732 };
1458 1733
1459 if (dsn.pass && !this._globalOptions.allowSecretKey) {1734 // Case when we don't have any information about the error
1460 throw new RavenConfigError(1735 // E.g. throwing a string or raw object, instead of an `Error` in Firefox
1461 'Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'1736 // Generating synthetic error doesn't add any value here
1462 );1737 //
1463 }1738 // We should probably somehow let a user know that they should fix their code
1739 if (!frame.url) {
1740 normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler
1741 }
1464 1742
1465 return dsn;1743 normalized.in_app = !(
1466 },1744 // determine if an exception came from outside of our app
1745 // first we check the global includePaths list.
1746 (
1747 (!!this._globalOptions.includePaths.test &&
1748 !this._globalOptions.includePaths.test(
1749 normalized.filename,
1750 )) ||
1751 // Now we check for fun, if the function name is Raven or TraceKit
1752 /(Raven|TraceKit)\./.test(normalized["function"]) ||
1753 // finally, we do a last ditch effort and check for raven.min.js
1754 /raven\.(min\.)?js$/.test(normalized.filename)
1755 )
1756 );
1757
1758 return normalized;
1759 },
1760
1761 _processException: function (
1762 type,
1763 message,
1764 fileurl,
1765 lineno,
1766 frames,
1767 options,
1768 ) {
1769 var prefixedMessage =
1770 (type ? type + ": " : "") + (message || "");
1771 if (
1772 !!this._globalOptions.ignoreErrors.test &&
1773 (this._globalOptions.ignoreErrors.test(message) ||
1774 this._globalOptions.ignoreErrors.test(prefixedMessage))
1775 ) {
1776 return;
1777 }
1467 1778
1468 _getGlobalServer: function(uri) {1779 var stacktrace;
1469 // assemble the endpoint from the uri pieces1780
1470 var globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : '');1781 if (frames && frames.length) {
1782 fileurl = frames[0].filename || fileurl;
1783 // Sentry expects frames oldest to newest
1784 // and JS sends them as newest to oldest
1785 frames.reverse();
1786 stacktrace = { frames: frames };
1787 } else if (fileurl) {
1788 stacktrace = {
1789 frames: [
1790 {
1791 filename: fileurl,
1792 lineno: lineno,
1793 in_app: true,
1794 },
1795 ],
1796 };
1797 }
1471 1798
1472 if (uri.protocol) {1799 if (
1473 globalServer = uri.protocol + ':' + globalServer;1800 !!this._globalOptions.ignoreUrls.test &&
1474 }1801 this._globalOptions.ignoreUrls.test(fileurl)
1475 return globalServer;1802 ) {
1476 },1803 return;
1804 }
1477 1805
1478 _handleOnErrorStackInfo: function() {1806 if (
1479 // if we are intentionally ignoring errors via onerror, bail out1807 !!this._globalOptions.whitelistUrls.test &&
1480 if (!this._ignoreOnError) {1808 !this._globalOptions.whitelistUrls.test(fileurl)
1481 this._handleStackInfo.apply(this, arguments);1809 ) {
1482 }1810 return;
1483 },1811 }
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 1812
1514 // e.g. frames captured via captureMessage throw1813 var data = objectMerge(
1515 if (options && options.trimHeadFrames) {1814 {
1516 for (var j = 0; j < options.trimHeadFrames && j < frames.length; j++) {1815 // sentry.interfaces.Exception
1517 frames[j].in_app = false;1816 exception: {
1518 }1817 values: [
1519 }1818 {
1520 }1819 type: type,
1521 frames = frames.slice(0, this._globalOptions.stackTraceLimit);1820 value: message,
1522 return frames;1821 stacktrace: stacktrace,
1523 },1822 },
1524 1823 ],
1525 _normalizeFrame: function(frame, stackInfoUrl) {1824 },
1526 // normalize the frames data1825 culprit: fileurl,
1527 var normalized = {1826 },
1528 filename: frame.url,1827 options,
1529 lineno: frame.line,1828 );
1530 colno: frame.column,1829
1531 function: frame.func || '?'1830 // Fire away!
1532 };1831 this._send(data);
1533 1832 },
1534 // Case when we don't have any information about the error1833
1535 // E.g. throwing a string or raw object, instead of an `Error` in Firefox1834 _trimPacket: function (data) {
1536 // Generating synthetic error doesn't add any value here1835 // For now, we only want to truncate the two different messages
1537 //1836 // but this could/should be expanded to just trim everything
1538 // We should probably somehow let a user know that they should fix their code1837 var max = this._globalOptions.maxMessageLength;
1539 if (!frame.url) {1838 if (data.message) {
1540 normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler1839 data.message = truncate(data.message, max);
1541 }1840 }
1841 if (data.exception) {
1842 var exception = data.exception.values[0];
1843 exception.value = truncate(exception.value, max);
1844 }
1542 1845
1543 normalized.in_app = !// determine if an exception came from outside of our app1846 var request = data.request;
1544 // first we check the global includePaths list.1847 if (request) {
1545 (1848 if (request.url) {
1546 (!!this._globalOptions.includePaths.test &&1849 request.url = truncate(
1547 !this._globalOptions.includePaths.test(normalized.filename)) ||1850 request.url,
1548 // Now we check for fun, if the function name is Raven or TraceKit1851 this._globalOptions.maxUrlLength,
1549 /(Raven|TraceKit)\./.test(normalized['function']) ||1852 );
1550 // finally, we do a last ditch effort and check for raven.min.js1853 }
1551 /raven\.(min\.)?js$/.test(normalized.filename)1854 if (request.Referer) {
1552 );1855 request.Referer = truncate(
1553 1856 request.Referer,
1554 return normalized;1857 this._globalOptions.maxUrlLength,
1555 },1858 );
1556 1859 }
1557 _processException: function(type, message, fileurl, lineno, frames, options) {1860 }
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 1861
1567 var stacktrace;1862 if (data.breadcrumbs && data.breadcrumbs.values)
1568 1863 this._trimBreadcrumbs(data.breadcrumbs);
1569 if (frames && frames.length) {1864
1570 fileurl = frames[0].filename || fileurl;1865 return data;
1571 // Sentry expects frames oldest to newest1866 },
1572 // and JS sends them as newest to oldest1867
1573 frames.reverse();1868 /**
1574 stacktrace = {frames: frames};1869 * Truncate breadcrumb values (right now just URLs)
1575 } else if (fileurl) {1870 */
1576 stacktrace = {1871 _trimBreadcrumbs: function (breadcrumbs) {
1577 frames: [1872 // known breadcrumb properties with urls
1578 {1873 // TODO: also consider arbitrary prop values that start with (https?)?://
1579 filename: fileurl,1874 var urlProps = ["to", "from", "url"],
1580 lineno: lineno,1875 urlProp,
1581 in_app: true1876 crumb,
1582 }1877 data;
1583 ]1878
1584 };1879 for (var i = 0; i < breadcrumbs.values.length; ++i) {
1585 }1880 crumb = breadcrumbs.values[i];
1881 if (
1882 !crumb.hasOwnProperty("data") ||
1883 !isObject(crumb.data) ||
1884 objectFrozen(crumb.data)
1885 )
1886 continue;
1887
1888 data = objectMerge({}, crumb.data);
1889 for (var j = 0; j < urlProps.length; ++j) {
1890 urlProp = urlProps[j];
1891 if (data.hasOwnProperty(urlProp) && data[urlProp]) {
1892 data[urlProp] = truncate(
1893 data[urlProp],
1894 this._globalOptions.maxUrlLength,
1895 );
1896 }
1897 }
1898 breadcrumbs.values[i].data = data;
1899 }
1900 },
1586 1901
1587 if (1902 _getHttpData: function () {
1588 !!this._globalOptions.ignoreUrls.test &&1903 if (!this._hasNavigator && !this._hasDocument) return;
1589 this._globalOptions.ignoreUrls.test(fileurl)1904 var httpData = {};
1590 ) {
1591 return;
1592 }
1593 1905
1594 if (1906 if (this._hasNavigator && _navigator.userAgent) {
1595 !!this._globalOptions.whitelistUrls.test &&1907 httpData.headers = {
1596 !this._globalOptions.whitelistUrls.test(fileurl)1908 "User-Agent": navigator.userAgent,
1597 ) {1909 };
1598 return;1910 }
1599 }
1600 1911
1601 var data = objectMerge(1912 if (this._hasDocument) {
1602 {1913 if (_document.location && _document.location.href) {
1603 // sentry.interfaces.Exception1914 httpData.url = _document.location.href;
1604 exception: {1915 }
1605 values: [1916 if (_document.referrer) {
1606 {1917 if (!httpData.headers) httpData.headers = {};
1607 type: type,1918 httpData.headers.Referer = _document.referrer;
1608 value: message,1919 }
1609 stacktrace: stacktrace1920 }
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 }
1633
1634 var request = data.request;
1635 if (request) {
1636 if (request.url) {
1637 request.url = truncate(request.url, this._globalOptions.maxUrlLength);
1638 }
1639 if (request.Referer) {
1640 request.Referer = truncate(request.Referer, this._globalOptions.maxUrlLength);
1641 }
1642 }
1643
1644 if (data.breadcrumbs && data.breadcrumbs.values)
1645 this._trimBreadcrumbs(data.breadcrumbs);
1646
1647 return data;
1648 },
1649
1650 /**
1651 * Truncate breadcrumb values (right now just URLs)
1652 */
1653 _trimBreadcrumbs: function(breadcrumbs) {
1654 // known breadcrumb properties with urls
1655 // TODO: also consider arbitrary prop values that start with (https?)?://
1656 var urlProps = ['to', 'from', 'url'],
1657 urlProp,
1658 crumb,
1659 data;
1660
1661 for (var i = 0; i < breadcrumbs.values.length; ++i) {
1662 crumb = breadcrumbs.values[i];
1663 if (
1664 !crumb.hasOwnProperty('data') ||
1665 !isObject(crumb.data) ||
1666 objectFrozen(crumb.data)
1667 )
1668 continue;
1669
1670 data = objectMerge({}, crumb.data);
1671 for (var j = 0; j < urlProps.length; ++j) {
1672 urlProp = urlProps[j];
1673 if (data.hasOwnProperty(urlProp) && data[urlProp]) {
1674 data[urlProp] = truncate(data[urlProp], this._globalOptions.maxUrlLength);
1675 }
1676 }
1677 breadcrumbs.values[i].data = data;
1678 }
1679 },
1680
1681 _getHttpData: function() {
1682 if (!this._hasNavigator && !this._hasDocument) return;
1683 var httpData = {};
1684
1685 if (this._hasNavigator && _navigator.userAgent) {
1686 httpData.headers = {
1687 'User-Agent': navigator.userAgent
1688 };
1689 }
1690
1691 if (this._hasDocument) {
1692 if (_document.location && _document.location.href) {
1693 httpData.url = _document.location.href;
1694 }
1695 if (_document.referrer) {
1696 if (!httpData.headers) httpData.headers = {};
1697 httpData.headers.Referer = _document.referrer;
1698 }
1699 }
1700
1701 return httpData;
1702 },
1703
1704 _resetBackoff: function() {
1705 this._backoffDuration = 0;
1706 this._backoffStart = null;
1707 },
1708
1709 _shouldBackoff: function() {
1710 return this._backoffDuration && now() - this._backoffStart < this._backoffDuration;
1711 },
1712
1713 /**
1714 * Returns true if the in-process data payload matches the signature
1715 * of the previously-sent data
1716 *
1717 * NOTE: This has to be done at this level because TraceKit can generate
1718 * data from window.onerror WITHOUT an exception object (IE8, IE9,
1719 * other old browsers). This can take the form of an "exception"
1720 * data object with a single frame (derived from the onerror args).
1721 */
1722 _isRepeatData: function(current) {
1723 var last = this._lastData;
1724
1725 if (
1726 !last ||
1727 current.message !== last.message || // defined for captureMessage
1728 current.culprit !== last.culprit // defined for captureException/onerror
1729 )
1730 return false;
1731
1732 // Stacktrace interface (i.e. from captureMessage)
1733 if (current.stacktrace || last.stacktrace) {
1734 return isSameStacktrace(current.stacktrace, last.stacktrace);
1735 } else if (current.exception || last.exception) {
1736 // Exception interface (i.e. from captureException/onerror)
1737 return isSameException(current.exception, last.exception);
1738 }
1739
1740 return true;
1741 },
1742
1743 _setBackoffState: function(request) {
1744 // If we are already in a backoff state, don't change anything
1745 if (this._shouldBackoff()) {
1746 return;
1747 }
1748
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 }
1765
1766 this._backoffDuration = retry
1767 ? // If Sentry server returned a Retry-After value, use it
1768 retry
1769 : // Otherwise, double the last backoff duration (starts at 1 sec)
1770 this._backoffDuration * 2 || 1000;
1771 1921
1772 this._backoffStart = now();1922 return httpData;
1773 },1923 },
1924
1925 _resetBackoff: function () {
1926 this._backoffDuration = 0;
1927 this._backoffStart = null;
1928 },
1929
1930 _shouldBackoff: function () {
1931 return (
1932 this._backoffDuration &&
1933 now() - this._backoffStart < this._backoffDuration
1934 );
1935 },
1936
1937 /**
1938 * Returns true if the in-process data payload matches the signature
1939 * of the previously-sent data
1940 *
1941 * NOTE: This has to be done at this level because TraceKit can generate
1942 * data from window.onerror WITHOUT an exception object (IE8, IE9,
1943 * other old browsers). This can take the form of an "exception"
1944 * data object with a single frame (derived from the onerror args).
1945 */
1946 _isRepeatData: function (current) {
1947 var last = this._lastData;
1948
1949 if (
1950 !last ||
1951 current.message !== last.message || // defined for captureMessage
1952 current.culprit !== last.culprit // defined for captureException/onerror
1953 )
1954 return false;
1955
1956 // Stacktrace interface (i.e. from captureMessage)
1957 if (current.stacktrace || last.stacktrace) {
1958 return isSameStacktrace(current.stacktrace, last.stacktrace);
1959 } else if (current.exception || last.exception) {
1960 // Exception interface (i.e. from captureException/onerror)
1961 return isSameException(current.exception, last.exception);
1962 }
1774 1963
1775 _send: function(data) {1964 return true;
1776 var globalOptions = this._globalOptions;1965 },
1777 1966
1778 var baseData = {1967 _setBackoffState: function (request) {
1779 project: this._globalProject,1968 // If we are already in a backoff state, don't change anything
1780 logger: globalOptions.logger,1969 if (this._shouldBackoff()) {
1781 platform: 'javascript'1970 return;
1782 },1971 }
1783 httpData = this._getHttpData();
1784 1972
1785 if (httpData) {1973 var status = request.status;
1786 baseData.request = httpData;
1787 }
1788 1974
1789 // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload1975 // 400 - project_id doesn't exist or some other fatal
1790 if (data.trimHeadFrames) delete data.trimHeadFrames;1976 // 401 - invalid/revoked dsn
1977 // 429 - too many requests
1978 if (!(status === 400 || status === 401 || status === 429))
1979 return;
1791 1980
1792 data = objectMerge(baseData, data);1981 var retry;
1982 try {
1983 // If Retry-After is not in Access-Control-Expose-Headers, most
1984 // browsers will throw an exception trying to access it
1985 retry = request.getResponseHeader("Retry-After");
1986 retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds
1987 } catch (e) {
1988 /* eslint no-empty:0 */
1989 }
1793 1990
1794 // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge1991 this._backoffDuration = retry
1795 data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags);1992 ? // If Sentry server returned a Retry-After value, use it
1796 data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra);1993 retry
1994 : // Otherwise, double the last backoff duration (starts at 1 sec)
1995 this._backoffDuration * 2 || 1000;
1797 1996
1798 // Send along our own collected metadata with extra1997 this._backoffStart = now();
1799 data.extra['session:duration'] = now() - this._startTime;1998 },
1800 1999
1801 if (this._breadcrumbs && this._breadcrumbs.length > 0) {2000 _send: function (data) {
1802 // intentionally make shallow copy so that additions2001 var globalOptions = this._globalOptions;
1803 // to breadcrumbs aren't accidentally sent in this request
1804 data.breadcrumbs = {
1805 values: [].slice.call(this._breadcrumbs, 0)
1806 };
1807 }
1808 2002
1809 // If there are no tags/extra, strip the key from the payload alltogther.2003 var baseData = {
1810 if (isEmptyObject(data.tags)) delete data.tags;2004 project: this._globalProject,
2005 logger: globalOptions.logger,
2006 platform: "javascript",
2007 },
2008 httpData = this._getHttpData();
1811 2009
1812 if (this._globalContext.user) {2010 if (httpData) {
1813 // sentry.interfaces.User2011 baseData.request = httpData;
1814 data.user = this._globalContext.user;2012 }
1815 }
1816 2013
1817 // Include the environment if it's defined in globalOptions2014 // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload
1818 if (globalOptions.environment) data.environment = globalOptions.environment;2015 if (data.trimHeadFrames) delete data.trimHeadFrames;
2016
2017 data = objectMerge(baseData, data);
2018
2019 // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge
2020 data.tags = objectMerge(
2021 objectMerge({}, this._globalContext.tags),
2022 data.tags,
2023 );
2024 data.extra = objectMerge(
2025 objectMerge({}, this._globalContext.extra),
2026 data.extra,
2027 );
2028
2029 // Send along our own collected metadata with extra
2030 data.extra["session:duration"] = now() - this._startTime;
2031
2032 if (this._breadcrumbs && this._breadcrumbs.length > 0) {
2033 // intentionally make shallow copy so that additions
2034 // to breadcrumbs aren't accidentally sent in this request
2035 data.breadcrumbs = {
2036 values: [].slice.call(this._breadcrumbs, 0),
2037 };
2038 }
1819 2039
1820 // Include the release if it's defined in globalOptions2040 // If there are no tags/extra, strip the key from the payload alltogther.
1821 if (globalOptions.release) data.release = globalOptions.release;2041 if (isEmptyObject(data.tags)) delete data.tags;
1822 2042
1823 // Include server_name if it's defined in globalOptions2043 if (this._globalContext.user) {
1824 if (globalOptions.serverName) data.server_name = globalOptions.serverName;2044 // sentry.interfaces.User
2045 data.user = this._globalContext.user;
2046 }
1825 2047
1826 if (isFunction(globalOptions.dataCallback)) {2048 // Include the environment if it's defined in globalOptions
1827 data = globalOptions.dataCallback(data) || data;2049 if (globalOptions.environment)
1828 }2050 data.environment = globalOptions.environment;
1829 2051
1830 // Why??????????2052 // Include the release if it's defined in globalOptions
1831 if (!data || isEmptyObject(data)) {2053 if (globalOptions.release) data.release = globalOptions.release;
1832 return;
1833 }
1834 2054
1835 // Check if the request should be filtered or not2055 // Include server_name if it's defined in globalOptions
1836 if (2056 if (globalOptions.serverName)
1837 isFunction(globalOptions.shouldSendCallback) &&2057 data.server_name = globalOptions.serverName;
1838 !globalOptions.shouldSendCallback(data)
1839 ) {
1840 return;
1841 }
1842 2058
1843 // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests),2059 if (isFunction(globalOptions.dataCallback)) {
1844 // so drop requests until "cool-off" period has elapsed.2060 data = globalOptions.dataCallback(data) || data;
1845 if (this._shouldBackoff()) {2061 }
1846 this._logDebug('warn', 'Raven dropped error due to backoff: ', data);
1847 return;
1848 }
1849 2062
1850 if (typeof globalOptions.sampleRate === 'number') {2063 // Why??????????
1851 if (Math.random() < globalOptions.sampleRate) {2064 if (!data || isEmptyObject(data)) {
1852 this._sendProcessedPayload(data);2065 return;
1853 }2066 }
1854 } else {
1855 this._sendProcessedPayload(data);
1856 }
1857 },
1858 2067
1859 _getUuid: function() {2068 // Check if the request should be filtered or not
1860 return uuid4();2069 if (
1861 },2070 isFunction(globalOptions.shouldSendCallback) &&
2071 !globalOptions.shouldSendCallback(data)
2072 ) {
2073 return;
2074 }
1862 2075
1863 _sendProcessedPayload: function(data, callback) {2076 // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests),
1864 var self = this;2077 // so drop requests until "cool-off" period has elapsed.
1865 var globalOptions = this._globalOptions;2078 if (this._shouldBackoff()) {
2079 this._logDebug(
2080 "warn",
2081 "Raven dropped error due to backoff: ",
2082 data,
2083 );
2084 return;
2085 }
1866 2086
1867 if (!this.isSetup()) return;2087 if (typeof globalOptions.sampleRate === "number") {
2088 if (Math.random() < globalOptions.sampleRate) {
2089 this._sendProcessedPayload(data);
2090 }
2091 } else {
2092 this._sendProcessedPayload(data);
2093 }
2094 },
2095
2096 _getUuid: function () {
2097 return uuid4();
2098 },
2099
2100 _sendProcessedPayload: function (data, callback) {
2101 var self = this;
2102 var globalOptions = this._globalOptions;
2103
2104 if (!this.isSetup()) return;
2105
2106 // Try and clean up the packet before sending by truncating long values
2107 data = this._trimPacket(data);
2108
2109 // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback,
2110 // but this would require copying an un-truncated copy of the data packet, which can be
2111 // arbitrarily deep (extra_data) -- could be worthwhile? will revisit
2112 if (
2113 !this._globalOptions.allowDuplicates &&
2114 this._isRepeatData(data)
2115 ) {
2116 this._logDebug("warn", "Raven dropped repeat event: ", data);
2117 return;
2118 }
1868 2119
1869 // Try and clean up the packet before sending by truncating long values2120 // Send along an event_id if not explicitly passed.
1870 data = this._trimPacket(data);2121 // This event_id can be used to reference the error within Sentry itself.
2122 // Set lastEventId after we know the error should actually be sent
2123 this._lastEventId =
2124 data.event_id || (data.event_id = this._getUuid());
1871 2125
1872 // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback,2126 // Store outbound payload after trim
1873 // but this would require copying an un-truncated copy of the data packet, which can be2127 this._lastData = data;
1874 // arbitrarily deep (extra_data) -- could be worthwhile? will revisit
1875 if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) {
1876 this._logDebug('warn', 'Raven dropped repeat event: ', data);
1877 return;
1878 }
1879 2128
1880 // Send along an event_id if not explicitly passed.2129 this._logDebug("debug", "Raven about to send:", 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());
1884 2130
1885 // Store outbound payload after trim2131 var auth = {
1886 this._lastData = data;2132 sentry_version: "7",
2133 sentry_client: "raven-js/" + this.VERSION,
2134 sentry_key: this._globalKey,
2135 };
1887 2136
1888 this._logDebug('debug', 'Raven about to send:', data);2137 if (this._globalSecret) {
2138 auth.sentry_secret = this._globalSecret;
2139 }
1889 2140
1890 var auth = {2141 var exception = data.exception && data.exception.values[0];
1891 sentry_version: '7',2142
1892 sentry_client: 'raven-js/' + this.VERSION,2143 // only capture 'sentry' breadcrumb is autoBreadcrumbs is truthy
1893 sentry_key: this._globalKey2144 if (
1894 };2145 this._globalOptions.autoBreadcrumbs &&
2146 this._globalOptions.autoBreadcrumbs.sentry
2147 ) {
2148 this.captureBreadcrumb({
2149 category: "sentry",
2150 message: exception
2151 ? (exception.type ? exception.type + ": " : "") +
2152 exception.value
2153 : data.message,
2154 event_id: data.event_id,
2155 level: data.level || "error", // presume error unless specified
2156 });
2157 }
1895 2158
1896 if (this._globalSecret) {2159 var url = this._globalEndpoint;
1897 auth.sentry_secret = this._globalSecret;2160 (globalOptions.transport || this._makeRequest).call(this, {
1898 }2161 url: url,
2162 auth: auth,
2163 data: data,
2164 options: globalOptions,
2165 onSuccess: function success() {
2166 self._resetBackoff();
2167
2168 self._triggerEvent("success", {
2169 data: data,
2170 src: url,
2171 });
2172 callback && callback();
2173 },
2174 onError: function failure(error) {
2175 self._logDebug(
2176 "error",
2177 "Raven transport failed to send: ",
2178 error,
2179 );
2180
2181 if (error.request) {
2182 self._setBackoffState(error.request);
2183 }
2184
2185 self._triggerEvent("failure", {
2186 data: data,
2187 src: url,
2188 });
2189 error =
2190 error ||
2191 new Error(
2192 "Raven send failed (no additional details provided)",
2193 );
2194 callback && callback(error);
2195 },
2196 });
2197 },
2198
2199 _makeRequest: function (opts) {
2200 var request =
2201 _window.XMLHttpRequest && new _window.XMLHttpRequest();
2202 if (!request) return;
2203
2204 // if browser doesn't support CORS (e.g. IE7), we are out of luck
2205 var hasCORS =
2206 "withCredentials" in request ||
2207 typeof XDomainRequest !== "undefined";
2208
2209 if (!hasCORS) return;
2210
2211 var url = opts.url;
2212
2213 if ("withCredentials" in request) {
2214 request.onreadystatechange = function () {
2215 if (request.readyState !== 4) {
2216 return;
2217 } else if (request.status === 200) {
2218 opts.onSuccess && opts.onSuccess();
2219 } else if (opts.onError) {
2220 var err = new Error(
2221 "Sentry error code: " + request.status,
2222 );
2223 err.request = request;
2224 opts.onError(err);
2225 }
2226 };
2227 } else {
2228 request = new XDomainRequest();
2229 // xdomainrequest cannot go http -> https (or vice versa),
2230 // so always use protocol relative
2231 url = url.replace(/^https?:/, "");
2232
2233 // onreadystatechange not supported by XDomainRequest
2234 if (opts.onSuccess) {
2235 request.onload = opts.onSuccess;
2236 }
2237 if (opts.onError) {
2238 request.onerror = function () {
2239 var err = new Error("Sentry error code: XDomainRequest");
2240 err.request = request;
2241 opts.onError(err);
2242 };
2243 }
2244 }
1899 2245
1900 var exception = data.exception && data.exception.values[0];2246 // NOTE: auth is intentionally sent as part of query string (NOT as custom
1901 2247 // HTTP header) so as to avoid preflight CORS requests
1902 // only capture 'sentry' breadcrumb is autoBreadcrumbs is truthy2248 request.open("POST", url + "?" + urlencode(opts.auth));
1903 if (2249 request.send(stringify(opts.data));
1904 this._globalOptions.autoBreadcrumbs &&2250 },
1905 this._globalOptions.autoBreadcrumbs.sentry2251
1906 ) {2252 _logDebug: function (level) {
1907 this.captureBreadcrumb({2253 if (this._originalConsoleMethods[level] && this.debug) {
1908 category: 'sentry',2254 // In IE<10 console methods do not have their own 'apply' method
1909 message: exception2255 Function.prototype.apply.call(
1910 ? (exception.type ? exception.type + ': ' : '') + exception.value2256 this._originalConsoleMethods[level],
1911 : data.message,2257 this._originalConsole,
1912 event_id: data.event_id,2258 [].slice.call(arguments, 1),
1913 level: data.level || 'error' // presume error unless specified2259 );
1914 });2260 }
1915 }2261 },
2262
2263 _mergeContext: function (key, context) {
2264 if (isUndefined(context)) {
2265 delete this._globalContext[key];
2266 } else {
2267 this._globalContext[key] = objectMerge(
2268 this._globalContext[key] || {},
2269 context,
2270 );
2271 }
2272 },
2273 };
1916 2274
1917 var url = this._globalEndpoint;2275 // Deprecations
1918 (globalOptions.transport || this._makeRequest).call(this, {2276 Raven.prototype.setUser = Raven.prototype.setUserContext;
1919 url: url,2277 Raven.prototype.setReleaseContext = Raven.prototype.setRelease;
1920 auth: auth,2278
1921 data: data,2279 module.exports = Raven;
1922 options: globalOptions,2280 }).call(
1923 onSuccess: function success() {2281 this,
1924 self._resetBackoff();2282 typeof global !== "undefined"
1925 2283 ? global
1926 self._triggerEvent('success', {2284 : typeof self !== "undefined"
1927 data: data,2285 ? self
1928 src: url2286 : typeof window !== "undefined"
1929 });2287 ? window
1930 callback && callback();2288 : {},
1931 },2289 );
1932 onError: function failure(error) {2290 },
1933 self._logDebug('error', 'Raven transport failed to send: ', error);2291 { 1: 1, 2: 2, 5: 5, 6: 6, 7: 7 },
1934 2292 ],
1935 if (error.request) {2293 4: [
1936 self._setBackoffState(error.request);2294 function (_dereq_, module, exports) {
1937 }2295 (function (global) {
2296 /**
2297 * Enforces a single instance of the Raven client, and the
2298 * main entry point for Raven. If you are a consumer of the
2299 * Raven library, you SHOULD load this file (vs raven.js).
2300 **/
2301
2302 var RavenConstructor = _dereq_(3);
2303
2304 // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
2305 var _window =
2306 typeof window !== "undefined"
2307 ? window
2308 : typeof global !== "undefined"
2309 ? global
2310 : typeof self !== "undefined"
2311 ? self
2312 : {};
2313 var _Raven = _window.Raven;
2314
2315 var Raven = new RavenConstructor();
2316
2317 /*
2318 * Allow multiple versions of Raven to be installed.
2319 * Strip Raven from the global context and returns the instance.
2320 *
2321 * @return {Raven}
2322 */
2323 Raven.noConflict = function () {
2324 _window.Raven = _Raven;
2325 return Raven;
2326 };
1938 2327
1939 self._triggerEvent('failure', {2328 Raven.afterLoad();
1940 data: data,2329
1941 src: url2330 module.exports = Raven;
1942 });2331 }).call(
1943 error = error || new Error('Raven send failed (no additional details provided)');2332 this,
1944 callback && callback(error);2333 typeof global !== "undefined"
1945 }2334 ? global
1946 });2335 : typeof self !== "undefined"
1947 },2336 ? self
1948 2337 : typeof window !== "undefined"
1949 _makeRequest: function(opts) {2338 ? window
1950 var request = _window.XMLHttpRequest && new _window.XMLHttpRequest();2339 : {},
1951 if (!request) return;2340 );
1952 2341 },
1953 // if browser doesn't support CORS (e.g. IE7), we are out of luck2342 { 3: 3 },
1954 var hasCORS = 'withCredentials' in request || typeof XDomainRequest !== 'undefined';2343 ],
1955 2344 5: [
1956 if (!hasCORS) return;2345 function (_dereq_, module, exports) {
1957 2346 (function (global) {
1958 var url = opts.url;2347 var _window =
1959 2348 typeof window !== "undefined"
1960 if ('withCredentials' in request) {2349 ? window
1961 request.onreadystatechange = function() {2350 : typeof global !== "undefined"
1962 if (request.readyState !== 4) {2351 ? global
1963 return;2352 : typeof self !== "undefined"
1964 } else if (request.status === 200) {2353 ? self
1965 opts.onSuccess && opts.onSuccess();2354 : {};
1966 } else if (opts.onError) {2355
1967 var err = new Error('Sentry error code: ' + request.status);2356 function isObject(what) {
1968 err.request = request;2357 return typeof what === "object" && what !== null;
1969 opts.onError(err);2358 }
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 2359
1991 // NOTE: auth is intentionally sent as part of query string (NOT as custom2360 // Yanked from https://git.io/vS8DV re-used under CC0
1992 // HTTP header) so as to avoid preflight CORS requests2361 // with some tiny modifications
1993 request.open('POST', url + '?' + urlencode(opts.auth));2362 function isError(value) {
1994 request.send(stringify(opts.data));2363 switch ({}.toString.call(value)) {
1995 },2364 case "[object Error]":
1996 2365 return true;
1997 _logDebug: function(level) {2366 case "[object Exception]":
1998 if (this._originalConsoleMethods[level] && this.debug) {2367 return true;
1999 // In IE<10 console methods do not have their own 'apply' method2368 case "[object DOMException]":
2000 Function.prototype.apply.call(2369 return true;
2001 this._originalConsoleMethods[level],2370 default:
2002 this._originalConsole,2371 return value instanceof Error;
2003 [].slice.call(arguments, 1)2372 }
2004 );2373 }
2005 }
2006 },
2007 2374
2008 _mergeContext: function(key, context) {2375 function isErrorEvent(value) {
2009 if (isUndefined(context)) {2376 return (
2010 delete this._globalContext[key];2377 supportsErrorEvent() &&
2011 } else {2378 {}.toString.call(value) === "[object ErrorEvent]"
2012 this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context);2379 );
2013 }2380 }
2014 }
2015};
2016 2381
2017// Deprecations2382 function isUndefined(what) {
2018Raven.prototype.setUser = Raven.prototype.setUserContext;2383 return what === void 0;
2019Raven.prototype.setReleaseContext = Raven.prototype.setRelease;2384 }
2020 2385
2021module.exports = Raven;2386 function isFunction(what) {
2387 return typeof what === "function";
2388 }
2022 2389
2023}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})2390 function isString(what) {
2024},{"1":1,"2":2,"5":5,"6":6,"7":7}],4:[function(_dereq_,module,exports){2391 return Object.prototype.toString.call(what) === "[object String]";
2025(function (global){2392 }
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 2393
2032var RavenConstructor = _dereq_(3);2394 function isArray(what) {
2395 return Object.prototype.toString.call(what) === "[object Array]";
2396 }
2033 2397
2034// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)2398 function isEmptyObject(what) {
2035var _window =2399 for (var _ in what) {
2036 typeof window !== 'undefined'2400 if (what.hasOwnProperty(_)) {
2037 ? window2401 return false;
2038 : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};2402 }
2039var _Raven = _window.Raven;2403 }
2404 return true;
2405 }
2040 2406
2041var Raven = new RavenConstructor();2407 function supportsErrorEvent() {
2408 try {
2409 new ErrorEvent(""); // eslint-disable-line no-new
2410 return true;
2411 } catch (e) {
2412 return false;
2413 }
2414 }
2042 2415
2043/*2416 function wrappedCallback(callback) {
2044 * Allow multiple versions of Raven to be installed.2417 function dataCallback(data, original) {
2045 * Strip Raven from the global context and returns the instance.2418 var normalizedData = callback(data) || data;
2046 *2419 if (original) {
2047 * @return {Raven}2420 return original(normalizedData) || normalizedData;
2048 */2421 }
2049Raven.noConflict = function() {2422 return normalizedData;
2050 _window.Raven = _Raven;2423 }
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}
2084 2424
2085function isErrorEvent(value) {2425 return dataCallback;
2086 return supportsErrorEvent() && {}.toString.call(value) === '[object ErrorEvent]';2426 }
2087}
2088 2427
2089function isUndefined(what) {2428 function each(obj, callback) {
2090 return what === void 0;2429 var i, j;
2091}
2092 2430
2093function isFunction(what) {2431 if (isUndefined(obj.length)) {
2094 return typeof what === 'function';2432 for (i in obj) {
2095}2433 if (hasKey(obj, i)) {
2434 callback.call(null, i, obj[i]);
2435 }
2436 }
2437 } else {
2438 j = obj.length;
2439 if (j) {
2440 for (i = 0; i < j; i++) {
2441 callback.call(null, i, obj[i]);
2442 }
2443 }
2444 }
2445 }
2096 2446
2097function isString(what) {2447 function objectMerge(obj1, obj2) {
2098 return Object.prototype.toString.call(what) === '[object String]';2448 if (!obj2) {
2099}2449 return obj1;
2450 }
2451 each(obj2, function (key, value) {
2452 obj1[key] = value;
2453 });
2454 return obj1;
2455 }
2100 2456
2101function isArray(what) {2457 /**
2102 return Object.prototype.toString.call(what) === '[object Array]';2458 * This function is only used for react-native.
2103}2459 * react-native freezes object that have already been sent over the
2460 * js bridge. We need this function in order to check if the object is frozen.
2461 * So it's ok that objectFrozen returns false if Object.isFrozen is not
2462 * supported because it's not relevant for other "platforms". See related issue:
2463 * https://github.com/getsentry/react-native-sentry/issues/57
2464 */
2465 function objectFrozen(obj) {
2466 if (!Object.isFrozen) {
2467 return false;
2468 }
2469 return Object.isFrozen(obj);
2470 }
2104 2471
2105function isEmptyObject(what) {2472 function truncate(str, max) {
2106 for (var _ in what) {2473 return !max || str.length <= max
2107 if (what.hasOwnProperty(_)) {2474 ? str
2108 return false;2475 : str.substr(0, max) + "\u2026";
2109 }2476 }
2110 }
2111 return true;
2112}
2113
2114function supportsErrorEvent() {
2115 try {
2116 new ErrorEvent(''); // eslint-disable-line no-new
2117 return true;
2118 } catch (e) {
2119 return false;
2120 }
2121}
2122 2477
2123function wrappedCallback(callback) {2478 /**
2124 function dataCallback(data, original) {2479 * hasKey, a better form of hasOwnProperty
2125 var normalizedData = callback(data) || data;2480 * Example: hasKey(MainHostObject, property) === true/false
2126 if (original) {2481 *
2127 return original(normalizedData) || normalizedData;2482 * @param {Object} host object to check property
2128 }2483 * @param {string} key to check
2129 return normalizedData;2484 */
2130 }2485 function hasKey(object, key) {
2486 return Object.prototype.hasOwnProperty.call(object, key);
2487 }
2131 2488
2132 return dataCallback;2489 function joinRegExp(patterns) {
2133}2490 // Combine an array of regular expressions and strings into one large regexp
2491 // Be mad.
2492 var sources = [],
2493 i = 0,
2494 len = patterns.length,
2495 pattern;
2496
2497 for (; i < len; i++) {
2498 pattern = patterns[i];
2499 if (isString(pattern)) {
2500 // If it's a string, we need to escape it
2501 // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
2502 sources.push(
2503 pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"),
2504 );
2505 } else if (pattern && pattern.source) {
2506 // If it's a regexp already, we want to extract the source
2507 sources.push(pattern.source);
2508 }
2509 // Intentionally skip other cases
2510 }
2511 return new RegExp(sources.join("|"), "i");
2512 }
2134 2513
2135function each(obj, callback) {2514 function urlencode(o) {
2136 var i, j;2515 var pairs = [];
2516 each(o, function (key, value) {
2517 pairs.push(
2518 encodeURIComponent(key) + "=" + encodeURIComponent(value),
2519 );
2520 });
2521 return pairs.join("&");
2522 }
2137 2523
2138 if (isUndefined(obj.length)) {2524 // borrowed from https://datatracker.ietf.org/doc/html/rfc3986#appendix-B
2139 for (i in obj) {2525 // intentionally using regex and not <a/> href parsing trick because React Native and other
2140 if (hasKey(obj, i)) {2526 // environments where DOM might not be available
2141 callback.call(null, i, obj[i]);2527 function parseUrl(url) {
2142 }2528 var match = url.match(
2143 }2529 /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/,
2144 } else {2530 );
2145 j = obj.length;2531 if (!match) return {};
2146 if (j) {2532
2147 for (i = 0; i < j; i++) {2533 // coerce to undefined values to empty string so we don't get 'undefined'
2148 callback.call(null, i, obj[i]);2534 var query = match[6] || "";
2149 }2535 var fragment = match[8] || "";
2150 }2536 return {
2151 }2537 protocol: match[2],
2152}2538 host: match[4],
2539 path: match[5],
2540 relative: match[5] + query + fragment, // everything minus origin
2541 };
2542 }
2543 function uuid4() {
2544 var crypto = _window.crypto || _window.msCrypto;
2545
2546 if (!isUndefined(crypto) && crypto.getRandomValues) {
2547 // Use window.crypto API if available
2548 // eslint-disable-next-line no-undef
2549 var arr = new Uint16Array(8);
2550 crypto.getRandomValues(arr);
2551
2552 // set 4 in byte 7
2553 arr[3] = (arr[3] & 0xfff) | 0x4000;
2554 // set 2 most significant bits of byte 9 to '10'
2555 arr[4] = (arr[4] & 0x3fff) | 0x8000;
2556
2557 var pad = function (num) {
2558 var v = num.toString(16);
2559 while (v.length < 4) {
2560 v = "0" + v;
2561 }
2562 return v;
2563 };
2153 2564
2154function objectMerge(obj1, obj2) {2565 return (
2155 if (!obj2) {2566 pad(arr[0]) +
2156 return obj1;2567 pad(arr[1]) +
2157 }2568 pad(arr[2]) +
2158 each(obj2, function(key, value) {2569 pad(arr[3]) +
2159 obj1[key] = value;2570 pad(arr[4]) +
2160 });2571 pad(arr[5]) +
2161 return obj1;2572 pad(arr[6]) +
2162}2573 pad(arr[7])
2163 2574 );
2164/**2575 } else {
2165 * This function is only used for react-native.2576 // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
2166 * react-native freezes object that have already been sent over the2577 return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(
2167 * js bridge. We need this function in order to check if the object is frozen.2578 /[xy]/g,
2168 * So it's ok that objectFrozen returns false if Object.isFrozen is not2579 function (c) {
2169 * supported because it's not relevant for other "platforms". See related issue:2580 var r = (Math.random() * 16) | 0,
2170 * https://github.com/getsentry/react-native-sentry/issues/572581 v = c === "x" ? r : (r & 0x3) | 0x8;
2171 */2582 return v.toString(16);
2172function objectFrozen(obj) {2583 },
2173 if (!Object.isFrozen) {2584 );
2174 return false;2585 }
2175 }2586 }
2176 return Object.isFrozen(obj);
2177}
2178 2587
2179function truncate(str, max) {2588 /**
2180 return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';2589 * Given a child DOM element, returns a query-selector statement describing that
2181}2590 * and its ancestors
2591 * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
2592 * @param elem
2593 * @returns {string}
2594 */
2595 function htmlTreeAsString(elem) {
2596 /* eslint no-extra-parens:0*/
2597 var MAX_TRAVERSE_HEIGHT = 5,
2598 MAX_OUTPUT_LEN = 80,
2599 out = [],
2600 height = 0,
2601 len = 0,
2602 separator = " > ",
2603 sepLength = separator.length,
2604 nextStr;
2605
2606 while (elem && height++ < MAX_TRAVERSE_HEIGHT) {
2607 nextStr = htmlElementAsString(elem);
2608 // bail out if
2609 // - nextStr is the 'html' element
2610 // - the length of the string that would be created exceeds MAX_OUTPUT_LEN
2611 // (ignore this limit if we are on the first iteration)
2612 if (
2613 nextStr === "html" ||
2614 (height > 1 &&
2615 len + out.length * sepLength + nextStr.length >=
2616 MAX_OUTPUT_LEN)
2617 ) {
2618 break;
2619 }
2182 2620
2183/**2621 out.push(nextStr);
2184 * hasKey, a better form of hasOwnProperty
2185 * Example: hasKey(MainHostObject, property) === true/false
2186 *
2187 * @param {Object} host object to check property
2188 * @param {string} key to check
2189 */
2190function hasKey(object, key) {
2191 return Object.prototype.hasOwnProperty.call(object, key);
2192}
2193
2194function joinRegExp(patterns) {
2195 // Combine an array of regular expressions and strings into one large regexp
2196 // Be mad.
2197 var sources = [],
2198 i = 0,
2199 len = patterns.length,
2200 pattern;
2201
2202 for (; i < len; i++) {
2203 pattern = patterns[i];
2204 if (isString(pattern)) {
2205 // If it's a string, we need to escape it
2206 // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
2207 sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
2208 } else if (pattern && pattern.source) {
2209 // If it's a regexp already, we want to extract the source
2210 sources.push(pattern.source);
2211 }
2212 // Intentionally skip other cases
2213 }
2214 return new RegExp(sources.join('|'), 'i');
2215}
2216
2217function urlencode(o) {
2218 var pairs = [];
2219 each(o, function(key, value) {
2220 pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
2221 });
2222 return pairs.join('&');
2223}
2224
2225// borrowed from https://datatracker.ietf.org/doc/html/rfc3986#appendix-B
2226// intentionally using regex and not <a/> href parsing trick because React Native and other
2227// environments where DOM might not be available
2228function parseUrl(url) {
2229 var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);
2230 if (!match) return {};
2231
2232 // coerce to undefined values to empty string so we don't get 'undefined'
2233 var query = match[6] || '';
2234 var fragment = match[8] || '';
2235 return {
2236 protocol: match[2],
2237 host: match[4],
2238 path: match[5],
2239 relative: match[5] + query + fragment // everything minus origin
2240 };
2241}
2242function uuid4() {
2243 var crypto = _window.crypto || _window.msCrypto;
2244
2245 if (!isUndefined(crypto) && crypto.getRandomValues) {
2246 // Use window.crypto API if available
2247 // eslint-disable-next-line no-undef
2248 var arr = new Uint16Array(8);
2249 crypto.getRandomValues(arr);
2250
2251 // set 4 in byte 7
2252 arr[3] = (arr[3] & 0xfff) | 0x4000;
2253 // set 2 most significant bits of byte 9 to '10'
2254 arr[4] = (arr[4] & 0x3fff) | 0x8000;
2255
2256 var pad = function(num) {
2257 var v = num.toString(16);
2258 while (v.length < 4) {
2259 v = '0' + v;
2260 }
2261 return v;
2262 };
2263
2264 return (
2265 pad(arr[0]) +
2266 pad(arr[1]) +
2267 pad(arr[2]) +
2268 pad(arr[3]) +
2269 pad(arr[4]) +
2270 pad(arr[5]) +
2271 pad(arr[6]) +
2272 pad(arr[7])
2273 );
2274 } else {
2275 // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
2276 return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
2277 var r = (Math.random() * 16) | 0,
2278 v = c === 'x' ? r : (r & 0x3) | 0x8;
2279 return v.toString(16);
2280 });
2281 }
2282}
2283
2284/**
2285 * Given a child DOM element, returns a query-selector statement describing that
2286 * and its ancestors
2287 * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]
2288 * @param elem
2289 * @returns {string}
2290 */
2291function htmlTreeAsString(elem) {
2292 /* eslint no-extra-parens:0*/
2293 var MAX_TRAVERSE_HEIGHT = 5,
2294 MAX_OUTPUT_LEN = 80,
2295 out = [],
2296 height = 0,
2297 len = 0,
2298 separator = ' > ',
2299 sepLength = separator.length,
2300 nextStr;
2301
2302 while (elem && height++ < MAX_TRAVERSE_HEIGHT) {
2303 nextStr = htmlElementAsString(elem);
2304 // bail out if
2305 // - nextStr is the 'html' element
2306 // - the length of the string that would be created exceeds MAX_OUTPUT_LEN
2307 // (ignore this limit if we are on the first iteration)
2308 if (
2309 nextStr === 'html' ||
2310 (height > 1 && len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN)
2311 ) {
2312 break;
2313 }
2314 2622
2315 out.push(nextStr);2623 len += nextStr.length;
2624 elem = elem.parentNode;
2625 }
2316 2626
2317 len += nextStr.length;2627 return out.reverse().join(separator);
2318 elem = elem.parentNode;2628 }
2319 }
2320 2629
2321 return out.reverse().join(separator);2630 /**
2322}2631 * Returns a simple, query-selector representation of a DOM element
2632 * e.g. [HTMLElement] => input#foo.btn[name=baz]
2633 * @param HTMLElement
2634 * @returns {string}
2635 */
2636 function htmlElementAsString(elem) {
2637 var out = [],
2638 className,
2639 classes,
2640 key,
2641 attr,
2642 i;
2643
2644 if (!elem || !elem.tagName) {
2645 return "";
2646 }
2323 2647
2324/**2648 out.push(elem.tagName.toLowerCase());
2325 * Returns a simple, query-selector representation of a DOM element2649 if (elem.id) {
2326 * e.g. [HTMLElement] => input#foo.btn[name=baz]2650 out.push("#" + elem.id);
2327 * @param HTMLElement2651 }
2328 * @returns {string}
2329 */
2330function htmlElementAsString(elem) {
2331 var out = [],
2332 className,
2333 classes,
2334 key,
2335 attr,
2336 i;
2337
2338 if (!elem || !elem.tagName) {
2339 return '';
2340 }
2341 2652
2342 out.push(elem.tagName.toLowerCase());2653 className = elem.className;
2343 if (elem.id) {2654 if (className && isString(className)) {
2344 out.push('#' + elem.id);2655 classes = className.split(/\s+/);
2345 }2656 for (i = 0; i < classes.length; i++) {
2657 out.push("." + classes[i]);
2658 }
2659 }
2660 var attrWhitelist = ["type", "name", "title", "alt"];
2661 for (i = 0; i < attrWhitelist.length; i++) {
2662 key = attrWhitelist[i];
2663 attr = elem.getAttribute(key);
2664 if (attr) {
2665 out.push("[" + key + '="' + attr + '"]');
2666 }
2667 }
2668 return out.join("");
2669 }
2346 2670
2347 className = elem.className;2671 /**
2348 if (className && isString(className)) {2672 * Returns true if either a OR b is truthy, but not both
2349 classes = className.split(/\s+/);2673 */
2350 for (i = 0; i < classes.length; i++) {2674 function isOnlyOneTruthy(a, b) {
2351 out.push('.' + classes[i]);2675 return !!(!!a ^ !!b);
2352 }2676 }
2353 }
2354 var attrWhitelist = ['type', 'name', 'title', 'alt'];
2355 for (i = 0; i < attrWhitelist.length; i++) {
2356 key = attrWhitelist[i];
2357 attr = elem.getAttribute(key);
2358 if (attr) {
2359 out.push('[' + key + '="' + attr + '"]');
2360 }
2361 }
2362 return out.join('');
2363}
2364 2677
2365/**2678 /**
2366 * Returns true if either a OR b is truthy, but not both2679 * Returns true if the two input exception interfaces have the same content
2367 */2680 */
2368function isOnlyOneTruthy(a, b) {2681 function isSameException(ex1, ex2) {
2369 return !!(!!a ^ !!b);2682 if (isOnlyOneTruthy(ex1, ex2)) return false;
2370}
2371 2683
2372/**2684 ex1 = ex1.values[0];
2373 * Returns true if the two input exception interfaces have the same content2685 ex2 = ex2.values[0];
2374 */
2375function isSameException(ex1, ex2) {
2376 if (isOnlyOneTruthy(ex1, ex2)) return false;
2377 2686
2378 ex1 = ex1.values[0];2687 if (ex1.type !== ex2.type || ex1.value !== ex2.value)
2379 ex2 = ex2.values[0];2688 return false;
2380 2689
2381 if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false;2690 return isSameStacktrace(ex1.stacktrace, ex2.stacktrace);
2691 }
2382 2692
2383 return isSameStacktrace(ex1.stacktrace, ex2.stacktrace);2693 /**
2384}2694 * Returns true if the two input stack trace interfaces have the same content
2695 */
2696 function isSameStacktrace(stack1, stack2) {
2697 if (isOnlyOneTruthy(stack1, stack2)) return false;
2698
2699 var frames1 = stack1.frames;
2700 var frames2 = stack2.frames;
2701
2702 // Exit early if frame count differs
2703 if (frames1.length !== frames2.length) return false;
2704
2705 // Iterate through every frame; bail out if anything differs
2706 var a, b;
2707 for (var i = 0; i < frames1.length; i++) {
2708 a = frames1[i];
2709 b = frames2[i];
2710 if (
2711 a.filename !== b.filename ||
2712 a.lineno !== b.lineno ||
2713 a.colno !== b.colno ||
2714 a["function"] !== b["function"]
2715 )
2716 return false;
2717 }
2718 return true;
2719 }
2385 2720
2386/**2721 /**
2387 * Returns true if the two input stack trace interfaces have the same content2722 * Polyfill a method
2388 */2723 * @param obj object e.g. `document`
2389function isSameStacktrace(stack1, stack2) {2724 * @param name method name present on object e.g. `addEventListener`
2390 if (isOnlyOneTruthy(stack1, stack2)) return false;2725 * @param replacement replacement function
2391 2726 * @param track {optional} record instrumentation to an array
2392 var frames1 = stack1.frames;2727 */
2393 var frames2 = stack2.frames;2728 function fill(obj, name, replacement, track) {
2394 2729 var orig = obj[name];
2395 // Exit early if frame count differs2730 obj[name] = replacement(orig);
2396 if (frames1.length !== frames2.length) return false;2731 obj[name].__raven__ = true;
2397 2732 obj[name].__orig__ = orig;
2398 // Iterate through every frame; bail out if anything differs2733 if (track) {
2399 var a, b;
2400 for (var i = 0; i < frames1.length; i++) {
2401 a = frames1[i];
2402 b = frames2[i];
2403 if (
2404 a.filename !== b.filename ||
2405 a.lineno !== b.lineno ||
2406 a.colno !== b.colno ||
2407 a['function'] !== b['function']
2408 )
2409 return false;
2410 }
2411 return true;
2412}
2413
2414/**
2415 * Polyfill a method
2416 * @param obj object e.g. `document`
2417 * @param name method name present on object e.g. `addEventListener`
2418 * @param replacement replacement function
2419 * @param track {optional} record instrumentation to an array
2420 */
2421function fill(obj, name, replacement, track) {
2422 var orig = obj[name];
2423 obj[name] = replacement(orig);
2424 obj[name].__raven__ = true;
2425 obj[name].__orig__ = orig;
2426 if (track) {
2427 track.push([obj, name, orig]);
2428 }
2429}
2430
2431module.exports = {
2432 isObject: isObject,
2433 isError: isError,
2434 isErrorEvent: isErrorEvent,
2435 isUndefined: isUndefined,
2436 isFunction: isFunction,
2437 isString: isString,
2438 isArray: isArray,
2439 isEmptyObject: isEmptyObject,
2440 supportsErrorEvent: supportsErrorEvent,
2441 wrappedCallback: wrappedCallback,
2442 each: each,
2443 objectMerge: objectMerge,
2444 truncate: truncate,
2445 objectFrozen: objectFrozen,
2446 hasKey: hasKey,
2447 joinRegExp: joinRegExp,
2448 urlencode: urlencode,
2449 uuid4: uuid4,
2450 htmlTreeAsString: htmlTreeAsString,
2451 htmlElementAsString: htmlElementAsString,
2452 isSameException: isSameException,
2453 isSameStacktrace: isSameStacktrace,
2454 parseUrl: parseUrl,
2455 fill: fill
2456};
2457
2458}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
2459},{}],6:[function(_dereq_,module,exports){
2460(function (global){
2461var utils = _dereq_(5);

1440 more lines aren’t shown here.

assets/javascripts/views/content/content.js+81−56
@@ -32,51 +32,46 @@ const Cls = (app.views.Content = class Content extends app.View { · 31 unmodified lines
32 }32 }
33 33
34 static initClass() {34 static initClass() {
35 this.el = '._content';35 this.el = "._content";
36 this.loadingClass = '_content-loading';36 this.loadingClass = "_content-loading";
37 37
38 this.events =38 this.events = { click: "onClick" };
39 {click: 'onClick'};39
40
41 this.shortcuts = {40 this.shortcuts = {
42 altUp: 'scrollStepUp',41 altUp: "scrollStepUp",
43 altDown: 'scrollStepDown',42 altDown: "scrollStepDown",
44 pageUp: 'scrollPageUp',43 pageUp: "scrollPageUp",
45 pageDown: 'scrollPageDown',44 pageDown: "scrollPageDown",
46 pageTop: 'scrollToTop',45 pageTop: "scrollToTop",
47 pageBottom: 'scrollToBottom',46 pageBottom: "scrollToBottom",
48 altF: 'onAltF'47 altF: "onAltF",
49 };48 };
50 49
51 this.routes = {50 this.routes = {
52 before: 'beforeRoute',51 before: "beforeRoute",
53 after: 'afterRoute'52 after: "afterRoute",
54 };53 };
55 }54 }
56 55
57 init() {56 init() {
58 this.scrollEl = app.isMobile() ?57 this.scrollEl = app.isMobile()
59 (document.scrollingElement || document.body)58 ? document.scrollingElement || document.body
60 :59 : this.el;
61 this.el;
62 this.scrollMap = {};60 this.scrollMap = {};
63 this.scrollStack = [];61 this.scrollStack = [];
64 62
65 this.rootPage = new app.views.RootPage;63 this.rootPage = new app.views.RootPage();
66 this.staticPage = new app.views.StaticPage;64 this.staticPage = new app.views.StaticPage();
67 this.settingsPage = new app.views.SettingsPage;65 this.settingsPage = new app.views.SettingsPage();
68 this.offlinePage = new app.views.OfflinePage;66 this.offlinePage = new app.views.OfflinePage();
69 this.typePage = new app.views.TypePage;67 this.typePage = new app.views.TypePage();
70 this.entryPage = new app.views.EntryPage;68 this.entryPage = new app.views.EntryPage();
71 69
72 this.entryPage70 this.entryPage
73 .on('loading', this.onEntryLoading)71 .on("loading", this.onEntryLoading)
74 .on('loaded', this.onEntryLoaded);72 .on("loaded", this.onEntryLoaded);
75
76 app
77 .on('ready', this.onReady)
78 .on('bootError', this.onBootError);
79 73
74 app.on("ready", this.onReady).on("bootError", this.onBootError);
80 }75 }
81 76
82 show(view) {77 show(view) {
@@ -85,7 +80,7 @@ const Cls = (app.views.Content = class Content extends app.View { · 2 unmodified lines
85 if (this.view != null) {80 if (this.view != null) {
86 this.view.deactivate();81 this.view.deactivate();
87 }82 }
88 this.html(this.view = view);83 this.html((this.view = view));
89 this.view.activate();84 this.view.activate();
90 }85 }
91 }86 }
@@ -107,7 +102,7 @@ const Cls = (app.views.Content = class Content extends app.View { · 15 unmodified lines
107 }102 }
108 103
109 smoothScrollTo(value) {104 smoothScrollTo(value) {
110 if (app.settings.get('fastScroll')) {105 if (app.settings.get("fastScroll")) {
111 this.scrollTo(value);106 this.scrollTo(value);
112 } else {107 } else {
113 $.smoothScroll(this.scrollEl, value || 0);108 $.smoothScroll(this.scrollEl, value || 0);
@@ -144,10 +139,14 @@ const Cls = (app.views.Content = class Content extends app.View { · 30 unmodified lines
144 139
145 scrollToTarget() {140 scrollToTarget() {
146 let el;141 let el;
147 if (this.routeCtx.hash && (el = this.findTargetByHash(this.routeCtx.hash))) {142 if (
148 $.scrollToWithImageLock(el, this.scrollEl, 'top',143 this.routeCtx.hash &&
149 {margin: this.scrollEl === this.el ? 0 : $.offset(this.el).top});144 (el = this.findTargetByHash(this.routeCtx.hash))
150 $.highlight(el, {className: '_highlight'});145 ) {
146 $.scrollToWithImageLock(el, this.scrollEl, "top", {
147 margin: this.scrollEl === this.el ? 0 : $.offset(this.el).top,
148 });
149 $.highlight(el, { className: "_highlight" });
151 } else {150 } else {
152 this.scrollTo(this.scrollMap[this.routeCtx.state.id]);151 this.scrollTo(this.scrollMap[this.routeCtx.state.id]);
153 }152 }
@@ -159,7 +158,7 @@ const Cls = (app.views.Content = class Content extends app.View { · 5 unmodified lines
159 158
160 onBootError() {159 onBootError() {
161 this.hideLoading();160 this.hideLoading();
162 this.html(this.tmpl('bootError'));161 this.html(this.tmpl("bootError"));
163 }162 }
164 163
165 onEntryLoading() {164 onEntryLoading() {
@@ -186,8 +185,12 @@ const Cls = (app.views.Content = class Content extends app.View { · 20 unmodified lines
186 }185 }
187 186
188 cacheScrollPosition() {187 cacheScrollPosition() {
189 if (!this.routeCtx || this.routeCtx.hash) { return; }188 if (!this.routeCtx || this.routeCtx.hash) {
190 if (this.routeCtx.path === '/') { return; }189 return;
190 }
191 if (this.routeCtx.path === "/") {
192 return;
193 }
191 194
192 if (this.scrollMap[this.routeCtx.state.id] == null) {195 if (this.scrollMap[this.routeCtx.state.id] == null) {
193 this.scrollStack.push(this.routeCtx.state.id);196 this.scrollStack.push(this.routeCtx.state.id);
@@ -200,24 +203,24 @@ const Cls = (app.views.Content = class Content extends app.View { · 6 unmodified lines
200 }203 }
201 204
202 afterRoute(route, context) {205 afterRoute(route, context) {
203 if ((route !== 'entry') && (route !== 'type')) {206 if (route !== "entry" && route !== "type") {
204 resetFavicon();207 resetFavicon();
205 }208 }
206 209
207 switch (route) {210 switch (route) {
208 case 'root':211 case "root":
209 this.show(this.rootPage);212 this.show(this.rootPage);
210 break;213 break;
211 case 'entry':214 case "entry":
212 this.show(this.entryPage);215 this.show(this.entryPage);
213 break;216 break;
214 case 'type':217 case "type":
215 this.show(this.typePage);218 this.show(this.typePage);
216 break;219 break;
217 case 'settings':220 case "settings":
218 this.show(this.settingsPage);221 this.show(this.settingsPage);
219 break;222 break;
220 case 'offline':223 case "offline":
221 this.show(this.offlinePage);224 this.show(this.offlinePage);
222 break;225 break;
223 default:226 default:
@@ -225,37 +228,59 @@ const Cls = (app.views.Content = class Content extends app.View { · 1 unmodified line
225 }228 }
226 229
227 this.view.onRoute(context);230 this.view.onRoute(context);
228 app.document.setTitle(typeof this.view.getTitle === 'function' ? this.view.getTitle() : undefined);231 app.document.setTitle(
232 typeof this.view.getTitle === "function"
233 ? this.view.getTitle()
234 : undefined,
235 );
229 }236 }
230 237
231 onClick(event) {238 onClick(event) {
232 const link = $.closestLink($.eventTarget(event), this.el);239 const link = $.closestLink($.eventTarget(event), this.el);
233 if (link && this.isExternalUrl(link.getAttribute('href'))) {240 if (link && this.isExternalUrl(link.getAttribute("href"))) {
234 $.stopEvent(event);241 $.stopEvent(event);
235 $.popup(link);242 $.popup(link);
236 }243 }
237 }244 }
238 245
239 onAltF(event) {246 onAltF(event) {
240 if (!document.activeElement || !$.hasChild(this.el, document.activeElement)) {247 if (
241 __guard__(this.find('a:not(:empty)'), x => x.focus());248 !document.activeElement ||
249 !$.hasChild(this.el, document.activeElement)
250 ) {
251 __guard__(this.find("a:not(:empty)"), (x) => x.focus());
242 return $.stopEvent(event);252 return $.stopEvent(event);
243 }253 }
244 }254 }
245 255
246 findTargetByHash(hash) {256 findTargetByHash(hash) {
247 let el = (() => { try { return $.id(decodeURIComponent(hash)); } catch (error) {} })();257 let el = (() => {
248 if (!el) { el = (() => { try { return $.id(hash); } catch (error1) {} })(); }258 try {
259 return $.id(decodeURIComponent(hash));
260 } catch (error) {}
261 })();
262 if (!el) {
263 el = (() => {
264 try {
265 return $.id(hash);
266 } catch (error1) {}
267 })();
268 }
249 return el;269 return el;
250 }270 }
251 271
252 isExternalUrl(url) {272 isExternalUrl(url) {
253 let needle;273 let needle;
254 return (needle = __guard__(url, x => x.slice(0, 6)), ['http:/', 'https:'].includes(needle));274 return (
275 (needle = __guard__(url, (x) => x.slice(0, 6))),
276 ["http:/", "https:"].includes(needle)
277 );
255 }278 }
256});279});
257Cls.initClass();280Cls.initClass();
258 281
259function __guard__(value, transform) {282function __guard__(value, transform) {
260 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;283 return typeof value !== "undefined" && value !== null
261}284 ? transform(value)
285 : undefined;
286}
assets/javascripts/views/content/entry_page.js+94−47
@@ -10,7 +10,7 @@ · 9 unmodified lines
10 * DS207: Consider shorter variations of null checks10 * DS207: Consider shorter variations of null checks
11 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md11 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
12 */12 */
13(function() {13(function () {
14 let LINKS = undefined;14 let LINKS = undefined;
15 const Cls = (app.views.EntryPage = class EntryPage extends app.View {15 const Cls = (app.views.EntryPage = class EntryPage extends app.View {
16 constructor(...args) {16 constructor(...args) {
@@ -24,23 +24,21 @@ · 7 unmodified lines
24 }24 }
25 25
26 static initClass() {26 static initClass() {
27 this.className = '_page';27 this.className = "_page";
28 this.errorClass = '_page-error';28 this.errorClass = "_page-error";
29 29
30 this.events =30 this.events = { click: "onClick" };
31 {click: 'onClick'};31
32
33 this.shortcuts = {32 this.shortcuts = {
34 altC: 'onAltC',33 altC: "onAltC",
35 altO: 'onAltO'34 altO: "onAltO",
36 };35 };
37 36
38 this.routes =37 this.routes = { before: "beforeRoute" };
39 {before: 'beforeRoute'};38
40
41 LINKS = {39 LINKS = {
42 home: 'Homepage',40 home: "Homepage",
43 code: 'Source code'41 code: "Source code",
44 };42 };
45 }43 }
46 44
@@ -58,61 +56,84 @@ · 11 unmodified lines
58 56
59 loading() {57 loading() {
60 this.empty();58 this.empty();
61 this.trigger('loading');59 this.trigger("loading");
62 }60 }
63 61
64 render(content, fromCache) {62 render(content, fromCache) {
65 if (content == null) { content = ''; }63 if (content == null) {
66 if (fromCache == null) { fromCache = false; }64 content = "";
67 if (!this.activated) { return; }65 }
66 if (fromCache == null) {
67 fromCache = false;
68 }
69 if (!this.activated) {
70 return;
71 }
68 this.empty();72 this.empty();
69 this.subview = new (this.subViewClass())(this.el, this.entry);73 this.subview = new (this.subViewClass())(this.el, this.entry);
70 74
71 $.batchUpdate(this.el, () => {75 $.batchUpdate(this.el, () => {
72 this.subview.render(content, fromCache);76 this.subview.render(content, fromCache);
73 if (!fromCache) { this.addCopyButtons(); }77 if (!fromCache) {
78 this.addCopyButtons();
79 }
74 });80 });
75 81
76 if (app.disabledDocs.findBy('slug', this.entry.doc.slug)) {82 if (app.disabledDocs.findBy("slug", this.entry.doc.slug)) {
77 this.hiddenView = new app.views.HiddenPage(this.el, this.entry);83 this.hiddenView = new app.views.HiddenPage(this.el, this.entry);
78 }84 }
79 85
80 setFaviconForDoc(this.entry.doc);86 setFaviconForDoc(this.entry.doc);
81 this.delay(this.polyfillMathML);87 this.delay(this.polyfillMathML);
82 this.trigger('loaded');88 this.trigger("loaded");
83 }89 }
84 90
85 addCopyButtons() {91 addCopyButtons() {
86 if (!this.copyButton) {92 if (!this.copyButton) {
87 this.copyButton = document.createElement('button');93 this.copyButton = document.createElement("button");
88 this.copyButton.innerHTML = '<svg><use xlink:href="#icon-copy"/></svg>';94 this.copyButton.innerHTML = '<svg><use xlink:href="#icon-copy"/></svg>';
89 this.copyButton.type = 'button';95 this.copyButton.type = "button";
90 this.copyButton.className = '_pre-clip';96 this.copyButton.className = "_pre-clip";
91 this.copyButton.title = 'Copy to clipboard';97 this.copyButton.title = "Copy to clipboard";
92 this.copyButton.setAttribute('aria-label', 'Copy to clipboard');98 this.copyButton.setAttribute("aria-label", "Copy to clipboard");
99 }
100 for (var el of Array.from(this.findAllByTag("pre"))) {
101 el.appendChild(this.copyButton.cloneNode(true));
93 }102 }
94 for (var el of Array.from(this.findAllByTag('pre'))) { el.appendChild(this.copyButton.cloneNode(true)); }
95 }103 }
96 104
97 polyfillMathML() {105 polyfillMathML() {
98 if ((window.supportsMathML !== false) || !!this.polyfilledMathML || !this.findByTag('math')) { return; }106 if (
107 window.supportsMathML !== false ||
108 !!this.polyfilledMathML ||
109 !this.findByTag("math")
110 ) {
111 return;
112 }
99 this.polyfilledMathML = true;113 this.polyfilledMathML = true;
100 $.append(document.head, `<link rel="stylesheet" href="${app.config.mathml_stylesheet}">`);114 $.append(
115 document.head,
116 `<link rel="stylesheet" href="${app.config.mathml_stylesheet}">`,
117 );
101 }118 }
102 119
103 prepareContent(content) {120 prepareContent(content) {
104 if (!this.entry.isIndex() || !this.entry.doc.links) { return content; }121 if (!this.entry.isIndex() || !this.entry.doc.links) {
122 return content;
123 }
105 124
106 const links = (() => {125 const links = (() => {
107 const result = [];126 const result = [];
108 for (var link in this.entry.doc.links) {127 for (var link in this.entry.doc.links) {
109 var url = this.entry.doc.links[link];128 var url = this.entry.doc.links[link];
110 result.push(`<a href="${url}" class="_links-link">${LINKS[link]}</a>`);129 result.push(
130 `<a href="${url}" class="_links-link">${LINKS[link]}</a>`,
131 );
111 }132 }
112 return result;133 return result;
113 })();134 })();
114 135
115 return `<p class="_links">${links.join('')}</p>${content}`;136 return `<p class="_links">${links.join("")}</p>${content}`;
116 }137 }
117 138
118 empty() {139 empty() {
@@ -131,11 +152,17 @@ · 12 unmodified lines
131 }152 }
132 153
133 subViewClass() {154 subViewClass() {
134 return app.views[`${$.classify(this.entry.doc.type)}Page`] || app.views.BasePage;155 return (
156 app.views[`${$.classify(this.entry.doc.type)}Page`] ||
157 app.views.BasePage
158 );
135 }159 }
136 160
137 getTitle() {161 getTitle() {
138 return this.entry.doc.fullName + (this.entry.isIndex() ? ' documentation' : ` / ${this.entry.name}`);162 return (
163 this.entry.doc.fullName +
164 (this.entry.isIndex() ? " documentation" : ` / ${this.entry.name}`)
165 );
139 }166 }
140 167
141 beforeRoute() {168 beforeRoute() {
@@ -144,9 +171,13 @@ · 2 unmodified lines
144 }171 }
145 172
146 onRoute(context) {173 onRoute(context) {
147 const isSameFile = context.entry.filePath() === (this.entry != null ? this.entry.filePath() : undefined);174 const isSameFile =
175 context.entry.filePath() ===
176 (this.entry != null ? this.entry.filePath() : undefined);
148 this.entry = context.entry;177 this.entry = context.entry;
149 if (!isSameFile) { this.restore() || this.load(); }178 if (!isSameFile) {
179 this.restore() || this.load();
180 }
150 }181 }
151 182
152 load() {183 load() {
@@ -157,19 +188,21 @@ · 4 unmodified lines
157 abort() {188 abort() {
158 if (this.xhr) {189 if (this.xhr) {
159 this.xhr.abort();190 this.xhr.abort();
160 this.xhr = (this.entry = null);191 this.xhr = this.entry = null;
161 }192 }
162 }193 }
163 194
164 onSuccess(response) {195 onSuccess(response) {
165 if (!this.activated) { return; }196 if (!this.activated) {
197 return;
198 }
166 this.xhr = null;199 this.xhr = null;
167 this.render(this.prepareContent(response));200 this.render(this.prepareContent(response));
168 }201 }
169 202
170 onError() {203 onError() {
171 this.xhr = null;204 this.xhr = null;
172 this.render(this.tmpl('pageLoadError'));205 this.render(this.tmpl("pageLoadError"));
173 this.resetClass();206 this.resetClass();
174 this.addClass(this.constructor.errorClass);207 this.addClass(this.constructor.errorClass);
175 if (app.serviceWorker != null) {208 if (app.serviceWorker != null) {
@@ -179,7 +212,13 @@ · 3 unmodified lines
179 212
180 cache() {213 cache() {
181 let path;214 let path;
182 if (this.xhr || !this.entry || this.cacheMap[(path = this.entry.filePath())]) { return; }215 if (
216 this.xhr ||
217 !this.entry ||
218 this.cacheMap[(path = this.entry.filePath())]
219 ) {
220 return;
221 }
183 222
184 this.cacheMap[path] = this.el.innerHTML;223 this.cacheMap[path] = this.el.innerHTML;
185 this.cacheStack.push(path);224 this.cacheStack.push(path);
@@ -199,26 +238,34 @@ · 13 unmodified lines
199 238
200 onClick(event) {239 onClick(event) {
201 const target = $.eventTarget(event);240 const target = $.eventTarget(event);
202 if (target.hasAttribute('data-retry')) {241 if (target.hasAttribute("data-retry")) {
203 $.stopEvent(event);242 $.stopEvent(event);
204 this.load();243 this.load();
205 } else if (target.classList.contains('_pre-clip')) {244 } else if (target.classList.contains("_pre-clip")) {
206 $.stopEvent(event);245 $.stopEvent(event);
207 target.classList.add($.copyToClipboard(target.parentNode.textContent) ? '_pre-clip-success' : '_pre-clip-error');246 target.classList.add(
208 setTimeout((() => target.className = '_pre-clip'), 2000);247 $.copyToClipboard(target.parentNode.textContent)
248 ? "_pre-clip-success"
249 : "_pre-clip-error",
250 );
251 setTimeout(() => (target.className = "_pre-clip"), 2000);
209 }252 }
210 }253 }
211 254
212 onAltC() {255 onAltC() {
213 let link;256 let link;
214 if (!(link = this.find('._attribution:last-child ._attribution-link'))) { return; }257 if (!(link = this.find("._attribution:last-child ._attribution-link"))) {
258 return;
259 }
215 console.log(link.href + location.hash);260 console.log(link.href + location.hash);
216 navigator.clipboard.writeText(link.href + location.hash);261 navigator.clipboard.writeText(link.href + location.hash);
217 }262 }
218 263
219 onAltO() {264 onAltO() {
220 let link;265 let link;
221 if (!(link = this.find('._attribution:last-child ._attribution-link'))) { return; }266 if (!(link = this.find("._attribution:last-child ._attribution-link"))) {
267 return;
268 }
222 this.delay(() => $.popup(link.href + location.hash));269 this.delay(() => $.popup(link.href + location.hash));
223 }270 }
224 });271 });
assets/javascripts/views/content/offline_page.js+70−37
@@ -15,11 +15,11 @@ const Cls = (app.views.OfflinePage = class OfflinePage extends app.View { · 14 unmodified lines
15 }15 }
16 16
17 static initClass() {17 static initClass() {
18 this.className = '_static';18 this.className = "_static";
19 19
20 this.events = {20 this.events = {
21 click: 'onClick',21 click: "onClick",
22 change: 'onChange'22 change: "onChange",
23 };23 };
24 }24 }
25 25
@@ -31,41 +31,49 @@ const Cls = (app.views.OfflinePage = class OfflinePage extends app.View { · 5 unmodified lines
31 31
32 render() {32 render() {
33 if (app.cookieBlocked) {33 if (app.cookieBlocked) {
34 this.html(this.tmpl('offlineError', 'cookie_blocked'));34 this.html(this.tmpl("offlineError", "cookie_blocked"));
35 return;35 return;
36 }36 }
37 37
38 app.docs.getInstallStatuses(statuses => {38 app.docs.getInstallStatuses((statuses) => {
39 if (!this.activated) { return; }39 if (!this.activated) {
40 return;
41 }
40 if (statuses === false) {42 if (statuses === false) {
41 this.html(this.tmpl('offlineError', app.db.reason, app.db.error));43 this.html(this.tmpl("offlineError", app.db.reason, app.db.error));
42 } else {44 } else {
43 let html = '';45 let html = "";
44 for (var doc of Array.from(app.docs.all())) { html += this.renderDoc(doc, statuses[doc.slug]); }46 for (var doc of Array.from(app.docs.all())) {
45 this.html(this.tmpl('offlinePage', html));47 html += this.renderDoc(doc, statuses[doc.slug]);
48 }
49 this.html(this.tmpl("offlinePage", html));
46 this.refreshLinks();50 this.refreshLinks();
47 }51 }
48 });52 });
49 }53 }
50 54
51 renderDoc(doc, status) {55 renderDoc(doc, status) {
52 return app.templates.render('offlineDoc', doc, status);56 return app.templates.render("offlineDoc", doc, status);
53 }57 }
54 58
55 getTitle() {59 getTitle() {
56 return 'Offline';60 return "Offline";
57 }61 }
58 62
59 refreshLinks() {63 refreshLinks() {
60 for (var action of ['install', 'update', 'uninstall']) {64 for (var action of ["install", "update", "uninstall"]) {
61 this.find(`[data-action-all='${action}']`).classList[this.find(`[data-action='${action}']`) ? 'add' : 'remove']('_show');65 this.find(`[data-action-all='${action}']`).classList[
66 this.find(`[data-action='${action}']`) ? "add" : "remove"
67 ]("_show");
62 }68 }
63 }69 }
64 70
65 docByEl(el) {71 docByEl(el) {
66 let slug;72 let slug;
67 while (!(slug = el.getAttribute('data-slug'))) { el = el.parentNode; }73 while (!(slug = el.getAttribute("data-slug"))) {
68 return app.docs.findBy('slug', slug);74 el = el.parentNode;
75 }
76 return app.docs.findBy("slug", slug);
69 }77 }
70 78
71 docEl(doc) {79 docEl(doc) {
@@ -79,26 +87,44 @@ const Cls = (app.views.OfflinePage = class OfflinePage extends app.View { · 7 unmodified lines
79 onClick(event) {87 onClick(event) {
80 let action;88 let action;
81 let el = $.eventTarget(event);89 let el = $.eventTarget(event);
82 if (action = el.getAttribute('data-action')) {90 if ((action = el.getAttribute("data-action"))) {
83 const doc = this.docByEl(el);91 const doc = this.docByEl(el);
84 if (action === 'update') { action = 'install'; }92 if (action === "update") {
85 doc[action](this.onInstallSuccess.bind(this, doc), this.onInstallError.bind(this, doc), this.onInstallProgress.bind(this, doc));93 action = "install";
86 el.parentNode.innerHTML = `${el.textContent.replace(/e$/, '')}ing…`;94 }
87 } else if (action = el.getAttribute('data-action-all') || el.parentElement.getAttribute('data-action-all')) {95 doc[action](
88 if ((action === 'uninstall') && !window.confirm('Uninstall all docs?')) { return; }96 this.onInstallSuccess.bind(this, doc),
97 this.onInstallError.bind(this, doc),
98 this.onInstallProgress.bind(this, doc),
99 );
100 el.parentNode.innerHTML = `${el.textContent.replace(/e$/, "")}ing…`;
101 } else if (
102 (action =
103 el.getAttribute("data-action-all") ||
104 el.parentElement.getAttribute("data-action-all"))
105 ) {
106 if (action === "uninstall" && !window.confirm("Uninstall all docs?")) {
107 return;
108 }
89 app.db.migrate();109 app.db.migrate();
90 for (el of Array.from(this.findAll(`[data-action='${action}']`))) { $.click(el); }110 for (el of Array.from(this.findAll(`[data-action='${action}']`))) {
111 $.click(el);
112 }
91 }113 }
92 }114 }
93 115
94 onInstallSuccess(doc) {116 onInstallSuccess(doc) {
95 if (!this.activated) { return; }117 if (!this.activated) {
96 doc.getInstallStatus(status => {118 return;
119 }
120 doc.getInstallStatus((status) => {
97 let el;121 let el;
98 if (!this.activated) { return; }122 if (!this.activated) {
99 if (el = this.docEl(doc)) {123 return;
124 }
125 if ((el = this.docEl(doc))) {
100 el.outerHTML = this.renderDoc(doc, status);126 el.outerHTML = this.renderDoc(doc, status);
101 $.highlight(el, {className: '_highlight'});127 $.highlight(el, { className: "_highlight" });
102 this.refreshLinks();128 this.refreshLinks();
103 }129 }
104 });130 });
@@ -106,24 +132,31 @@ const Cls = (app.views.OfflinePage = class OfflinePage extends app.View { · 1 unmodified line
106 132
107 onInstallError(doc) {133 onInstallError(doc) {
108 let el;134 let el;
109 if (!this.activated) { return; }135 if (!this.activated) {
110 if (el = this.docEl(doc)) {136 return;
111 el.lastElementChild.textContent = 'Error';137 }
138 if ((el = this.docEl(doc))) {
139 el.lastElementChild.textContent = "Error";
112 }140 }
113 }141 }
114 142
115 onInstallProgress(doc, event) {143 onInstallProgress(doc, event) {
116 let el;144 let el;
117 if (!this.activated || !event.lengthComputable) { return; }145 if (!this.activated || !event.lengthComputable) {
118 if (el = this.docEl(doc)) {146 return;
147 }
148 if ((el = this.docEl(doc))) {
119 const percentage = Math.round((event.loaded * 100) / event.total);149 const percentage = Math.round((event.loaded * 100) / event.total);
120 el.lastElementChild.textContent = el.lastElementChild.textContent.replace(/(\s.+)?$/, ` (${percentage}%)`);150 el.lastElementChild.textContent = el.lastElementChild.textContent.replace(
151 /(\s.+)?$/,
152 ` (${percentage}%)`,
153 );
121 }154 }
122 }155 }
123 156
124 onChange(event) {157 onChange(event) {
125 if (event.target.name === 'autoUpdate') {158 if (event.target.name === "autoUpdate") {
126 app.settings.set('manualUpdate', !event.target.checked);159 app.settings.set("manualUpdate", !event.target.checked);
127 }160 }
128 }161 }
129});162});
assets/javascripts/views/content/root_page.js+14−14
@@ -14,26 +14,26 @@ const Cls = (app.views.RootPage = class RootPage extends app.View { · 13 unmodified lines
14 }14 }
15 15
16 static initClass() {16 static initClass() {
17 this.events =17 this.events = { click: "onClick" };
18 {click: 'onClick'};
19 }18 }
20 19
21 init() {20 init() {
22 if (!this.isHidden()) { this.setHidden(false); } // reserve space in local storage21 if (!this.isHidden()) {
22 this.setHidden(false);
23 } // reserve space in local storage
23 this.render();24 this.render();
24 }25 }
25 26
26 render() {27 render() {
27 this.empty();28 this.empty();
28 29
29 const tmpl = app.isAndroidWebview() ?30 const tmpl = app.isAndroidWebview()
30 'androidWarning'31 ? "androidWarning"
31 : this.isHidden() ?32 : this.isHidden()
32 'splash'33 ? "splash"
33 : app.isMobile() ?34 : app.isMobile()
34 'mobileIntro'35 ? "mobileIntro"
35 :36 : "intro";
36 'intro';
37 37
38 this.append(this.tmpl(tmpl));38 this.append(this.tmpl(tmpl));
39 }39 }
@@ -44,17 +44,17 @@ const Cls = (app.views.RootPage = class RootPage extends app.View { · 4 unmodified lines
44 }44 }
45 45
46 setHidden(value) {46 setHidden(value) {
47 app.settings.set('hideIntro', value);47 app.settings.set("hideIntro", value);
48 }48 }
49 49
50 isHidden() {50 isHidden() {
51 return app.isSingleDoc() || app.settings.get('hideIntro');51 return app.isSingleDoc() || app.settings.get("hideIntro");
52 }52 }
53 53
54 onRoute() {}54 onRoute() {}
55 55
56 onClick(event) {56 onClick(event) {
57 if ($.eventTarget(event).hasAttribute('data-hide-intro')) {57 if ($.eventTarget(event).hasAttribute("data-hide-intro")) {
58 $.stopEvent(event);58 $.stopEvent(event);
59 this.hideIntro();59 this.hideIntro();
60 }60 }
assets/javascripts/views/content/settings_page.js+51−41
@@ -17,39 +17,41 @@ const Cls = (app.views.SettingsPage = class SettingsPage extends app.View { · 16 unmodified lines
17 }17 }
18 18
19 static initClass() {19 static initClass() {
20 this.className = '_static';20 this.className = "_static";
21 21
22 this.events = {22 this.events = {
23 click: 'onClick',23 click: "onClick",
24 change: 'onChange'24 change: "onChange",
25 };25 };
26 }26 }
27 27
28 render() {28 render() {
29 this.html(this.tmpl('settingsPage', this.currentSettings()));29 this.html(this.tmpl("settingsPage", this.currentSettings()));
30 }30 }
31 31
32 currentSettings() {32 currentSettings() {
33 const settings = {};33 const settings = {};
34 settings.theme = app.settings.get('theme');34 settings.theme = app.settings.get("theme");
35 settings.smoothScroll = !app.settings.get('fastScroll');35 settings.smoothScroll = !app.settings.get("fastScroll");
36 settings.arrowScroll = app.settings.get('arrowScroll');36 settings.arrowScroll = app.settings.get("arrowScroll");
37 settings.noAutofocus = app.settings.get('noAutofocus');37 settings.noAutofocus = app.settings.get("noAutofocus");
38 settings.autoInstall = app.settings.get('autoInstall');38 settings.autoInstall = app.settings.get("autoInstall");
39 settings.analyticsConsent = app.settings.get('analyticsConsent');39 settings.analyticsConsent = app.settings.get("analyticsConsent");
40 settings.spaceScroll = app.settings.get('spaceScroll');40 settings.spaceScroll = app.settings.get("spaceScroll");
41 settings.spaceTimeout = app.settings.get('spaceTimeout');41 settings.spaceTimeout = app.settings.get("spaceTimeout");
42 settings.autoSupported = app.settings.autoSupported;42 settings.autoSupported = app.settings.autoSupported;
43 for (var layout of Array.from(app.settings.LAYOUTS)) { settings[layout] = app.settings.hasLayout(layout); }43 for (var layout of Array.from(app.settings.LAYOUTS)) {
44 settings[layout] = app.settings.hasLayout(layout);
45 }
44 return settings;46 return settings;
45 }47 }
46 48
47 getTitle() {49 getTitle() {
48 return 'Preferences';50 return "Preferences";
49 }51 }
50 52
51 setTheme(value) {53 setTheme(value) {
52 app.settings.set('theme', value);54 app.settings.set("theme", value);
53 }55 }
54 56
55 toggleLayout(layout, enable) {57 toggleLayout(layout, enable) {
@@ -57,20 +59,22 @@ const Cls = (app.views.SettingsPage = class SettingsPage extends app.View { · 1 unmodified line
57 }59 }
58 60
59 toggleSmoothScroll(enable) {61 toggleSmoothScroll(enable) {
60 app.settings.set('fastScroll', !enable);62 app.settings.set("fastScroll", !enable);
61 }63 }
62 64
63 toggleAnalyticsConsent(enable) {65 toggleAnalyticsConsent(enable) {
64 app.settings.set('analyticsConsent', enable ? '1' : '0');66 app.settings.set("analyticsConsent", enable ? "1" : "0");
65 if (!enable) { resetAnalytics(); }67 if (!enable) {
68 resetAnalytics();
69 }
66 }70 }
67 71
68 toggleSpaceScroll(enable) {72 toggleSpaceScroll(enable) {
69 app.settings.set('spaceScroll', enable ? 1 : 0);73 app.settings.set("spaceScroll", enable ? 1 : 0);
70 }74 }
71 75
72 setScrollTimeout(value) {76 setScrollTimeout(value) {
73 return app.settings.set('spaceTimeout', value);77 return app.settings.set("spaceTimeout", value);
74 }78 }
75 79
76 toggle(name, enable) {80 toggle(name, enable) {
@@ -78,31 +82,37 @@ const Cls = (app.views.SettingsPage = class SettingsPage extends app.View { · 1 unmodified line
78 }82 }
79 83
80 export() {84 export() {
81 const data = new Blob([JSON.stringify(app.settings.export())], {type: 'application/json'});85 const data = new Blob([JSON.stringify(app.settings.export())], {
82 const link = document.createElement('a');86 type: "application/json",
87 });
88 const link = document.createElement("a");
83 link.href = URL.createObjectURL(data);89 link.href = URL.createObjectURL(data);
84 link.download = 'devdocs.json';90 link.download = "devdocs.json";
85 link.style.display = 'none';91 link.style.display = "none";
86 document.body.appendChild(link);92 document.body.appendChild(link);
87 link.click();93 link.click();
88 document.body.removeChild(link);94 document.body.removeChild(link);
89 }95 }
90 96
91 import(file, input) {97 import(file, input) {
92 if (!file || (file.type !== 'application/json')) {98 if (!file || file.type !== "application/json") {
93 new app.views.Notif('ImportInvalid', {autoHide: false});99 new app.views.Notif("ImportInvalid", { autoHide: false });
94 return;100 return;
95 }101 }
96 102
97 const reader = new FileReader();103 const reader = new FileReader();
98 reader.onloadend = function() {104 reader.onloadend = function () {
99 const data = (() => { try { return JSON.parse(reader.result); } catch (error) {} })();105 const data = (() => {
100 if (!data || (data.constructor !== Object)) {106 try {
101 new app.views.Notif('ImportInvalid', {autoHide: false});107 return JSON.parse(reader.result);
108 } catch (error) {}
109 })();
110 if (!data || data.constructor !== Object) {
111 new app.views.Notif("ImportInvalid", { autoHide: false });
102 return;112 return;
103 }113 }
104 app.settings.import(data);114 app.settings.import(data);
105 $.trigger(input.form, 'import');115 $.trigger(input.form, "import");
106 };116 };
107 reader.readAsText(file);117 reader.readAsText(file);
108 }118 }
@@ -110,25 +120,25 @@ const Cls = (app.views.SettingsPage = class SettingsPage extends app.View { · 1 unmodified line
110 onChange(event) {120 onChange(event) {
111 const input = event.target;121 const input = event.target;
112 switch (input.name) {122 switch (input.name) {
113 case 'theme':123 case "theme":
114 this.setTheme(input.value);124 this.setTheme(input.value);
115 break;125 break;
116 case 'layout':126 case "layout":
117 this.toggleLayout(input.value, input.checked);127 this.toggleLayout(input.value, input.checked);
118 break;128 break;
119 case 'smoothScroll':129 case "smoothScroll":
120 this.toggleSmoothScroll(input.checked);130 this.toggleSmoothScroll(input.checked);
121 break;131 break;
122 case 'import':132 case "import":
123 this.import(input.files[0], input);133 this.import(input.files[0], input);
124 break;134 break;
125 case 'analyticsConsent':135 case "analyticsConsent":
126 this.toggleAnalyticsConsent(input.checked);136 this.toggleAnalyticsConsent(input.checked);
127 break;137 break;
128 case 'spaceScroll':138 case "spaceScroll":
129 this.toggleSpaceScroll(input.checked);139 this.toggleSpaceScroll(input.checked);
130 break;140 break;
131 case 'spaceTimeout':141 case "spaceTimeout":
132 this.setScrollTimeout(input.value);142 this.setScrollTimeout(input.value);
133 break;143 break;
134 default:144 default:
@@ -138,8 +148,8 @@ const Cls = (app.views.SettingsPage = class SettingsPage extends app.View { · 3 unmodified lines
138 148
139 onClick(event) {149 onClick(event) {
140 const target = $.eventTarget(event);150 const target = $.eventTarget(event);
141 switch (target.getAttribute('data-action')) {151 switch (target.getAttribute("data-action")) {
142 case 'export':152 case "export":
143 $.stopEvent(event);153 $.stopEvent(event);
144 this.export();154 this.export();
145 break;155 break;
assets/javascripts/views/content/static_page.js+7−7
@@ -8,13 +8,13 @@ · 7 unmodified lines
8 */8 */
9const Cls = (app.views.StaticPage = class StaticPage extends app.View {9const Cls = (app.views.StaticPage = class StaticPage extends app.View {
10 static initClass() {10 static initClass() {
11 this.className = '_static';11 this.className = "_static";
12 12
13 this.titles = {13 this.titles = {
14 about: 'About',14 about: "About",
15 news: 'News',15 news: "News",
16 help: 'User Guide',16 help: "User Guide",
17 notFound: '404'17 notFound: "404",
18 };18 };
19 }19 }
20 20
@@ -35,7 +35,7 @@ const Cls = (app.views.StaticPage = class StaticPage extends app.View { · 14 unmodified lines
35 }35 }
36 36
37 onRoute(context) {37 onRoute(context) {
38 this.render(context.page || 'notFound');38 this.render(context.page || "notFound");
39 }39 }
40});40});
41Cls.initClass();41Cls.initClass();
assets/javascripts/views/content/type_page.js+2−2
@@ -8,7 +8,7 @@ · 7 unmodified lines
8 */8 */
9const Cls = (app.views.TypePage = class TypePage extends app.View {9const Cls = (app.views.TypePage = class TypePage extends app.View {
10 static initClass() {10 static initClass() {
11 this.className = '_page';11 this.className = "_page";
12 }12 }
13 13
14 deactivate() {14 deactivate() {
@@ -20,7 +20,7 @@ const Cls = (app.views.TypePage = class TypePage extends app.View { · 5 unmodified lines
20 20
21 render(type) {21 render(type) {
22 this.type = type;22 this.type = type;
23 this.html(this.tmpl('typePage', this.type));23 this.html(this.tmpl("typePage", this.type));
24 setFaviconForDoc(this.type.doc);24 setFaviconForDoc(this.type.doc);
25 }25 }
26 26
assets/javascripts/views/layout/document.js+69−40
@@ -17,41 +17,49 @@ const Cls = (app.views.Document = class Document extends app.View { · 16 unmodified lines
17 17
18 static initClass() {18 static initClass() {
19 this.el = document;19 this.el = document;
20 20
21 this.events =21 this.events = { visibilitychange: "onVisibilityChange" };
22 {visibilitychange: 'onVisibilityChange'};22
23
24 this.shortcuts = {23 this.shortcuts = {
25 help: 'onHelp',24 help: "onHelp",
26 preferences: 'onPreferences',25 preferences: "onPreferences",
27 escape: 'onEscape',26 escape: "onEscape",
28 superLeft: 'onBack',27 superLeft: "onBack",
29 superRight: 'onForward'28 superRight: "onForward",
30 };29 };
31 30
32 this.routes =31 this.routes = { after: "afterRoute" };
33 {after: 'afterRoute'};
34 }32 }
35 33
36 init() {34 init() {
37 this.addSubview((this.menu = new app.views.Menu),35 this.addSubview(
38 this.addSubview(this.sidebar = new app.views.Sidebar));36 (this.menu = new app.views.Menu()),
39 if (app.views.Resizer.isSupported()) { this.addSubview(this.resizer = new app.views.Resizer); }37 this.addSubview((this.sidebar = new app.views.Sidebar())),
40 this.addSubview(this.content = new app.views.Content);38 );
41 if (!app.isSingleDoc() && !app.isMobile()) { this.addSubview(this.path = new app.views.Path); }39 if (app.views.Resizer.isSupported()) {
42 if (!app.isSingleDoc()) { this.settings = new app.views.Settings; }40 this.addSubview((this.resizer = new app.views.Resizer()));
41 }
42 this.addSubview((this.content = new app.views.Content()));
43 if (!app.isSingleDoc() && !app.isMobile()) {
44 this.addSubview((this.path = new app.views.Path()));
45 }
46 if (!app.isSingleDoc()) {
47 this.settings = new app.views.Settings();
48 }
43 49
44 $.on(document.body, 'click', this.onClick);50 $.on(document.body, "click", this.onClick);
45 51
46 this.activate();52 this.activate();
47 }53 }
48 54
49 setTitle(title) {55 setTitle(title) {
50 return this.el.title = title ? `${title} — DevDocs` : 'DevDocs API Documentation';56 return (this.el.title = title
57 ? `${title} — DevDocs`
58 : "DevDocs API Documentation");
51 }59 }
52 60
53 afterRoute(route) {61 afterRoute(route) {
54 if (route === 'settings') {62 if (route === "settings") {
55 if (this.settings != null) {63 if (this.settings != null) {
56 this.settings.activate();64 this.settings.activate();
57 }65 }
@@ -63,26 +71,29 @@ const Cls = (app.views.Document = class Document extends app.View { · 5 unmodified lines
63 }71 }
64 72
65 onVisibilityChange() {73 onVisibilityChange() {
66 if (this.el.visibilityState !== 'visible') { return; }74 if (this.el.visibilityState !== "visible") {
67 this.delay(function() {75 return;
68 if (app.isMobile() !== app.views.Mobile.detect()) { location.reload(); }
69 }76 }
70 , 300);77 this.delay(function () {
78 if (app.isMobile() !== app.views.Mobile.detect()) {
79 location.reload();
80 }
81 }, 300);
71 }82 }
72 83
73 onHelp() {84 onHelp() {
74 app.router.show('/help#shortcuts');85 app.router.show("/help#shortcuts");
75 }86 }
76 87
77 onPreferences() {88 onPreferences() {
78 app.router.show('/settings');89 app.router.show("/settings");
79 }90 }
80 91
81 onEscape() {92 onEscape() {
82 const path = !app.isSingleDoc() || (location.pathname === app.doc.fullPath()) ?93 const path =
83 '/'94 !app.isSingleDoc() || location.pathname === app.doc.fullPath()
84 :95 ? "/"
85 app.doc.fullPath();96 : app.doc.fullPath();
86 97
87 app.router.show(path);98 app.router.show(path);
88 }99 }
@@ -97,16 +108,34 @@ const Cls = (app.views.Document = class Document extends app.View { · 8 unmodified lines
97 108
98 onClick(event) {109 onClick(event) {
99 const target = $.eventTarget(event);110 const target = $.eventTarget(event);
100 if (!target.hasAttribute('data-behavior')) { return; }111 if (!target.hasAttribute("data-behavior")) {
112 return;
113 }
101 $.stopEvent(event);114 $.stopEvent(event);
102 switch (target.getAttribute('data-behavior')) {115 switch (target.getAttribute("data-behavior")) {
103 case 'back': history.back(); break;116 case "back":
104 case 'reload': window.location.reload(); break;117 history.back();
105 case 'reboot': app.reboot(); break;118 break;
106 case 'hard-reload': app.reload(); break;119 case "reload":
107 case 'reset': if (confirm('Are you sure you want to reset DevDocs?')) { app.reset(); } break;120 window.location.reload();
108 case 'accept-analytics': Cookies.set('analyticsConsent', '1', {expires: 1e8}) && app.reboot(); break;121 break;
109 case 'decline-analytics': Cookies.set('analyticsConsent', '0', {expires: 1e8}) && app.reboot(); break;122 case "reboot":
123 app.reboot();
124 break;
125 case "hard-reload":
126 app.reload();
127 break;
128 case "reset":
129 if (confirm("Are you sure you want to reset DevDocs?")) {
130 app.reset();
131 }
132 break;
133 case "accept-analytics":
134 Cookies.set("analyticsConsent", "1", { expires: 1e8 }) && app.reboot();
135 break;
136 case "decline-analytics":
137 Cookies.set("analyticsConsent", "0", { expires: 1e8 }) && app.reboot();
138 break;
110 }139 }
111 }140 }
112});141});
assets/javascripts/views/layout/menu.js+16−9
@@ -13,25 +13,32 @@ const Cls = (app.views.Menu = class Menu extends app.View { · 12 unmodified lines
13 }13 }
14 14
15 static initClass() {15 static initClass() {
16 this.el = '._menu';16 this.el = "._menu";
17 this.activeClass = 'active';17 this.activeClass = "active";
18 18
19 this.events =19 this.events = { click: "onClick" };
20 {click: 'onClick'};
21 }20 }
22 21
23 init() {22 init() {
24 $.on(document.body, 'click', this.onGlobalClick);23 $.on(document.body, "click", this.onGlobalClick);
25 }24 }
26 25
27 onClick(event) {26 onClick(event) {
28 const target = $.eventTarget(event);27 const target = $.eventTarget(event);
29 if (target.tagName === 'A') { target.blur(); }28 if (target.tagName === "A") {
29 target.blur();
30 }
30 }31 }
31 32
32 onGlobalClick(event) {33 onGlobalClick(event) {
33 if (event.which !== 1) { return; }34 if (event.which !== 1) {
34 if (typeof event.target.hasAttribute === 'function' ? event.target.hasAttribute('data-toggle-menu') : undefined) {35 return;
36 }
37 if (
38 typeof event.target.hasAttribute === "function"
39 ? event.target.hasAttribute("data-toggle-menu")
40 : undefined
41 ) {
35 this.toggleClass(this.constructor.activeClass);42 this.toggleClass(this.constructor.activeClass);
36 } else if (this.hasClass(this.constructor.activeClass)) {43 } else if (this.hasClass(this.constructor.activeClass)) {
37 this.removeClass(this.constructor.activeClass);44 this.removeClass(this.constructor.activeClass);
assets/javascripts/views/layout/mobile.js+77−63
@@ -10,34 +10,36 @@ · 9 unmodified lines
10 */10 */
11const Cls = (app.views.Mobile = class Mobile extends app.View {11const Cls = (app.views.Mobile = class Mobile extends app.View {
12 static initClass() {12 static initClass() {
13 this.className = '_mobile';13 this.className = "_mobile";
14 14
15 this.elements = {15 this.elements = {
16 body: 'body',16 body: "body",
17 content: '._container',17 content: "._container",
18 sidebar: '._sidebar',18 sidebar: "._sidebar",
19 docPicker: '._settings ._sidebar'19 docPicker: "._settings ._sidebar",
20 };20 };
21 21
22 this.shortcuts =22 this.shortcuts = { escape: "onEscape" };
23 {escape: 'onEscape'};23
24 24 this.routes = { after: "afterRoute" };
25 this.routes =
26 {after: 'afterRoute'};
27 }25 }
28 26
29 static detect() {27 static detect() {
30 if (Cookies.get('override-mobile-detect') != null) {28 if (Cookies.get("override-mobile-detect") != null) {
31 return JSON.parse(Cookies.get('override-mobile-detect'));29 return JSON.parse(Cookies.get("override-mobile-detect"));
32 }30 }
33 try {31 try {
34 return (window.matchMedia('(max-width: 480px)').matches) ||32 return (
35 (window.matchMedia('(max-width: 767px)').matches) ||33 window.matchMedia("(max-width: 480px)").matches ||
36 (window.matchMedia('(max-height: 767px) and (max-width: 1024px)').matches) ||34 window.matchMedia("(max-width: 767px)").matches ||
37 // Need to sniff the user agent because some Android and Windows Phone devices don't take35 window.matchMedia("(max-height: 767px) and (max-width: 1024px)")
38 // resolution (dpi) into account when reporting device width/height.36 .matches ||
39 ((navigator.userAgent.indexOf('Android') !== -1) && (navigator.userAgent.indexOf('Mobile') !== -1)) ||37 // Need to sniff the user agent because some Android and Windows Phone devices don't take
40 (navigator.userAgent.indexOf('IEMobile') !== -1);38 // resolution (dpi) into account when reporting device width/height.
39 (navigator.userAgent.indexOf("Android") !== -1 &&
40 navigator.userAgent.indexOf("Mobile") !== -1) ||
41 navigator.userAgent.indexOf("IEMobile") !== -1
42 );
41 } catch (error) {43 } catch (error) {
42 return false;44 return false;
43 }45 }
@@ -67,30 +69,29 @@ const Cls = (app.views.Mobile = class Mobile extends app.View { · 23 unmodified lines
67 }69 }
68 70
69 init() {71 init() {
70 $.on($('._search'), 'touchend', this.onTapSearch);72 $.on($("._search"), "touchend", this.onTapSearch);
71 73
72 this.toggleSidebar = $('button[data-toggle-sidebar]');74 this.toggleSidebar = $("button[data-toggle-sidebar]");
73 this.toggleSidebar.removeAttribute('hidden');75 this.toggleSidebar.removeAttribute("hidden");
74 $.on(this.toggleSidebar, 'click', this.onClickToggleSidebar);76 $.on(this.toggleSidebar, "click", this.onClickToggleSidebar);
75 77
76 this.back = $('button[data-back]');78 this.back = $("button[data-back]");
77 this.back.removeAttribute('hidden');79 this.back.removeAttribute("hidden");
78 $.on(this.back, 'click', this.onClickBack);80 $.on(this.back, "click", this.onClickBack);
79 81
80 this.forward = $('button[data-forward]');82 this.forward = $("button[data-forward]");
81 this.forward.removeAttribute('hidden');83 this.forward.removeAttribute("hidden");
82 $.on(this.forward, 'click', this.onClickForward);84 $.on(this.forward, "click", this.onClickForward);
83 85
84 this.docPickerTab = $('button[data-tab="doc-picker"]');86 this.docPickerTab = $('button[data-tab="doc-picker"]');
85 this.docPickerTab.removeAttribute('hidden');87 this.docPickerTab.removeAttribute("hidden");
86 $.on(this.docPickerTab, 'click', this.onClickDocPickerTab);88 $.on(this.docPickerTab, "click", this.onClickDocPickerTab);
87 89
88 this.settingsTab = $('button[data-tab="settings"]');90 this.settingsTab = $('button[data-tab="settings"]');
89 this.settingsTab.removeAttribute('hidden');91 this.settingsTab.removeAttribute("hidden");
90 $.on(this.settingsTab, 'click', this.onClickSettingsTab);92 $.on(this.settingsTab, "click", this.onClickSettingsTab);
91 93
92 app.document.sidebar.search94 app.document.sidebar.search.on("searching", this.showSidebar);
93 .on('searching', this.showSidebar);
94 95
95 this.activate();96 this.activate();
96 }97 }
@@ -103,27 +104,36 @@ const Cls = (app.views.Mobile = class Mobile extends app.View { · 6 unmodified lines
103 }104 }
104 105
105 this.contentTop = window.scrollY;106 this.contentTop = window.scrollY;
106 this.content.style.display = 'none';107 this.content.style.display = "none";
107 this.sidebar.style.display = 'block';108 this.sidebar.style.display = "block";
108 109
109 if (selection = this.findByClass(app.views.ListSelect.activeClass)) {110 if ((selection = this.findByClass(app.views.ListSelect.activeClass))) {
110 const scrollContainer = window.scrollY === this.body.scrollTop ? this.body : document.documentElement;111 const scrollContainer =
111 $.scrollTo(selection, scrollContainer, 'center');112 window.scrollY === this.body.scrollTop
113 ? this.body
114 : document.documentElement;
115 $.scrollTo(selection, scrollContainer, "center");
112 } else {116 } else {
113 window.scrollTo(0, (this.findByClass(app.views.ListFold.activeClass) && this.sidebarTop) || 0);117 window.scrollTo(
118 0,
119 (this.findByClass(app.views.ListFold.activeClass) && this.sidebarTop) ||
120 0,
121 );
114 }122 }
115 }123 }
116 124
117 hideSidebar() {125 hideSidebar() {
118 if (!this.isSidebarShown()) { return; }126 if (!this.isSidebarShown()) {
127 return;
128 }
119 this.sidebarTop = window.scrollY;129 this.sidebarTop = window.scrollY;
120 this.sidebar.style.display = 'none';130 this.sidebar.style.display = "none";
121 this.content.style.display = 'block';131 this.content.style.display = "block";
122 window.scrollTo(0, this.contentTop || 0);132 window.scrollTo(0, this.contentTop || 0);
123 }133 }
124 134
125 isSidebarShown() {135 isSidebarShown() {
126 return this.sidebar.style.display !== 'none';136 return this.sidebar.style.display !== "none";
127 }137 }
128 138
129 onClickBack() {139 onClickBack() {
@@ -135,7 +145,11 @@ const Cls = (app.views.Mobile = class Mobile extends app.View { · 5 unmodified lines
135 }145 }
136 146
137 onClickToggleSidebar() {147 onClickToggleSidebar() {
138 if (this.isSidebarShown()) { this.hideSidebar(); } else { this.showSidebar(); }148 if (this.isSidebarShown()) {
149 this.hideSidebar();
150 } else {
151 this.showSidebar();
152 }
139 }153 }
140 154
141 onClickDocPickerTab(event) {155 onClickDocPickerTab(event) {
@@ -150,18 +164,18 @@ const Cls = (app.views.Mobile = class Mobile extends app.View { · 8 unmodified lines
150 164
151 showDocPicker() {165 showDocPicker() {
152 window.scrollTo(0, 0);166 window.scrollTo(0, 0);
153 this.docPickerTab.classList.add('active');167 this.docPickerTab.classList.add("active");
154 this.settingsTab.classList.remove('active');168 this.settingsTab.classList.remove("active");
155 this.docPicker.style.display = 'block';169 this.docPicker.style.display = "block";
156 this.content.style.display = 'none';170 this.content.style.display = "none";
157 }171 }
158 172
159 showSettings() {173 showSettings() {
160 window.scrollTo(0, 0);174 window.scrollTo(0, 0);
161 this.docPickerTab.classList.remove('active');175 this.docPickerTab.classList.remove("active");
162 this.settingsTab.classList.add('active');176 this.settingsTab.classList.add("active");
163 this.docPicker.style.display = 'none';177 this.docPicker.style.display = "none";
164 this.content.style.display = 'block';178 this.content.style.display = "block";
165 }179 }
166 180
167 onTapSearch() {181 onTapSearch() {
@@ -175,22 +189,22 @@ const Cls = (app.views.Mobile = class Mobile extends app.View { · 7 unmodified lines
175 afterRoute(route) {189 afterRoute(route) {
176 this.hideSidebar();190 this.hideSidebar();
177 191
178 if (route === 'settings') {192 if (route === "settings") {
179 this.showDocPicker();193 this.showDocPicker();
180 } else {194 } else {
181 this.content.style.display = 'block';195 this.content.style.display = "block";
182 }196 }
183 197
184 if (page.canGoBack()) {198 if (page.canGoBack()) {
185 this.back.removeAttribute('disabled');199 this.back.removeAttribute("disabled");
186 } else {200 } else {
187 this.back.setAttribute('disabled', 'disabled');201 this.back.setAttribute("disabled", "disabled");
188 }202 }
189 203
190 if (page.canGoForward()) {204 if (page.canGoForward()) {
191 this.forward.removeAttribute('disabled');205 this.forward.removeAttribute("disabled");
192 } else {206 } else {
193 this.forward.setAttribute('disabled', 'disabled');207 this.forward.setAttribute("disabled", "disabled");
194 }208 }
195 }209 }
196});210});
assets/javascripts/views/layout/path.js+16−13
@@ -15,33 +15,36 @@ const Cls = (app.views.Path = class Path extends app.View { · 14 unmodified lines
15 }15 }
16 16
17 static initClass() {17 static initClass() {
18 this.className = '_path';18 this.className = "_path";
19 this.attributes =19 this.attributes = { role: "complementary" };
20 {role: 'complementary'};20
21 21 this.events = { click: "onClick" };
22 this.events =22
23 {click: 'onClick'};23 this.routes = { after: "afterRoute" };
24
25 this.routes =
26 {after: 'afterRoute'};
27 }24 }
28 25
29 render(...args) {26 render(...args) {
30 this.html(this.tmpl('path', ...Array.from(args)));27 this.html(this.tmpl("path", ...Array.from(args)));
31 this.show();28 this.show();
32 }29 }
33 30
34 show() {31 show() {
35 if (!this.el.parentNode) { this.prependTo(app.el); }32 if (!this.el.parentNode) {
33 this.prependTo(app.el);
34 }
36 }35 }
37 36
38 hide() {37 hide() {
39 if (this.el.parentNode) { $.remove(this.el); }38 if (this.el.parentNode) {
39 $.remove(this.el);
40 }
40 }41 }
41 42
42 onClick(event) {43 onClick(event) {
43 let link;44 let link;
44 if (link = $.closestLink(event.target, this.el)) { this.clicked = true; }45 if ((link = $.closestLink(event.target, this.el))) {
46 this.clicked = true;
47 }
45 }48 }
46 49
47 afterRoute(route, context) {50 afterRoute(route, context) {
assets/javascripts/views/layout/resizer.js+32−20
@@ -7,7 +7,7 @@ · 6 unmodified lines
7 * DS206: Consider reworking classes to avoid initClass7 * DS206: Consider reworking classes to avoid initClass
8 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md8 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
9 */9 */
10(function() {10(function () {
11 let MIN = undefined;11 let MIN = undefined;
12 let MAX = undefined;12 let MAX = undefined;
13 const Cls = (app.views.Resizer = class Resizer extends app.View {13 const Cls = (app.views.Resizer = class Resizer extends app.View {
@@ -19,54 +19,66 @@ · 5 unmodified lines
19 }19 }
20 20
21 static initClass() {21 static initClass() {
22 this.className = '_resizer';22 this.className = "_resizer";
23 23
24 this.events = {24 this.events = {
25 dragstart: 'onDragStart',25 dragstart: "onDragStart",
26 dragend: 'onDragEnd'26 dragend: "onDragEnd",
27 };27 };
28 28
29 MIN = 260;29 MIN = 260;
30 MAX = 600;30 MAX = 600;
31 }31 }
32 32
33 static isSupported() {33 static isSupported() {
34 return 'ondragstart' in document.createElement('div') && !app.isMobile();34 return "ondragstart" in document.createElement("div") && !app.isMobile();
35 }35 }
36 36
37 init() {37 init() {
38 this.el.setAttribute('draggable', 'true');38 this.el.setAttribute("draggable", "true");
39 this.appendTo($('._app'));39 this.appendTo($("._app"));
40 }40 }
41 41
42 resize(value, save) {42 resize(value, save) {
43 value -= app.el.offsetLeft;43 value -= app.el.offsetLeft;
44 if (!(value > 0)) { return; }44 if (!(value > 0)) {
45 return;
46 }
45 value = Math.min(Math.max(Math.round(value), MIN), MAX);47 value = Math.min(Math.max(Math.round(value), MIN), MAX);
46 const newSize = `${value}px`;48 const newSize = `${value}px`;
47 document.documentElement.style.setProperty('--sidebarWidth', newSize);49 document.documentElement.style.setProperty("--sidebarWidth", newSize);
48 if (save) { app.settings.setSize(value); }50 if (save) {
51 app.settings.setSize(value);
52 }
49 }53 }
50 54
51 onDragStart(event) {55 onDragStart(event) {
52 event.dataTransfer.effectAllowed = 'link';56 event.dataTransfer.effectAllowed = "link";
53 event.dataTransfer.setData('Text', '');57 event.dataTransfer.setData("Text", "");
54 $.on(window, 'dragover', this.onDrag);58 $.on(window, "dragover", this.onDrag);
55 }59 }
56 60
57 onDrag(event) {61 onDrag(event) {
58 const value = event.pageX;62 const value = event.pageX;
59 if (!(value > 0)) { return; }63 if (!(value > 0)) {
64 return;
65 }
60 this.lastDragValue = value;66 this.lastDragValue = value;
61 if (this.lastDrag && (this.lastDrag > (Date.now() - 50))) { return; }67 if (this.lastDrag && this.lastDrag > Date.now() - 50) {
68 return;
69 }
62 this.lastDrag = Date.now();70 this.lastDrag = Date.now();
63 this.resize(value, false);71 this.resize(value, false);
64 }72 }
65 73
66 onDragEnd(event) {74 onDragEnd(event) {
67 $.off(window, 'dragover', this.onDrag);75 $.off(window, "dragover", this.onDrag);
68 let value = event.pageX || (event.screenX - window.screenX);76 let value = event.pageX || event.screenX - window.screenX;
69 if (this.lastDragValue && !(this.lastDragValue - 5 < value && value < this.lastDragValue + 5)) { // https://github.com/freeCodeCamp/devdocs/issues/26577 if (
78 this.lastDragValue &&
79 !(this.lastDragValue - 5 < value && value < this.lastDragValue + 5)
80 ) {
81 // https://github.com/freeCodeCamp/devdocs/issues/265
70 value = this.lastDragValue;82 value = this.lastDragValue;
71 }83 }
72 this.resize(value, true);84 this.resize(value, true);
assets/javascripts/views/layout/settings.js+41−33
@@ -10,7 +10,7 @@ · 9 unmodified lines
10 * DS207: Consider shorter variations of null checks10 * DS207: Consider shorter variations of null checks
11 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md11 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
12 */12 */
13(function() {13(function () {
14 let SIDEBAR_HIDDEN_LAYOUT = undefined;14 let SIDEBAR_HIDDEN_LAYOUT = undefined;
15 const Cls = (app.views.Settings = class Settings extends app.View {15 const Cls = (app.views.Settings = class Settings extends app.View {
16 constructor(...args) {16 constructor(...args) {
@@ -23,29 +23,28 @@ · 6 unmodified lines
23 }23 }
24 24
25 static initClass() {25 static initClass() {
26 SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden';26 SIDEBAR_HIDDEN_LAYOUT = "_sidebar-hidden";
27 27
28 this.el = '._settings';28 this.el = "._settings";
29 29
30 this.elements = {30 this.elements = {
31 sidebar: '._sidebar',31 sidebar: "._sidebar",
32 saveBtn: 'button[type="submit"]',32 saveBtn: 'button[type="submit"]',
33 backBtn: 'button[data-back]'33 backBtn: "button[data-back]",
34 };34 };
35 35
36 this.events = {36 this.events = {
37 import: 'onImport',37 import: "onImport",
38 change: 'onChange',38 change: "onChange",
39 submit: 'onSubmit',39 submit: "onSubmit",
40 click: 'onClick'40 click: "onClick",
41 };41 };
42 42
43 this.shortcuts =43 this.shortcuts = { enter: "onEnter" };
44 {enter: 'onEnter'};
45 }44 }
46 45
47 init() {46 init() {
48 this.addSubview(this.docPicker = new app.views.DocPicker);47 this.addSubview((this.docPicker = new app.views.DocPicker()));
49 }48 }
50 49
51 activate() {50 activate() {
@@ -59,18 +58,22 @@ · 7 unmodified lines
59 if (super.deactivate(...arguments)) {58 if (super.deactivate(...arguments)) {
60 this.resetClass();59 this.resetClass();
61 this.docPicker.detach();60 this.docPicker.detach();
62 if (app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)) { document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT); }61 if (app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)) {
62 document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT);
63 }
63 }64 }
64 }65 }
65 66
66 render() {67 render() {
67 this.docPicker.appendTo(this.sidebar);68 this.docPicker.appendTo(this.sidebar);
68 this.refreshElements();69 this.refreshElements();
69 this.addClass('_in');70 this.addClass("_in");
70 }71 }
71 72
72 save(options) {73 save(options) {
73 if (options == null) { options = {}; }74 if (options == null) {
75 options = {};
76 }
74 if (!this.saving) {77 if (!this.saving) {
75 let docs;78 let docs;
76 this.saving = true;79 this.saving = true;
@@ -82,16 +85,19 @@ · 5 unmodified lines
82 app.settings.setDocs(docs);85 app.settings.setDocs(docs);
83 }86 }
84 87
85 this.saveBtn.textContent = 'Saving\u2026';88 this.saveBtn.textContent = "Saving\u2026";
86 const disabledDocs = new app.collections.Docs((() => {89 const disabledDocs = new app.collections.Docs(
87 const result = [];90 (() => {
88 for (var doc of Array.from(app.docs.all())) { if (docs.indexOf(doc.slug) === -1) {91 const result = [];
89 result.push(doc);92 for (var doc of Array.from(app.docs.all())) {
93 if (docs.indexOf(doc.slug) === -1) {
94 result.push(doc);
95 }
90 }96 }
91 }97 return result;
92 return result;98 })(),
93 })());99 );
94 disabledDocs.uninstall(function() {100 disabledDocs.uninstall(function () {
95 app.db.migrate();101 app.db.migrate();
96 return app.reload();102 return app.reload();
97 });103 });
@@ -99,7 +105,7 @@ · 1 unmodified line
99 }105 }
100 106
101 onChange() {107 onChange() {
102 this.addClass('_dirty');108 this.addClass("_dirty");
103 }109 }
104 110
105 onEnter() {111 onEnter() {
@@ -112,15 +118,17 @@ · 6 unmodified lines
112 }118 }
113 119
114 onImport() {120 onImport() {
115 this.addClass('_dirty');121 this.addClass("_dirty");
116 this.save({import: true});122 this.save({ import: true });
117 }123 }
118 124
119 onClick(event) {125 onClick(event) {
120 if (event.which !== 1) { return; }126 if (event.which !== 1) {
127 return;
128 }
121 if (event.target === this.backBtn) {129 if (event.target === this.backBtn) {
122 $.stopEvent(event);130 $.stopEvent(event);
123 app.router.show('/');131 app.router.show("/");
124 }132 }
125 }133 }
126 });134 });
assets/javascripts/views/list/list_focus.js+71−44
@@ -10,18 +10,17 @@ · 9 unmodified lines
10 */10 */
11const Cls = (app.views.ListFocus = class ListFocus extends app.View {11const Cls = (app.views.ListFocus = class ListFocus extends app.View {
12 static initClass() {12 static initClass() {
13 this.activeClass = 'focus';13 this.activeClass = "focus";
14 14
15 this.events =15 this.events = { click: "onClick" };
16 {click: 'onClick'};16
17
18 this.shortcuts = {17 this.shortcuts = {
19 up: 'onUp',18 up: "onUp",
20 down: 'onDown',19 down: "onDown",
21 left: 'onLeft',20 left: "onLeft",
22 enter: 'onEnter',21 enter: "onEnter",
23 superEnter: 'onSuperEnter',22 superEnter: "onSuperEnter",
24 escape: 'blur'23 escape: "blur",
25 };24 };
26 }25 }
27 26
@@ -39,41 +38,51 @@ const Cls = (app.views.ListFocus = class ListFocus extends app.View { · 11 unmodified lines
39 }38 }
40 39
41 focus(el, options) {40 focus(el, options) {
42 if (options == null) { options = {}; }41 if (options == null) {
42 options = {};
43 }
43 if (el && !el.classList.contains(this.constructor.activeClass)) {44 if (el && !el.classList.contains(this.constructor.activeClass)) {
44 this.blur();45 this.blur();
45 el.classList.add(this.constructor.activeClass);46 el.classList.add(this.constructor.activeClass);
46 if (options.silent !== true) { $.trigger(el, 'focus'); }47 if (options.silent !== true) {
48 $.trigger(el, "focus");
49 }
47 }50 }
48 }51 }
49 52
50 blur() {53 blur() {
51 let cursor;54 let cursor;
52 if (cursor = this.getCursor()) {55 if ((cursor = this.getCursor())) {
53 cursor.classList.remove(this.constructor.activeClass);56 cursor.classList.remove(this.constructor.activeClass);
54 $.trigger(cursor, 'blur');57 $.trigger(cursor, "blur");
55 }58 }
56 }59 }
57 60
58 getCursor() {61 getCursor() {
59 return this.findByClass(this.constructor.activeClass) || this.findByClass(app.views.ListSelect.activeClass);62 return (
63 this.findByClass(this.constructor.activeClass) ||
64 this.findByClass(app.views.ListSelect.activeClass)
65 );
60 }66 }
61 67
62 findNext(cursor) {68 findNext(cursor) {
63 let next;69 let next;
64 if (next = cursor.nextSibling) {70 if ((next = cursor.nextSibling)) {
65 if (next.tagName === 'A') {71 if (next.tagName === "A") {
66 return next;72 return next;
67 } else if (next.tagName === 'SPAN') { // pagination link73 } else if (next.tagName === "SPAN") {
74 // pagination link
68 $.click(next);75 $.click(next);
69 return this.findNext(cursor);76 return this.findNext(cursor);
70 } else if (next.tagName === 'DIV') { // sub-list77 } else if (next.tagName === "DIV") {
71 if (cursor.className.indexOf(' open') >= 0) {78 // sub-list
79 if (cursor.className.indexOf(" open") >= 0) {
72 return this.findFirst(next) || this.findNext(next);80 return this.findFirst(next) || this.findNext(next);
73 } else {81 } else {
74 return this.findNext(next);82 return this.findNext(next);
75 }83 }
76 } else if (next.tagName === 'H6') { // title84 } else if (next.tagName === "H6") {
85 // title
77 return this.findNext(next);86 return this.findNext(next);
78 }87 }
79 } else if (cursor.parentNode !== this.el) {88 } else if (cursor.parentNode !== this.el) {
@@ -83,11 +92,14 @@ const Cls = (app.views.ListFocus = class ListFocus extends app.View { · 3 unmodified lines
83 92
84 findFirst(cursor) {93 findFirst(cursor) {
85 let first;94 let first;
86 if (!(first = cursor.firstChild)) { return; }95 if (!(first = cursor.firstChild)) {
96 return;
97 }
87 98
88 if (first.tagName === 'A') {99 if (first.tagName === "A") {
89 return first;100 return first;
90 } else if (first.tagName === 'SPAN') { // pagination link101 } else if (first.tagName === "SPAN") {
102 // pagination link
91 $.click(first);103 $.click(first);
92 return this.findFirst(cursor);104 return this.findFirst(cursor);
93 }105 }
@@ -95,19 +107,22 @@ const Cls = (app.views.ListFocus = class ListFocus extends app.View { · 1 unmodified line
95 107
96 findPrev(cursor) {108 findPrev(cursor) {
97 let prev;109 let prev;
98 if (prev = cursor.previousSibling) {110 if ((prev = cursor.previousSibling)) {
99 if (prev.tagName === 'A') {111 if (prev.tagName === "A") {
100 return prev;112 return prev;
101 } else if (prev.tagName === 'SPAN') { // pagination link113 } else if (prev.tagName === "SPAN") {
114 // pagination link
102 $.click(prev);115 $.click(prev);
103 return this.findPrev(cursor);116 return this.findPrev(cursor);
104 } else if (prev.tagName === 'DIV') { // sub-list117 } else if (prev.tagName === "DIV") {
105 if (prev.previousSibling.className.indexOf('open') >= 0) {118 // sub-list
119 if (prev.previousSibling.className.indexOf("open") >= 0) {
106 return this.findLast(prev) || this.findPrev(prev);120 return this.findLast(prev) || this.findPrev(prev);
107 } else {121 } else {
108 return this.findPrev(prev);122 return this.findPrev(prev);
109 }123 }
110 } else if (prev.tagName === 'H6') { // title124 } else if (prev.tagName === "H6") {
125 // title
111 return this.findPrev(prev);126 return this.findPrev(prev);
112 }127 }
113 } else if (cursor.parentNode !== this.el) {128 } else if (cursor.parentNode !== this.el) {
@@ -117,13 +132,17 @@ const Cls = (app.views.ListFocus = class ListFocus extends app.View { · 3 unmodified lines
117 132
118 findLast(cursor) {133 findLast(cursor) {
119 let last;134 let last;
120 if (!(last = cursor.lastChild)) { return; }135 if (!(last = cursor.lastChild)) {
136 return;
137 }
121 138
122 if (last.tagName === 'A') {139 if (last.tagName === "A") {
123 return last;140 return last;
124 } else if ((last.tagName === 'SPAN') || (last.tagName === 'H6')) { // pagination link or title141 } else if (last.tagName === "SPAN" || last.tagName === "H6") {
142 // pagination link or title
125 return this.findPrev(last);143 return this.findPrev(last);
126 } else if (last.tagName === 'DIV') { // sub-list144 } else if (last.tagName === "DIV") {
145 // sub-list
127 return this.findLast(last);146 return this.findLast(last);
128 }147 }
129 }148 }
@@ -133,7 +152,7 @@ const Cls = (app.views.ListFocus = class ListFocus extends app.View { · 3 unmodified lines
133 if ((cursor = this.getCursor())) {152 if ((cursor = this.getCursor())) {
134 this.focusOnNextFrame(this.findNext(cursor));153 this.focusOnNextFrame(this.findNext(cursor));
135 } else {154 } else {
136 this.focusOnNextFrame(this.findByTag('a'));155 this.focusOnNextFrame(this.findByTag("a"));
137 }156 }
138 }157 }
139 158
@@ -142,37 +161,45 @@ const Cls = (app.views.ListFocus = class ListFocus extends app.View { · 2 unmodified lines
142 if ((cursor = this.getCursor())) {161 if ((cursor = this.getCursor())) {
143 this.focusOnNextFrame(this.findPrev(cursor));162 this.focusOnNextFrame(this.findPrev(cursor));
144 } else {163 } else {
145 this.focusOnNextFrame(this.findLastByTag('a'));164 this.focusOnNextFrame(this.findLastByTag("a"));
146 }165 }
147 }166 }
148 167
149 onLeft() {168 onLeft() {
150 const cursor = this.getCursor();169 const cursor = this.getCursor();
151 if (cursor && !cursor.classList.contains(app.views.ListFold.activeClass) && (cursor.parentNode !== this.el)) {170 if (
171 cursor &&
172 !cursor.classList.contains(app.views.ListFold.activeClass) &&
173 cursor.parentNode !== this.el
174 ) {
152 const prev = cursor.parentNode.previousSibling;175 const prev = cursor.parentNode.previousSibling;
153 if (prev && prev.classList.contains(app.views.ListFold.targetClass)) { this.focusOnNextFrame(cursor.parentNode.previousSibling); }176 if (prev && prev.classList.contains(app.views.ListFold.targetClass)) {
177 this.focusOnNextFrame(cursor.parentNode.previousSibling);
178 }
154 }179 }
155 }180 }
156 181
157 onEnter() {182 onEnter() {
158 let cursor;183 let cursor;
159 if (cursor = this.getCursor()) {184 if ((cursor = this.getCursor())) {
160 $.click(cursor);185 $.click(cursor);
161 }186 }
162 }187 }
163 188
164 onSuperEnter() {189 onSuperEnter() {
165 let cursor;190 let cursor;
166 if (cursor = this.getCursor()) {191 if ((cursor = this.getCursor())) {
167 $.popup(cursor);192 $.popup(cursor);
168 }193 }
169 }194 }
170 195
171 onClick(event) {196 onClick(event) {
172 if ((event.which !== 1) || event.metaKey || event.ctrlKey) { return; }197 if (event.which !== 1 || event.metaKey || event.ctrlKey) {
198 return;
199 }
173 const target = $.eventTarget(event);200 const target = $.eventTarget(event);
174 if (target.tagName === 'A') {201 if (target.tagName === "A") {
175 this.focus(target, {silent: true});202 this.focus(target, { silent: true });
176 }203 }
177 }204 }
178});205});
assets/javascripts/views/list/list_fold.js+44−20
@@ -10,32 +10,37 @@ · 9 unmodified lines
10 */10 */
11const Cls = (app.views.ListFold = class ListFold extends app.View {11const Cls = (app.views.ListFold = class ListFold extends app.View {
12 static initClass() {12 static initClass() {
13 this.targetClass = '_list-dir';13 this.targetClass = "_list-dir";
14 this.handleClass = '_list-arrow';14 this.handleClass = "_list-arrow";
15 this.activeClass = 'open';15 this.activeClass = "open";
16 16
17 this.events =17 this.events = { click: "onClick" };
18 {click: 'onClick'};18
19
20 this.shortcuts = {19 this.shortcuts = {
21 left: 'onLeft',20 left: "onLeft",
22 right: 'onRight'21 right: "onRight",
23 };22 };
24 }23 }
25 24
26 constructor(el) { this.onLeft = this.onLeft.bind(this); this.onRight = this.onRight.bind(this); this.onClick = this.onClick.bind(this); this.el = el; super(...arguments); }25 constructor(el) {
26 this.onLeft = this.onLeft.bind(this);
27 this.onRight = this.onRight.bind(this);
28 this.onClick = this.onClick.bind(this);
29 this.el = el;
30 super(...arguments);
31 }
27 32
28 open(el) {33 open(el) {
29 if (el && !el.classList.contains(this.constructor.activeClass)) {34 if (el && !el.classList.contains(this.constructor.activeClass)) {
30 el.classList.add(this.constructor.activeClass);35 el.classList.add(this.constructor.activeClass);
31 $.trigger(el, 'open');36 $.trigger(el, "open");
32 }37 }
33 }38 }
34 39
35 close(el) {40 close(el) {
36 if (el && el.classList.contains(this.constructor.activeClass)) {41 if (el && el.classList.contains(this.constructor.activeClass)) {
37 el.classList.remove(this.constructor.activeClass);42 el.classList.remove(this.constructor.activeClass);
38 $.trigger(el, 'close');43 $.trigger(el, "close");
39 }44 }
40 }45 }
41 46
@@ -55,36 +60,55 @@ const Cls = (app.views.ListFold = class ListFold extends app.View { · 13 unmodified lines
55 }60 }
56 61
57 getCursor() {62 getCursor() {
58 return this.findByClass(app.views.ListFocus.activeClass) || this.findByClass(app.views.ListSelect.activeClass);63 return (
64 this.findByClass(app.views.ListFocus.activeClass) ||
65 this.findByClass(app.views.ListSelect.activeClass)
66 );
59 }67 }
60 68
61 onLeft() {69 onLeft() {
62 const cursor = this.getCursor();70 const cursor = this.getCursor();
63 if (cursor != null ? cursor.classList.contains(this.constructor.activeClass) : undefined) {71 if (
72 cursor != null
73 ? cursor.classList.contains(this.constructor.activeClass)
74 : undefined
75 ) {
64 this.close(cursor);76 this.close(cursor);
65 }77 }
66 }78 }
67 79
68 onRight() {80 onRight() {
69 const cursor = this.getCursor();81 const cursor = this.getCursor();
70 if (cursor != null ? cursor.classList.contains(this.constructor.targetClass) : undefined) {82 if (
83 cursor != null
84 ? cursor.classList.contains(this.constructor.targetClass)
85 : undefined
86 ) {
71 this.open(cursor);87 this.open(cursor);
72 }88 }
73 }89 }
74 90
75 onClick(event) {91 onClick(event) {
76 if ((event.which !== 1) || event.metaKey || event.ctrlKey) { return; }92 if (event.which !== 1 || event.metaKey || event.ctrlKey) {
77 if (!event.pageY) { return; } // ignore fabricated clicks93 return;
94 }
95 if (!event.pageY) {
96 return;
97 } // ignore fabricated clicks
78 let el = $.eventTarget(event);98 let el = $.eventTarget(event);
79 if (el.parentNode.tagName.toUpperCase() === 'SVG') { el = el.parentNode; }99 if (el.parentNode.tagName.toUpperCase() === "SVG") {
100 el = el.parentNode;
101 }
80 102
81 if (el.classList.contains(this.constructor.handleClass)) {103 if (el.classList.contains(this.constructor.handleClass)) {
82 $.stopEvent(event);104 $.stopEvent(event);
83 this.toggle(el.parentNode);105 this.toggle(el.parentNode);
84 } else if (el.classList.contains(this.constructor.targetClass)) {106 } else if (el.classList.contains(this.constructor.targetClass)) {
85 if (el.hasAttribute('href')) {107 if (el.hasAttribute("href")) {
86 if (el.classList.contains(this.constructor.activeClass)) {108 if (el.classList.contains(this.constructor.activeClass)) {
87 if (el.classList.contains(app.views.ListSelect.activeClass)) { this.close(el); }109 if (el.classList.contains(app.views.ListSelect.activeClass)) {
110 this.close(el);
111 }
88 } else {112 } else {
89 this.open(el);113 this.open(el);
90 }114 }
assets/javascripts/views/list/list_select.js+25−14
@@ -10,36 +10,43 @@ · 9 unmodified lines
10 */10 */
11const Cls = (app.views.ListSelect = class ListSelect extends app.View {11const Cls = (app.views.ListSelect = class ListSelect extends app.View {
12 static initClass() {12 static initClass() {
13 this.activeClass = 'active';13 this.activeClass = "active";
14 14
15 this.events =15 this.events = { click: "onClick" };
16 {click: 'onClick'};
17 }16 }
18 17
19 constructor(el) { this.onClick = this.onClick.bind(this); this.el = el; super(...arguments); }18 constructor(el) {
19 this.onClick = this.onClick.bind(this);
20 this.el = el;
21 super(...arguments);
22 }
20 23
21 deactivate() {24 deactivate() {
22 if (super.deactivate(...arguments)) { this.deselect(); }25 if (super.deactivate(...arguments)) {
26 this.deselect();
27 }
23 }28 }
24 29
25 select(el) {30 select(el) {
26 this.deselect();31 this.deselect();
27 if (el) {32 if (el) {
28 el.classList.add(this.constructor.activeClass);33 el.classList.add(this.constructor.activeClass);
29 $.trigger(el, 'select');34 $.trigger(el, "select");
30 }35 }
31 }36 }
32 37
33 deselect() {38 deselect() {
34 let selection;39 let selection;
35 if (selection = this.getSelection()) {40 if ((selection = this.getSelection())) {
36 selection.classList.remove(this.constructor.activeClass);41 selection.classList.remove(this.constructor.activeClass);
37 $.trigger(selection, 'deselect');42 $.trigger(selection, "deselect");
38 }43 }
39 }44 }
40 45
41 selectByHref(href) {46 selectByHref(href) {
42 if (__guard__(this.getSelection(), x => x.getAttribute('href')) !== href) {47 if (
48 __guard__(this.getSelection(), (x) => x.getAttribute("href")) !== href
49 ) {
43 this.select(this.find(`a[href='${href}']`));50 this.select(this.find(`a[href='${href}']`));
44 }51 }
45 }52 }
@@ -53,9 +60,11 @@ const Cls = (app.views.ListSelect = class ListSelect extends app.View { · 7 unmodified lines
53 }60 }
54 61
55 onClick(event) {62 onClick(event) {
56 if ((event.which !== 1) || event.metaKey || event.ctrlKey) { return; }63 if (event.which !== 1 || event.metaKey || event.ctrlKey) {
64 return;
65 }
57 const target = $.eventTarget(event);66 const target = $.eventTarget(event);
58 if (target.tagName === 'A') {67 if (target.tagName === "A") {
59 this.select(target);68 this.select(target);
60 }69 }
61 }70 }
@@ -63,5 +72,7 @@ const Cls = (app.views.ListSelect = class ListSelect extends app.View { · 1 unmodified line
63Cls.initClass();72Cls.initClass();
64 73
65function __guard__(value, transform) {74function __guard__(value, transform) {
66 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;75 return typeof value !== "undefined" && value !== null
67}76 ? transform(value)
77 : undefined;
78}
assets/javascripts/views/list/paginated_list.js+57−22
@@ -10,7 +10,7 @@ · 9 unmodified lines
10 * DS207: Consider shorter variations of null checks10 * DS207: Consider shorter variations of null checks
11 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md11 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
12 */12 */
13(function() {13(function () {
14 let PER_PAGE = undefined;14 let PER_PAGE = undefined;
15 const Cls = (app.views.PaginatedList = class PaginatedList extends app.View {15 const Cls = (app.views.PaginatedList = class PaginatedList extends app.View {
16 static initClass() {16 static initClass() {
@@ -21,7 +21,12 @@ · 4 unmodified lines
21 let base;21 let base;
22 this.onClick = this.onClick.bind(this);22 this.onClick = this.onClick.bind(this);
23 this.data = data;23 this.data = data;
24 if (((base = this.constructor.events || (this.constructor.events = {}))).click == null) { base.click = 'onClick'; }24 if (
25 (base = this.constructor.events || (this.constructor.events = {}))
26 .click == null
27 ) {
28 base.click = "onClick";
29 }
25 super(...arguments);30 super(...arguments);
26 }31 }
27 32
@@ -42,11 +47,13 @@ · 14 unmodified lines
42 }47 }
43 48
44 renderPage(page) {49 renderPage(page) {
45 return this.render(this.data.slice(((page - 1) * PER_PAGE), (page * PER_PAGE)));50 return this.render(
51 this.data.slice((page - 1) * PER_PAGE, page * PER_PAGE),
52 );
46 }53 }
47 54
48 renderPageLink(count) {55 renderPageLink(count) {
49 return this.tmpl('sidebarPageLink', count);56 return this.tmpl("sidebarPageLink", count);
50 }57 }
51 58
52 renderPrevLink(page) {59 renderPrevLink(page) {
@@ -54,7 +61,7 @@ · 1 unmodified line
54 }61 }
55 62
56 renderNextLink(page) {63 renderNextLink(page) {
57 return this.renderPageLink(this.data.length - (page * PER_PAGE));64 return this.renderPageLink(this.data.length - page * PER_PAGE);
58 }65 }
59 66
60 totalPages() {67 totalPages() {
@@ -64,17 +71,27 @@ · 3 unmodified lines
64 paginate(link) {71 paginate(link) {
65 $.lockScroll(link.nextSibling || link.previousSibling, () => {72 $.lockScroll(link.nextSibling || link.previousSibling, () => {
66 $.batchUpdate(this.el, () => {73 $.batchUpdate(this.el, () => {
67 if (link.nextSibling) { this.paginatePrev(link); } else { this.paginateNext(link); }74 if (link.nextSibling) {
75 this.paginatePrev(link);
76 } else {
77 this.paginateNext(link);
78 }
68 });79 });
69 });80 });
70 }81 }
71 82
72 paginateNext() {83 paginateNext() {
73 if (this.el.lastChild) { this.remove(this.el.lastChild); } // remove link84 if (this.el.lastChild) {
74 if (this.page >= 2) { this.hideTopPage(); } // keep previous page into view85 this.remove(this.el.lastChild);
86 } // remove link
87 if (this.page >= 2) {
88 this.hideTopPage();
89 } // keep previous page into view
75 this.page++;90 this.page++;
76 this.append(this.renderPage(this.page));91 this.append(this.renderPage(this.page));
77 if (this.page < this.totalPages()) { this.append(this.renderNextLink(this.page)); }92 if (this.page < this.totalPages()) {
93 this.append(this.renderNextLink(this.page));
94 }
78 }95 }
79 96
80 paginatePrev() {97 paginatePrev() {
@@ -82,37 +99,55 @@ · 1 unmodified line
82 this.hideBottomPage();99 this.hideBottomPage();
83 this.page--;100 this.page--;
84 this.prepend(this.renderPage(this.page - 1)); // previous page is offset by one101 this.prepend(this.renderPage(this.page - 1)); // previous page is offset by one
85 if (this.page >= 3) { this.prepend(this.renderPrevLink(this.page - 1)); }102 if (this.page >= 3) {
103 this.prepend(this.renderPrevLink(this.page - 1));
104 }
86 }105 }
87 106
88 paginateTo(object) {107 paginateTo(object) {
89 const index = this.data.indexOf(object);108 const index = this.data.indexOf(object);
90 if (index >= PER_PAGE) {109 if (index >= PER_PAGE) {
91 for (let i = 0, end = Math.floor(index / PER_PAGE), asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) { this.paginateNext(); }110 for (
111 let i = 0, end = Math.floor(index / PER_PAGE), asc = 0 <= end;
112 asc ? i < end : i > end;
113 asc ? i++ : i--
114 ) {
115 this.paginateNext();
116 }
92 }117 }
93 }118 }
94 119
95 hideTopPage() {120 hideTopPage() {
96 const n = this.page <= 2 ?121 const n = this.page <= 2 ? PER_PAGE : PER_PAGE + 1; // remove link
97 PER_PAGE122 for (
98 :123 let i = 0, end = n, asc = 0 <= end;
99 PER_PAGE + 1; // remove link124 asc ? i < end : i > end;
100 for (let i = 0, end = n, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) { this.remove(this.el.firstChild); }125 asc ? i++ : i--
126 ) {
127 this.remove(this.el.firstChild);
128 }
101 this.prepend(this.renderPrevLink(this.page));129 this.prepend(this.renderPrevLink(this.page));
102 }130 }
103 131
104 hideBottomPage() {132 hideBottomPage() {
105 const n = this.page === this.totalPages() ?133 const n =
106 (this.data.length % PER_PAGE) || PER_PAGE134 this.page === this.totalPages()
107 :135 ? this.data.length % PER_PAGE || PER_PAGE
108 PER_PAGE + 1; // remove link136 : PER_PAGE + 1; // remove link
109 for (let i = 0, end = n, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) { this.remove(this.el.lastChild); }137 for (
138 let i = 0, end = n, asc = 0 <= end;
139 asc ? i < end : i > end;
140 asc ? i++ : i--
141 ) {
142 this.remove(this.el.lastChild);
143 }
110 this.append(this.renderNextLink(this.page - 1));144 this.append(this.renderNextLink(this.page - 1));
111 }145 }
112 146
113 onClick(event) {147 onClick(event) {
114 const target = $.eventTarget(event);148 const target = $.eventTarget(event);
115 if (target.tagName === 'SPAN') { // link149 if (target.tagName === "SPAN") {
150 // link
116 $.stopEvent(event);151 $.stopEvent(event);
117 this.paginate(target);152 this.paginate(target);
118 }153 }
assets/javascripts/views/misc/news.js+14−9
@@ -12,15 +12,16 @@ · 11 unmodified lines
12 12
13const Cls = (app.views.News = class News extends app.views.Notif {13const Cls = (app.views.News = class News extends app.views.Notif {
14 static initClass() {14 static initClass() {
15 this.className += ' _notif-news';15 this.className += " _notif-news";
16 16
17 this.defautOptions =17 this.defautOptions = { autoHide: 30000 };
18 {autoHide: 30000};
19 }18 }
20 19
21 init() {20 init() {
22 this.unreadNews = this.getUnreadNews();21 this.unreadNews = this.getUnreadNews();
23 if (this.unreadNews.length) { this.show(); }22 if (this.unreadNews.length) {
23 this.show();
24 }
24 this.markAllAsRead();25 this.markAllAsRead();
25 }26 }
26 27
@@ -30,12 +31,16 @@ const Cls = (app.views.News = class News extends app.views.Notif { · 3 unmodified lines
30 31
31 getUnreadNews() {32 getUnreadNews() {
32 let time;33 let time;
33 if (!(time = this.getLastReadTime())) { return []; }34 if (!(time = this.getLastReadTime())) {
35 return [];
36 }
34 37
35 return (() => {38 return (() => {
36 const result = [];39 const result = [];
37 for (var news of Array.from(app.news)) {40 for (var news of Array.from(app.news)) {
38 if (new Date(news[0]).getTime() <= time) { break; }41 if (new Date(news[0]).getTime() <= time) {
42 break;
43 }
39 result.push(news);44 result.push(news);
40 }45 }
41 return result;46 return result;
@@ -47,11 +52,11 @@ const Cls = (app.views.News = class News extends app.views.Notif { · 5 unmodified lines
47 }52 }
48 53
49 getLastReadTime() {54 getLastReadTime() {
50 return app.settings.get('news');55 return app.settings.get("news");
51 }56 }
52 57
53 markAllAsRead() {58 markAllAsRead() {
54 app.settings.set('news', this.getLastNewsTime());59 app.settings.set("news", this.getLastNewsTime());
55 }60 }
56});61});
57Cls.initClass();62Cls.initClass();
assets/javascripts/views/misc/notice.js+13−6
@@ -9,23 +9,30 @@ · 8 unmodified lines
9 */9 */
10const Cls = (app.views.Notice = class Notice extends app.View {10const Cls = (app.views.Notice = class Notice extends app.View {
11 static initClass() {11 static initClass() {
12 this.className = '_notice';12 this.className = "_notice";
13 this.attributes =13 this.attributes = { role: "alert" };
14 {role: 'alert'};
15 }14 }
16 15
17 constructor(type, ...rest) { this.type = type; [...this.args] = Array.from(rest); super(...arguments); }16 constructor(type, ...rest) {
17 this.type = type;
18 [...this.args] = Array.from(rest);
19 super(...arguments);
20 }
18 21
19 init() {22 init() {
20 this.activate();23 this.activate();
21 }24 }
22 25
23 activate() {26 activate() {
24 if (super.activate(...arguments)) { this.show(); }27 if (super.activate(...arguments)) {
28 this.show();
29 }
25 }30 }
26 31
27 deactivate() {32 deactivate() {
28 if (super.deactivate(...arguments)) { this.hide(); }33 if (super.deactivate(...arguments)) {
34 this.hide();
35 }
29 }36 }
30 37
31 show() {38 show() {
assets/javascripts/views/misc/notif.js+22−16
@@ -9,22 +9,21 @@ · 8 unmodified lines
9 */9 */
10const Cls = (app.views.Notif = class Notif extends app.View {10const Cls = (app.views.Notif = class Notif extends app.View {
11 static initClass() {11 static initClass() {
12 this.className = '_notif';12 this.className = "_notif";
13 this.activeClass = '_in';13 this.activeClass = "_in";
14 this.attributes =14 this.attributes = { role: "alert" };
15 {role: 'alert'};15
16 16 this.defautOptions = { autoHide: 15000 };
17 this.defautOptions =17
18 {autoHide: 15000};18 this.events = { click: "onClick" };
19
20 this.events =
21 {click: 'onClick'};
22 }19 }
23 20
24 constructor(type, options) {21 constructor(type, options) {
25 this.onClick = this.onClick.bind(this);22 this.onClick = this.onClick.bind(this);
26 this.type = type;23 this.type = type;
27 if (options == null) { options = {}; }24 if (options == null) {
25 options = {};
26 }
28 this.options = options;27 this.options = options;
29 this.options = $.extend({}, this.constructor.defautOptions, this.options);28 this.options = $.extend({}, this.constructor.defautOptions, this.options);
30 super(...arguments);29 super(...arguments);
@@ -45,7 +44,9 @@ const Cls = (app.views.Notif = class Notif extends app.View { · 14 unmodified lines
45 this.appendTo(document.body);44 this.appendTo(document.body);
46 this.el.offsetWidth; // force reflow45 this.el.offsetWidth; // force reflow
47 this.addClass(this.constructor.activeClass);46 this.addClass(this.constructor.activeClass);
48 if (this.options.autoHide) { this.timeout = this.delay(this.hide, this.options.autoHide); }47 if (this.options.autoHide) {
48 this.timeout = this.delay(this.hide, this.options.autoHide);
49 }
49 }50 }
50 }51 }
51 52
@@ -63,15 +64,20 @@ const Cls = (app.views.Notif = class Notif extends app.View { · 11 unmodified lines
63 const notifications = $$(`.${app.views.Notif.className}`);64 const notifications = $$(`.${app.views.Notif.className}`);
64 if (notifications.length) {65 if (notifications.length) {
65 const lastNotif = notifications[notifications.length - 1];66 const lastNotif = notifications[notifications.length - 1];
66 this.el.style.top = lastNotif.offsetTop + lastNotif.offsetHeight + 16 + 'px';67 this.el.style.top =
68 lastNotif.offsetTop + lastNotif.offsetHeight + 16 + "px";
67 }69 }
68 }70 }
69 71
70 onClick(event) {72 onClick(event) {
71 if (event.which !== 1) { return; }73 if (event.which !== 1) {
74 return;
75 }
72 const target = $.eventTarget(event);76 const target = $.eventTarget(event);
73 if (target.hasAttribute('data-behavior')) { return; }77 if (target.hasAttribute("data-behavior")) {
74 if ((target.tagName !== 'A') || target.classList.contains('_notif-close')) {78 return;
79 }
80 if (target.tagName !== "A" || target.classList.contains("_notif-close")) {
75 $.stopEvent(event);81 $.stopEvent(event);
76 this.hide();82 this.hide();
77 }83 }
assets/javascripts/views/misc/tip.js+3−4
@@ -9,10 +9,9 @@ · 8 unmodified lines
9 9
10const Cls = (app.views.Tip = class Tip extends app.views.Notif {10const Cls = (app.views.Tip = class Tip extends app.views.Notif {
11 static initClass() {11 static initClass() {
12 this.className = '_notif _notif-tip';12 this.className = "_notif _notif-tip";
13 13
14 this.defautOptions =14 this.defautOptions = { autoHide: false };
15 {autoHide: false};
16 }15 }
17 16
18 render() {17 render() {
assets/javascripts/views/misc/updates.js+30−12
@@ -12,34 +12,47 @@ · 11 unmodified lines
12 12
13const Cls = (app.views.Updates = class Updates extends app.views.Notif {13const Cls = (app.views.Updates = class Updates extends app.views.Notif {
14 static initClass() {14 static initClass() {
15 this.className += ' _notif-news';15 this.className += " _notif-news";
16 16
17 this.defautOptions =17 this.defautOptions = { autoHide: 30000 };
18 {autoHide: 30000};
19 }18 }
20 19
21 init() {20 init() {
22 this.lastUpdateTime = this.getLastUpdateTime();21 this.lastUpdateTime = this.getLastUpdateTime();
23 this.updatedDocs = this.getUpdatedDocs();22 this.updatedDocs = this.getUpdatedDocs();
24 this.updatedDisabledDocs = this.getUpdatedDisabledDocs();23 this.updatedDisabledDocs = this.getUpdatedDisabledDocs();
25 if ((this.updatedDocs.length > 0) || (this.updatedDisabledDocs.length > 0)) { this.show(); }24 if (this.updatedDocs.length > 0 || this.updatedDisabledDocs.length > 0) {
25 this.show();
26 }
26 this.markAllAsRead();27 this.markAllAsRead();
27 }28 }
28 29
29 render() {30 render() {
30 this.html(app.templates.notifUpdates(this.updatedDocs, this.updatedDisabledDocs));31 this.html(
32 app.templates.notifUpdates(this.updatedDocs, this.updatedDisabledDocs),
33 );
31 }34 }
32 35
33 getUpdatedDocs() {36 getUpdatedDocs() {
34 if (!this.lastUpdateTime) { return []; }37 if (!this.lastUpdateTime) {
35 return Array.from(app.docs.all()).filter((doc) => doc.mtime > this.lastUpdateTime);38 return [];
39 }
40 return Array.from(app.docs.all()).filter(
41 (doc) => doc.mtime > this.lastUpdateTime,
42 );
36 }43 }
37 44
38 getUpdatedDisabledDocs() {45 getUpdatedDisabledDocs() {
39 if (!this.lastUpdateTime) { return []; }46 if (!this.lastUpdateTime) {
47 return [];
48 }
40 return (() => {49 return (() => {
41 const result = [];50 const result = [];
42 for (var doc of Array.from(app.disabledDocs.all())) { if ((doc.mtime > this.lastUpdateTime) && app.docs.findBy('slug_without_version', doc.slug_without_version)) {51 for (var doc of Array.from(app.disabledDocs.all())) {
52 if (
53 doc.mtime > this.lastUpdateTime &&
54 app.docs.findBy("slug_without_version", doc.slug_without_version)
55 ) {
43 result.push(doc);56 result.push(doc);
44 }57 }
45 }58 }
@@ -48,11 +61,16 @@ const Cls = (app.views.Updates = class Updates extends app.views.Notif { · 2 unmodified lines
48 }61 }
49 62
50 getLastUpdateTime() {63 getLastUpdateTime() {
51 return app.settings.get('version');64 return app.settings.get("version");
52 }65 }
53 66
54 markAllAsRead() {67 markAllAsRead() {
55 app.settings.set('version', app.config.env === 'production' ? app.config.version : Math.floor(Date.now() / 1000));68 app.settings.set(
69 "version",
70 app.config.env === "production"
71 ? app.config.version
72 : Math.floor(Date.now() / 1000),
73 );
56 }74 }
57});75});
58Cls.initClass();76Cls.initClass();
assets/javascripts/views/pages/base.js+39−15
@@ -9,31 +9,44 @@ · 8 unmodified lines
9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md9 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
10 */10 */
11app.views.BasePage = class BasePage extends app.View {11app.views.BasePage = class BasePage extends app.View {
12 constructor(el, entry) { this.paintCode = this.paintCode.bind(this); this.el = el; this.entry = entry; super(...arguments); }12 constructor(el, entry) {
13 this.paintCode = this.paintCode.bind(this);
14 this.el = el;
15 this.entry = entry;
16 super(...arguments);
17 }
13 18
14 deactivate() {19 deactivate() {
15 if (super.deactivate(...arguments)) {20 if (super.deactivate(...arguments)) {
16 return this.highlightNodes = [];21 return (this.highlightNodes = []);
17 }22 }
18 }23 }
19 24
20 render(content, fromCache) {25 render(content, fromCache) {
21 if (fromCache == null) { fromCache = false; }26 if (fromCache == null) {
27 fromCache = false;
28 }
22 this.highlightNodes = [];29 this.highlightNodes = [];
23 this.previousTiming = null;30 this.previousTiming = null;
24 if (!this.constructor.className) { this.addClass(`_${this.entry.doc.type}`); }31 if (!this.constructor.className) {
32 this.addClass(`_${this.entry.doc.type}`);
33 }
25 this.html(content);34 this.html(content);
26 if (!fromCache) { this.highlightCode(); }35 if (!fromCache) {
36 this.highlightCode();
37 }
27 this.activate();38 this.activate();
28 if (this.afterRender) { this.delay(this.afterRender); }39 if (this.afterRender) {
40 this.delay(this.afterRender);
41 }
29 if (this.highlightNodes.length > 0) {42 if (this.highlightNodes.length > 0) {
30 $.requestAnimationFrame(() => $.requestAnimationFrame(this.paintCode));43 $.requestAnimationFrame(() => $.requestAnimationFrame(this.paintCode));
31 }44 }
32 }45 }
33 46
34 highlightCode() {47 highlightCode() {
35 for (var el of Array.from(this.findAll('pre[data-language]'))) {48 for (var el of Array.from(this.findAll("pre[data-language]"))) {
36 var language = el.getAttribute('data-language');49 var language = el.getAttribute("data-language");
37 el.classList.add(`language-${language}`);50 el.classList.add(`language-${language}`);
38 this.highlightNodes.push(el);51 this.highlightNodes.push(el);
39 }52 }
@@ -41,23 +54,34 @@ app.views.BasePage = class BasePage extends app.View { · 1 unmodified line
41 54
42 paintCode(timing) {55 paintCode(timing) {
43 if (this.previousTiming) {56 if (this.previousTiming) {
44 if (Math.round(1000 / (timing - this.previousTiming)) > 50) { // fps57 if (Math.round(1000 / (timing - this.previousTiming)) > 50) {
45 this.nodesPerFrame = Math.round(Math.min(this.nodesPerFrame * 1.25, 50));58 // fps
59 this.nodesPerFrame = Math.round(
60 Math.min(this.nodesPerFrame * 1.25, 50),
61 );
46 } else {62 } else {
47 this.nodesPerFrame = Math.round(Math.max(this.nodesPerFrame * .8, 10));63 this.nodesPerFrame = Math.round(Math.max(this.nodesPerFrame * 0.8, 10));
48 }64 }
49 } else {65 } else {
50 this.nodesPerFrame = 10;66 this.nodesPerFrame = 10;
51 }67 }
52 68
53 for (var el of Array.from(this.highlightNodes.splice(0, this.nodesPerFrame))) {69 for (var el of Array.from(
70 this.highlightNodes.splice(0, this.nodesPerFrame),
71 )) {
54 var clipEl;72 var clipEl;
55 if (clipEl = el.lastElementChild) { $.remove(clipEl); }73 if ((clipEl = el.lastElementChild)) {
74 $.remove(clipEl);
75 }
56 Prism.highlightElement(el);76 Prism.highlightElement(el);
57 if (clipEl) { $.append(el, clipEl); }77 if (clipEl) {
78 $.append(el, clipEl);
79 }
58 }80 }
59 81
60 if (this.highlightNodes.length > 0) { $.requestAnimationFrame(this.paintCode); }82 if (this.highlightNodes.length > 0) {
83 $.requestAnimationFrame(this.paintCode);
84 }
61 this.previousTiming = timing;85 this.previousTiming = timing;
62 }86 }
63};87};
assets/javascripts/views/pages/hidden.js+9−5
@@ -8,20 +8,24 @@ · 7 unmodified lines
8 */8 */
9const Cls = (app.views.HiddenPage = class HiddenPage extends app.View {9const Cls = (app.views.HiddenPage = class HiddenPage extends app.View {
10 static initClass() {10 static initClass() {
11 this.events =11 this.events = { click: "onClick" };
12 {click: 'onClick'};
13 }12 }
14 13
15 constructor(el, entry) { this.onClick = this.onClick.bind(this); this.el = el; this.entry = entry; super(...arguments); }14 constructor(el, entry) {
15 this.onClick = this.onClick.bind(this);
16 this.el = el;
17 this.entry = entry;
18 super(...arguments);
19 }
16 20
17 init() {21 init() {
18 this.addSubview(this.notice = new app.views.Notice('disabledDoc'));22 this.addSubview((this.notice = new app.views.Notice("disabledDoc")));
19 this.activate();23 this.activate();
20 }24 }
21 25
22 onClick(event) {26 onClick(event) {
23 let link;27 let link;
24 if (link = $.closestLink(event.target, this.el)) {28 if ((link = $.closestLink(event.target, this.el))) {
25 $.stopEvent(event);29 $.stopEvent(event);
26 $.popup(link);30 $.popup(link);
27 }31 }
assets/javascripts/views/pages/jquery.js+30−17
@@ -10,46 +10,54 @@ · 9 unmodified lines
10 */10 */
11//= require views/pages/base11//= require views/pages/base
12 12
13const Cls = (app.views.JqueryPage = class JqueryPage extends app.views.BasePage {13const Cls = (app.views.JqueryPage = class JqueryPage extends (
14 app.views.BasePage
15) {
14 constructor(...args) {16 constructor(...args) {
15 this.onIframeLoaded = this.onIframeLoaded.bind(this);17 this.onIframeLoaded = this.onIframeLoaded.bind(this);
16 super(...args);18 super(...args);
17 }19 }
18 20
19 static initClass() {21 static initClass() {
20 this.demoClassName = '_jquery-demo';22 this.demoClassName = "_jquery-demo";
21 }23 }
22 24
23 afterRender() {25 afterRender() {
24 // Prevent jQuery Mobile's demo iframes from scrolling the page26 // Prevent jQuery Mobile's demo iframes from scrolling the page
25 for (var iframe of Array.from(this.findAllByTag('iframe'))) {27 for (var iframe of Array.from(this.findAllByTag("iframe"))) {
26 iframe.style.display = 'none';28 iframe.style.display = "none";
27 $.on(iframe, 'load', this.onIframeLoaded);29 $.on(iframe, "load", this.onIframeLoaded);
28 }30 }
29 31
30 return this.runExamples();32 return this.runExamples();
31 }33 }
32 34
33 onIframeLoaded(event) {35 onIframeLoaded(event) {
34 event.target.style.display = '';36 event.target.style.display = "";
35 $.off(event.target, 'load', this.onIframeLoaded);37 $.off(event.target, "load", this.onIframeLoaded);
36 }38 }
37 39
38 runExamples() {40 runExamples() {
39 for (var el of Array.from(this.findAllByClass('entry-example'))) {41 for (var el of Array.from(this.findAllByClass("entry-example"))) {
40 try { this.runExample(el); } catch (error) {}42 try {
43 this.runExample(el);
44 } catch (error) {}
41 }45 }
42 }46 }
43 47
44 runExample(el) {48 runExample(el) {
45 let iframe;49 let iframe;
46 const source = el.getElementsByClassName('syntaxhighlighter')[0];50 const source = el.getElementsByClassName("syntaxhighlighter")[0];
47 if (!source || (source.innerHTML.indexOf('!doctype') === -1)) { return; }51 if (!source || source.innerHTML.indexOf("!doctype") === -1) {
52 return;
53 }
48 54
49 if (!(iframe = el.getElementsByClassName(this.constructor.demoClassName)[0])) {55 if (
50 iframe = document.createElement('iframe');56 !(iframe = el.getElementsByClassName(this.constructor.demoClassName)[0])
57 ) {
58 iframe = document.createElement("iframe");
51 iframe.className = this.constructor.demoClassName;59 iframe.className = this.constructor.demoClassName;
52 iframe.width = '100%';60 iframe.width = "100%";
53 iframe.height = 200;61 iframe.height = 200;
54 el.appendChild(iframe);62 el.appendChild(iframe);
55 }63 }
@@ -60,8 +68,13 @@ const Cls = (app.views.JqueryPage = class JqueryPage extends app.views.BasePage · 4 unmodified lines
60 }68 }
61 69
62 fixIframeSource(source) {70 fixIframeSource(source) {
63 source = source.replace('"/resources/', '"https://api.jquery.com/resources/'); // attr(), keydown()71 source = source.replace(
64 source = source.replace('</head>', `\72 '"/resources/',
73 '"https://api.jquery.com/resources/',
74 ); // attr(), keydown()
75 source = source.replace(
76 "</head>",
77 `\
65<style>78<style>
66 html, body { border: 0; margin: 0; padding: 0; }79 html, body { border: 0; margin: 0; padding: 0; }
67 body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; }80 body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; }
@@ -75,7 +88,7 @@ const Cls = (app.views.JqueryPage = class JqueryPage extends app.views.BasePage · 7 unmodified lines
75 });88 });
76</script>89</script>
77</head>\90</head>\
78`91`,
79 );92 );
80 return source.replace(/<script>/gi, '<script nonce="devdocs">');93 return source.replace(/<script>/gi, '<script nonce="devdocs">');
81 }94 }
assets/javascripts/views/pages/rdoc.js+11−7
@@ -10,19 +10,23 @@ · 9 unmodified lines
10 10
11const Cls = (app.views.RdocPage = class RdocPage extends app.views.BasePage {11const Cls = (app.views.RdocPage = class RdocPage extends app.views.BasePage {
12 static initClass() {12 static initClass() {
13 this.events =13 this.events = { click: "onClick" };
14 {click: 'onClick'};
15 }14 }
16 15
17 onClick(event) {16 onClick(event) {
18 if (!event.target.classList.contains('method-click-advice')) { return; }17 if (!event.target.classList.contains("method-click-advice")) {
18 return;
19 }
19 $.stopEvent(event);20 $.stopEvent(event);
20 21
21 const source = $('.method-source-code', event.target.closest('.method-detail'));22 const source = $(
22 const isShown = source.style.display === 'block';23 ".method-source-code",
24 event.target.closest(".method-detail"),
25 );
26 const isShown = source.style.display === "block";
23 27
24 source.style.display = isShown ? 'none' : 'block';28 source.style.display = isShown ? "none" : "block";
25 return event.target.textContent = isShown ? 'Show source' : 'Hide source';29 return (event.target.textContent = isShown ? "Show source" : "Hide source");
26 }30 }
27});31});
28Cls.initClass();32Cls.initClass();
assets/javascripts/views/pages/sqlite.js+15−10
@@ -8,28 +8,33 @@ · 7 unmodified lines
8 */8 */
9//= require views/pages/base9//= require views/pages/base
10 10
11const Cls = (app.views.SqlitePage = class SqlitePage extends app.views.BasePage {11const Cls = (app.views.SqlitePage = class SqlitePage extends (
12 app.views.BasePage
13) {
12 constructor(...args) {14 constructor(...args) {
13 this.onClick = this.onClick.bind(this);15 this.onClick = this.onClick.bind(this);
14 super(...args);16 super(...args);
15 }17 }
16 18
17 static initClass() {19 static initClass() {
18 this.events =20 this.events = { click: "onClick" };
19 {click: 'onClick'};
20 }21 }
21 22
22 onClick(event) {23 onClick(event) {
23 let el, id;24 let el, id;
24 if (!(id = event.target.getAttribute('data-toggle'))) { return; }25 if (!(id = event.target.getAttribute("data-toggle"))) {
25 if (!(el = this.find(`#${id}`))) { return; }26 return;
27 }
28 if (!(el = this.find(`#${id}`))) {
29 return;
30 }
26 $.stopEvent(event);31 $.stopEvent(event);
27 if (el.style.display === 'none') {32 if (el.style.display === "none") {
28 el.style.display = 'block';33 el.style.display = "block";
29 event.target.textContent = 'hide';34 event.target.textContent = "hide";
30 } else {35 } else {
31 el.style.display = 'none';36 el.style.display = "none";
32 event.target.textContent = 'show';37 event.target.textContent = "show";
33 }38 }
34 }39 }
35});40});
assets/javascripts/views/pages/support_tables.js+11−6
@@ -7,19 +7,24 @@ · 6 unmodified lines
7 */7 */
8//= require views/pages/base8//= require views/pages/base
9 9
10const Cls = (app.views.SupportTablesPage = class SupportTablesPage extends app.views.BasePage {10const Cls = (app.views.SupportTablesPage = class SupportTablesPage extends (
11 app.views.BasePage
12) {
11 static initClass() {13 static initClass() {
12 this.events =14 this.events = { click: "onClick" };
13 {click: 'onClick'};
14 }15 }
15 16
16 onClick(event) {17 onClick(event) {
17 if (!event.target.classList.contains('show-all')) { return; }18 if (!event.target.classList.contains("show-all")) {
19 return;
20 }
18 $.stopEvent(event);21 $.stopEvent(event);
19 22
20 let el = event.target;23 let el = event.target;
21 while (el.tagName !== 'TABLE') { el = el.parentNode; }24 while (el.tagName !== "TABLE") {
22 el.classList.add('show-all');25 el = el.parentNode;
26 }
27 el.classList.add("show-all");
23 }28 }
24});29});
25Cls.initClass();30Cls.initClass();
assets/javascripts/views/search/search.js+113−62
@@ -9,7 +9,7 @@ · 8 unmodified lines
9 * DS207: Consider shorter variations of null checks9 * DS207: Consider shorter variations of null checks
10 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md10 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
11 */11 */
12(function() {12(function () {
13 let SEARCH_PARAM = undefined;13 let SEARCH_PARAM = undefined;
14 let HASH_RGX = undefined;14 let HASH_RGX = undefined;
15 const Cls = (app.views.Search = class Search extends app.View {15 const Cls = (app.views.Search = class Search extends app.View {
@@ -33,86 +33,106 @@ · 17 unmodified lines
33 33
34 static initClass() {34 static initClass() {
35 SEARCH_PARAM = app.config.search_param;35 SEARCH_PARAM = app.config.search_param;
36 36
37 this.el = '._search';37 this.el = "._search";
38 this.activeClass = '_search-active';38 this.activeClass = "_search-active";
39 39
40 this.elements = {40 this.elements = {
41 input: '._search-input',41 input: "._search-input",
42 resetLink: '._search-clear'42 resetLink: "._search-clear",
43 };43 };
44 44
45 this.events = {45 this.events = {
46 input: 'onInput',46 input: "onInput",
47 click: 'onClick',47 click: "onClick",
48 submit: 'onSubmit'48 submit: "onSubmit",
49 };49 };
50 50
51 this.shortcuts = {51 this.shortcuts = {
52 typing: 'focus',52 typing: "focus",
53 altG: 'google',53 altG: "google",
54 altS: 'stackoverflow',54 altS: "stackoverflow",
55 altD: 'duckduckgo'55 altD: "duckduckgo",
56 };56 };
57 57
58 this.routes =58 this.routes = { after: "afterRoute" };
59 {after: 'afterRoute'};59
60
61 HASH_RGX = new RegExp(`^#${SEARCH_PARAM}=(.*)`);60 HASH_RGX = new RegExp(`^#${SEARCH_PARAM}=(.*)`);
62 }61 }
63 62
64 init() {63 init() {
65 this.addSubview(this.scope = new app.views.SearchScope(this.el));64 this.addSubview((this.scope = new app.views.SearchScope(this.el)));
66 65
67 this.searcher = new app.Searcher;66 this.searcher = new app.Searcher();
68 this.searcher67 this.searcher.on("results", this.onResults).on("end", this.onEnd);
69 .on('results', this.onResults)
70 .on('end', this.onEnd);
71 68
72 this.scope69 this.scope.on("change", this.onScopeChange);
73 .on('change', this.onScopeChange);
74 70
75 app.on('ready', this.onReady);71 app.on("ready", this.onReady);
76 $.on(window, 'hashchange', this.searchUrl);72 $.on(window, "hashchange", this.searchUrl);
77 $.on(window, 'focus', this.onWindowFocus);73 $.on(window, "focus", this.onWindowFocus);
78 }74 }
79 75
80 focus() {76 focus() {
81 if (document.activeElement === this.input) { return; }77 if (document.activeElement === this.input) {
82 if (app.settings.get('noAutofocus')) { return; }78 return;
83 this.input.focus(); 79 }
80 if (app.settings.get("noAutofocus")) {
81 return;
82 }
83 this.input.focus();
84 }84 }
85 85
86 autoFocus() {86 autoFocus() {
87 if (app.isMobile() || $.isAndroid() || $.isIOS()) { return; }87 if (app.isMobile() || $.isAndroid() || $.isIOS()) {
88 if ((document.activeElement != null ? document.activeElement.tagName : undefined) === 'INPUT') { return; }88 return;
89 if (app.settings.get('noAutofocus')) { return; }89 }
90 if (
91 (document.activeElement != null
92 ? document.activeElement.tagName
93 : undefined) === "INPUT"
94 ) {
95 return;
96 }
97 if (app.settings.get("noAutofocus")) {
98 return;
99 }
90 this.input.focus();100 this.input.focus();
91 }101 }
92 102
93 onWindowFocus(event) {103 onWindowFocus(event) {
94 if (event.target === window) { return this.autoFocus(); }104 if (event.target === window) {
105 return this.autoFocus();
106 }
95 }107 }
96 108
97 getScopeDoc() {109 getScopeDoc() {
98 if (this.scope.isActive()) { return this.scope.getScope(); }110 if (this.scope.isActive()) {
111 return this.scope.getScope();
112 }
99 }113 }
100 114
101 reset(force) {115 reset(force) {
102 if (force || !this.input.value) { this.scope.reset(); }116 if (force || !this.input.value) {
117 this.scope.reset();
118 }
103 this.el.reset();119 this.el.reset();
104 this.onInput();120 this.onInput();
105 this.autoFocus();121 this.autoFocus();
106 }122 }
107 123
108 onReady() {124 onReady() {
109 this.value = '';125 this.value = "";
110 this.delay(this.onInput);126 this.delay(this.onInput);
111 }127 }
112 128
113 onInput() {129 onInput() {
114 if ((this.value == null) || // ignore events pre-"ready"130 if (
115 (this.value === this.input.value)) { return; }131 this.value == null || // ignore events pre-"ready"
132 this.value === this.input.value
133 ) {
134 return;
135 }
116 this.value = this.input.value;136 this.value = this.input.value;
117 137
118 if (this.value.length) {138 if (this.value.length) {
@@ -123,25 +143,33 @@ · 4 unmodified lines
123 }143 }
124 144
125 search(url) {145 search(url) {
126 if (url == null) { url = false; }146 if (url == null) {
147 url = false;
148 }
127 this.addClass(this.constructor.activeClass);149 this.addClass(this.constructor.activeClass);
128 this.trigger('searching');150 this.trigger("searching");
129 151
130 this.hasResults = null;152 this.hasResults = null;
131 this.flags = {urlSearch: url, initialResults: true};153 this.flags = { urlSearch: url, initialResults: true };
132 this.searcher.find(this.scope.getScope().entries.all(), 'text', this.value);154 this.searcher.find(
155 this.scope.getScope().entries.all(),
156 "text",
157 this.value,
158 );
133 }159 }
134 160
135 searchUrl() {161 searchUrl() {
136 let value;162 let value;
137 if (location.pathname === '/') {163 if (location.pathname === "/") {
138 this.scope.searchUrl();164 this.scope.searchUrl();
139 } else if (!app.router.isIndex()) {165 } else if (!app.router.isIndex()) {
140 return;166 return;
141 }167 }
142 168
143 if (!(value = this.extractHashValue())) { return; }169 if (!(value = this.extractHashValue())) {
144 this.input.value = (this.value = value);170 return;
171 }
172 this.input.value = this.value = value;
145 this.input.setSelectionRange(value.length, value.length);173 this.input.setSelectionRange(value.length, value.length);
146 this.search(true);174 this.search(true);
147 return true;175 return true;
@@ -149,13 +177,15 @@ · 1 unmodified line
149 177
150 clear() {178 clear() {
151 this.removeClass(this.constructor.activeClass);179 this.removeClass(this.constructor.activeClass);
152 this.trigger('clear');180 this.trigger("clear");
153 }181 }
154 182
155 externalSearch(url) {183 externalSearch(url) {
156 let value;184 let value;
157 if (value = this.value) {185 if ((value = this.value)) {
158 if (this.scope.name()) { value = `${this.scope.name()} ${value}`; }186 if (this.scope.name()) {
187 value = `${this.scope.name()} ${value}`;
188 }
159 $.popup(`${url}${encodeURIComponent(value)}`);189 $.popup(`${url}${encodeURIComponent(value)}`);
160 this.reset();190 this.reset();
161 }191 }
@@ -174,13 +204,17 @@ · 12 unmodified lines
174 }204 }
175 205
176 onResults(results) {206 onResults(results) {
177 if (results.length) { this.hasResults = true; }207 if (results.length) {
178 this.trigger('results', results, this.flags);208 this.hasResults = true;
209 }
210 this.trigger("results", results, this.flags);
179 this.flags.initialResults = false;211 this.flags.initialResults = false;
180 }212 }
181 213
182 onEnd() {214 onEnd() {
183 if (!this.hasResults) { this.trigger('noresults'); }215 if (!this.hasResults) {
216 this.trigger("noresults");
217 }
184 }218 }
185 219
186 onClick(event) {220 onClick(event) {
@@ -195,14 +229,24 @@ · 8 unmodified lines
195 }229 }
196 230
197 onScopeChange() {231 onScopeChange() {
198 this.value = '';232 this.value = "";
199 this.onInput();233 this.onInput();
200 }234 }
201 235
202 afterRoute(name, context) {236 afterRoute(name, context) {
203 if ((app.shortcuts.eventInProgress != null ? app.shortcuts.eventInProgress.name : undefined) === 'escape') { return; }237 if (
204 if (!context.init && app.router.isIndex()) { this.reset(true); }238 (app.shortcuts.eventInProgress != null
205 if (context.hash) { this.delay(this.searchUrl); }239 ? app.shortcuts.eventInProgress.name
240 : undefined) === "escape"
241 ) {
242 return;
243 }
244 if (!context.init && app.router.isIndex()) {
245 this.reset(true);
246 }
247 if (context.hash) {
248 this.delay(this.searchUrl);
249 }
206 $.requestAnimationFrame(this.autoFocus);250 $.requestAnimationFrame(this.autoFocus);
207 }251 }
208 252
@@ -215,7 +259,12 @@ · 6 unmodified lines
215 }259 }
216 260
217 getHashValue() {261 getHashValue() {
218 try { return __guard__(HASH_RGX.exec($.urlDecode(location.hash)), x => x[1]); } catch (error) {}262 try {
263 return __guard__(
264 HASH_RGX.exec($.urlDecode(location.hash)),
265 (x) => x[1],
266 );
267 } catch (error) {}
219 }268 }
220 });269 });
221 Cls.initClass();270 Cls.initClass();
@@ -223,5 +272,7 @@ · 1 unmodified line
223})();272})();
224 273
225function __guard__(value, transform) {274function __guard__(value, transform) {
226 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;275 return typeof value !== "undefined" && value !== null
227}276 ? transform(value)
277 : undefined;
278}
assets/javascripts/views/search/search_scope.js+91−53
@@ -9,41 +9,49 @@ · 8 unmodified lines
9 * DS207: Consider shorter variations of null checks9 * DS207: Consider shorter variations of null checks
10 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md10 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
11 */11 */
12(function() {12(function () {
13 let SEARCH_PARAM = undefined;13 let SEARCH_PARAM = undefined;
14 let HASH_RGX = undefined;14 let HASH_RGX = undefined;
15 const Cls = (app.views.SearchScope = class SearchScope extends app.View {15 const Cls = (app.views.SearchScope = class SearchScope extends app.View {
16 static initClass() {16 static initClass() {
17 SEARCH_PARAM = app.config.search_param;17 SEARCH_PARAM = app.config.search_param;
18 18
19 this.elements = {19 this.elements = {
20 input: '._search-input',20 input: "._search-input",
21 tag: '._search-tag'21 tag: "._search-tag",
22 };22 };
23 23
24 this.events = {24 this.events = {
25 click: 'onClick',25 click: "onClick",
26 keydown: 'onKeydown',26 keydown: "onKeydown",
27 textInput: 'onTextInput'27 textInput: "onTextInput",
28 };28 };
29 29
30 this.routes =30 this.routes = { after: "afterRoute" };
31 {after: 'afterRoute'};31
32
33 HASH_RGX = new RegExp(`^#${SEARCH_PARAM}=(.+?) .`);32 HASH_RGX = new RegExp(`^#${SEARCH_PARAM}=(.+?) .`);
34 }33 }
35 34
36 constructor(el) { this.onResults = this.onResults.bind(this); this.reset = this.reset.bind(this); this.doScopeSearch = this.doScopeSearch.bind(this); this.onClick = this.onClick.bind(this); this.onKeydown = this.onKeydown.bind(this); this.onTextInput = this.onTextInput.bind(this); this.afterRoute = this.afterRoute.bind(this); this.el = el; super(...arguments); }35 constructor(el) {
36 this.onResults = this.onResults.bind(this);
37 this.reset = this.reset.bind(this);
38 this.doScopeSearch = this.doScopeSearch.bind(this);
39 this.onClick = this.onClick.bind(this);
40 this.onKeydown = this.onKeydown.bind(this);
41 this.onTextInput = this.onTextInput.bind(this);
42 this.afterRoute = this.afterRoute.bind(this);
43 this.el = el;
44 super(...arguments);
45 }
37 46
38 init() {47 init() {
39 this.placeholder = this.input.getAttribute('placeholder');48 this.placeholder = this.input.getAttribute("placeholder");
40 49
41 this.searcher = new app.SynchronousSearcher({50 this.searcher = new app.SynchronousSearcher({
42 fuzzy_min_length: 2,51 fuzzy_min_length: 2,
43 max_results: 152 max_results: 1,
44 });53 });
45 this.searcher.on('results', this.onResults);54 this.searcher.on("results", this.onResults);
46
47 }55 }
48 56
49 getScope() {57 getScope() {
@@ -55,26 +63,34 @@ · 5 unmodified lines
55 }63 }
56 64
57 name() {65 name() {
58 return (this.doc != null ? this.doc.name : undefined);66 return this.doc != null ? this.doc.name : undefined;
59 }67 }
60 68
61 search(value, searchDisabled) {69 search(value, searchDisabled) {
62 if (searchDisabled == null) { searchDisabled = false; }70 if (searchDisabled == null) {
63 if (this.doc) { return; }71 searchDisabled = false;
64 this.searcher.find(app.docs.all(), 'text', value);72 }
65 if (!this.doc && searchDisabled) { this.searcher.find(app.disabledDocs.all(), 'text', value); }73 if (this.doc) {
74 return;
75 }
76 this.searcher.find(app.docs.all(), "text", value);
77 if (!this.doc && searchDisabled) {
78 this.searcher.find(app.disabledDocs.all(), "text", value);
79 }
66 }80 }
67 81
68 searchUrl() {82 searchUrl() {
69 let value;83 let value;
70 if (value = this.extractHashValue()) {84 if ((value = this.extractHashValue())) {
71 this.search(value, true);85 this.search(value, true);
72 }86 }
73 }87 }
74 88
75 onResults(results) {89 onResults(results) {
76 let doc;90 let doc;
77 if (!(doc = results[0])) { return; }91 if (!(doc = results[0])) {
92 return;
93 }
78 if (app.docs.contains(doc)) {94 if (app.docs.contains(doc)) {
79 this.selectDoc(doc);95 this.selectDoc(doc);
80 } else {96 } else {
@@ -84,45 +100,49 @@ · 3 unmodified lines
84 100
85 selectDoc(doc) {101 selectDoc(doc) {
86 const previousDoc = this.doc;102 const previousDoc = this.doc;
87 if (doc === previousDoc) { return; }103 if (doc === previousDoc) {
104 return;
105 }
88 this.doc = doc;106 this.doc = doc;
89 107
90 this.tag.textContent = doc.fullName;108 this.tag.textContent = doc.fullName;
91 this.tag.style.display = 'block';109 this.tag.style.display = "block";
92 110
93 this.input.removeAttribute('placeholder');111 this.input.removeAttribute("placeholder");
94 this.input.value = this.input.value.slice(this.input.selectionStart);112 this.input.value = this.input.value.slice(this.input.selectionStart);
95 this.input.style.paddingLeft = this.tag.offsetWidth + 10 + 'px';113 this.input.style.paddingLeft = this.tag.offsetWidth + 10 + "px";
96 114
97 $.trigger(this.input, 'input');115 $.trigger(this.input, "input");
98 this.trigger('change', this.doc, previousDoc);116 this.trigger("change", this.doc, previousDoc);
99 }117 }
100 118
101 redirectToDoc(doc) {119 redirectToDoc(doc) {
102 const {120 const { hash } = location;
103 hash121 app.router.replaceHash("");
104 } = location;
105 app.router.replaceHash('');
106 location.assign(doc.fullPath() + hash);122 location.assign(doc.fullPath() + hash);
107 }123 }
108 124
109 reset() {125 reset() {
110 if (!this.doc) { return; }126 if (!this.doc) {
127 return;
128 }
111 const previousDoc = this.doc;129 const previousDoc = this.doc;
112 this.doc = null;130 this.doc = null;
113 131
114 this.tag.textContent = '';132 this.tag.textContent = "";
115 this.tag.style.display = 'none';133 this.tag.style.display = "none";
116 134
117 this.input.setAttribute('placeholder', this.placeholder);135 this.input.setAttribute("placeholder", this.placeholder);
118 this.input.style.paddingLeft = '';136 this.input.style.paddingLeft = "";
119 137
120 this.trigger('change', null, previousDoc);138 this.trigger("change", null, previousDoc);
121 }139 }
122 140
123 doScopeSearch(event) {141 doScopeSearch(event) {
124 this.search(this.input.value.slice(0, this.input.selectionStart));142 this.search(this.input.value.slice(0, this.input.selectionStart));
125 if (this.doc) { $.stopEvent(event); }143 if (this.doc) {
144 $.stopEvent(event);
145 }
126 }146 }
127 147
128 onClick(event) {148 onClick(event) {
@@ -133,38 +153,54 @@ · 4 unmodified lines
133 }153 }
134 154
135 onKeydown(event) {155 onKeydown(event) {
136 if (event.which === 8) { // backspace156 if (event.which === 8) {
137 if (this.doc && (this.input.selectionEnd === 0)) {157 // backspace
158 if (this.doc && this.input.selectionEnd === 0) {
138 this.reset();159 this.reset();
139 $.stopEvent(event);160 $.stopEvent(event);
140 }161 }
141 } else if (!this.doc && this.input.value && !$.isChromeForAndroid()) {162 } else if (!this.doc && this.input.value && !$.isChromeForAndroid()) {
142 if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) { return; }163 if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) {
143 if ((event.which === 9) || // tab164 return;
144 ((event.which === 32) && app.isMobile())) { // space165 }
166 if (
167 event.which === 9 || // tab
168 (event.which === 32 && app.isMobile())
169 ) {
170 // space
145 this.doScopeSearch(event);171 this.doScopeSearch(event);
146 }172 }
147 }173 }
148 }174 }
149 175
150 onTextInput(event) {176 onTextInput(event) {
151 if (!$.isChromeForAndroid()) { return; }177 if (!$.isChromeForAndroid()) {
152 if (!this.doc && this.input.value && (event.data === ' ')) {178 return;
179 }
180 if (!this.doc && this.input.value && event.data === " ") {
153 this.doScopeSearch(event);181 this.doScopeSearch(event);
154 }182 }
155 }183 }
156 184
157 extractHashValue() {185 extractHashValue() {
158 let value;186 let value;
159 if (value = this.getHashValue()) {187 if ((value = this.getHashValue())) {
160 const newHash = $.urlDecode(location.hash).replace(`#${SEARCH_PARAM}=${value} `, `#${SEARCH_PARAM}=`);188 const newHash = $.urlDecode(location.hash).replace(
189 `#${SEARCH_PARAM}=${value} `,
190 `#${SEARCH_PARAM}=`,
191 );
161 app.router.replaceHash(newHash);192 app.router.replaceHash(newHash);
162 return value;193 return value;
163 }194 }
164 }195 }
165 196
166 getHashValue() {197 getHashValue() {
167 try { return __guard__(HASH_RGX.exec($.urlDecode(location.hash)), x => x[1]); } catch (error) {}198 try {
199 return __guard__(
200 HASH_RGX.exec($.urlDecode(location.hash)),
201 (x) => x[1],
202 );
203 } catch (error) {}
168 }204 }
169 205
170 afterRoute(name, context) {206 afterRoute(name, context) {
@@ -178,5 +214,7 @@ · 7 unmodified lines
178})();214})();
179 215
180function __guard__(value, transform) {216function __guard__(value, transform) {
181 return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;217 return typeof value !== "undefined" && value !== null
182}218 ? transform(value)
219 : undefined;
220}
assets/javascripts/views/sidebar/doc_list.js+90−55
@@ -20,65 +20,75 @@ const Cls = (app.views.DocList = class DocList extends app.View { · 19 unmodified lines
20 }20 }
21 21
22 static initClass() {22 static initClass() {
23 this.className = '_list';23 this.className = "_list";
24 this.attributes =24 this.attributes = { role: "navigation" };
25 {role: 'navigation'};25
26
27 this.events = {26 this.events = {
28 open: 'onOpen',27 open: "onOpen",
29 close: 'onClose',28 close: "onClose",
30 click: 'onClick'29 click: "onClick",
31 };30 };
32 31
33 this.routes =32 this.routes = { after: "afterRoute" };
34 {after: 'afterRoute'};33
35
36 this.elements = {34 this.elements = {
37 disabledTitle: '._list-title',35 disabledTitle: "._list-title",
38 disabledList: '._disabled-list'36 disabledList: "._disabled-list",
39 };37 };
40 }38 }
41 39
42 init() {40 init() {
43 this.lists = {};41 this.lists = {};
44 42
45 this.addSubview(this.listFocus = new app.views.ListFocus(this.el));43 this.addSubview((this.listFocus = new app.views.ListFocus(this.el)));
46 this.addSubview(this.listFold = new app.views.ListFold(this.el));44 this.addSubview((this.listFold = new app.views.ListFold(this.el)));
47 this.addSubview(this.listSelect = new app.views.ListSelect(this.el));45 this.addSubview((this.listSelect = new app.views.ListSelect(this.el)));
48 46
49 app.on('ready', this.render);47 app.on("ready", this.render);
50 }48 }
51 49
52 activate() {50 activate() {
53 if (super.activate(...arguments)) {51 if (super.activate(...arguments)) {
54 for (var slug in this.lists) { var list = this.lists[slug]; list.activate(); }52 for (var slug in this.lists) {
53 var list = this.lists[slug];
54 list.activate();
55 }
55 this.listSelect.selectCurrent();56 this.listSelect.selectCurrent();
56 }57 }
57 }58 }
58 59
59 deactivate() {60 deactivate() {
60 if (super.deactivate(...arguments)) {61 if (super.deactivate(...arguments)) {
61 for (var slug in this.lists) { var list = this.lists[slug]; list.deactivate(); }62 for (var slug in this.lists) {
63 var list = this.lists[slug];
64 list.deactivate();
65 }
62 }66 }
63 }67 }
64 68
65 render() {69 render() {
66 let html = '';70 let html = "";
67 for (var doc of Array.from(app.docs.all())) {71 for (var doc of Array.from(app.docs.all())) {
68 html += this.tmpl('sidebarDoc', doc, {fullName: app.docs.countAllBy('name', doc.name) > 1});72 html += this.tmpl("sidebarDoc", doc, {
73 fullName: app.docs.countAllBy("name", doc.name) > 1,
74 });
69 }75 }
70 this.html(html);76 this.html(html);
71 if (!app.isSingleDoc() && (app.disabledDocs.size() !== 0)) { this.renderDisabled(); }77 if (!app.isSingleDoc() && app.disabledDocs.size() !== 0) {
78 this.renderDisabled();
79 }
72 }80 }
73 81
74 renderDisabled() {82 renderDisabled() {
75 this.append(this.tmpl('sidebarDisabled', {count: app.disabledDocs.size()}));83 this.append(
84 this.tmpl("sidebarDisabled", { count: app.disabledDocs.size() }),
85 );
76 this.refreshElements();86 this.refreshElements();
77 this.renderDisabledList();87 this.renderDisabledList();
78 }88 }
79 89
80 renderDisabledList() {90 renderDisabledList() {
81 if (app.settings.get('hideDisabled')) {91 if (app.settings.get("hideDisabled")) {
82 this.removeDisabledList();92 this.removeDisabledList();
83 } else {93 } else {
84 this.appendDisabledList();94 this.appendDisabledList();
@@ -87,60 +97,67 @@ const Cls = (app.views.DocList = class DocList extends app.View { · 2 unmodified lines
87 97
88 appendDisabledList() {98 appendDisabledList() {
89 let doc;99 let doc;
90 let html = '';100 let html = "";
91 const docs = [].concat(...Array.from(app.disabledDocs.all() || []));101 const docs = [].concat(...Array.from(app.disabledDocs.all() || []));
92 102
93 while ((doc = docs.shift())) {103 while ((doc = docs.shift())) {
94 if (doc.version != null) {104 if (doc.version != null) {
95 var versions = '';105 var versions = "";
96 while (true) {106 while (true) {
97 versions += this.tmpl('sidebarDoc', doc, {disabled: true});107 versions += this.tmpl("sidebarDoc", doc, { disabled: true });
98 if ((docs[0] != null ? docs[0].name : undefined) !== doc.name) { break; }108 if ((docs[0] != null ? docs[0].name : undefined) !== doc.name) {
109 break;
110 }
99 doc = docs.shift();111 doc = docs.shift();
100 }112 }
101 html += this.tmpl('sidebarDisabledVersionedDoc', doc, versions);113 html += this.tmpl("sidebarDisabledVersionedDoc", doc, versions);
102 } else {114 } else {
103 html += this.tmpl('sidebarDoc', doc, {disabled: true});115 html += this.tmpl("sidebarDoc", doc, { disabled: true });
104 }116 }
105 }117 }
106 118
107 this.append(this.tmpl('sidebarDisabledList', html));119 this.append(this.tmpl("sidebarDisabledList", html));
108 this.disabledTitle.classList.add('open-title');120 this.disabledTitle.classList.add("open-title");
109 this.refreshElements();121 this.refreshElements();
110 }122 }
111 123
112 removeDisabledList() {124 removeDisabledList() {
113 if (this.disabledList) { $.remove(this.disabledList); }125 if (this.disabledList) {
114 this.disabledTitle.classList.remove('open-title');126 $.remove(this.disabledList);
127 }
128 this.disabledTitle.classList.remove("open-title");
115 this.refreshElements();129 this.refreshElements();
116 }130 }
117 131
118 reset(options) {132 reset(options) {
119 if (options == null) { options = {}; }133 if (options == null) {
134 options = {};
135 }
120 this.listSelect.deselect();136 this.listSelect.deselect();
121 if (this.listFocus != null) {137 if (this.listFocus != null) {
122 this.listFocus.blur();138 this.listFocus.blur();
123 }139 }
124 this.listFold.reset();140 this.listFold.reset();
125 if (options.revealCurrent || app.isSingleDoc()) { this.revealCurrent(); }141 if (options.revealCurrent || app.isSingleDoc()) {
142 this.revealCurrent();
143 }
126 }144 }

125 more lines aren’t shown here.

assets/javascripts/views/sidebar/doc_picker.js+62−32

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

assets/javascripts/views/sidebar/entry_list.js+10−5

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

assets/javascripts/views/sidebar/results.js+49−28

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

assets/javascripts/views/sidebar/sidebar.js+79−43

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

assets/javascripts/views/sidebar/sidebar_hover.js+35−26

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

assets/javascripts/views/sidebar/type_list.js+36−15

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

assets/javascripts/views/view.js+64−23

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