datalog-theme 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +188 -0
  3. data/CITATION.cff +2 -2
  4. data/README.md +25 -17
  5. data/_data/cdn-integrity.yml +0 -30
  6. data/_data/i18n/en.yml +297 -0
  7. data/_data/i18n/es.yml +297 -0
  8. data/_data/i18n/pt.yml +297 -0
  9. data/_data/js_manifest.json +16 -0
  10. data/_includes/analytics/dashboard.html +3 -1
  11. data/_includes/components/api-function.html +20 -1
  12. data/_includes/components/author-bio.html +21 -11
  13. data/_includes/components/author-list.html +32 -0
  14. data/_includes/components/citation-tools.html +33 -25
  15. data/_includes/components/comments-thread.html +90 -0
  16. data/_includes/components/contact-form.html +120 -0
  17. data/_includes/components/correction-report.html +82 -0
  18. data/_includes/components/enhanced-code-block.html +1 -1
  19. data/_includes/components/enhanced-toc.html +6 -7
  20. data/_includes/components/license-link.html +13 -0
  21. data/_includes/components/license-notice.html +28 -0
  22. data/_includes/components/moderation-inbox.html +139 -0
  23. data/_includes/components/reactions.html +49 -0
  24. data/_includes/components/reading-list.html +31 -0
  25. data/_includes/components/reading-mode-toggle.html +65 -0
  26. data/_includes/components/reading-state-bookmark.html +27 -0
  27. data/_includes/components/reading-state-panel.html +71 -0
  28. data/_includes/components/reproducibility.html +61 -0
  29. data/_includes/components/responsive-image.html +3 -3
  30. data/_includes/components/revision-history.html +39 -0
  31. data/_includes/components/revision-notice.html +35 -0
  32. data/_includes/components/series-nav.html +64 -0
  33. data/_includes/components/subscribe-form.html +88 -0
  34. data/_includes/components/subscription-manage.html +68 -0
  35. data/_includes/components/webmentions.html +48 -0
  36. data/_includes/csp-meta.html +135 -11
  37. data/_includes/footer.html +22 -20
  38. data/_includes/head.html +111 -63
  39. data/_includes/header/navigation.html +12 -15
  40. data/_includes/header.html +28 -19
  41. data/_includes/layouts/default/article.html +9 -7
  42. data/_includes/meta/dynamic-services-config.html +14 -0
  43. data/_includes/meta/math-config.html +18 -10
  44. data/_includes/meta/person-json.html +27 -0
  45. data/_includes/meta/publisher.html +45 -0
  46. data/_includes/meta/schema.html +67 -30
  47. data/_includes/meta/scholarly.html +121 -0
  48. data/_includes/meta/scripts-loader.html +18 -32
  49. data/_includes/meta/webmention-discovery.html +14 -0
  50. data/_includes/post/related-posts.html +4 -7
  51. data/_includes/scripts.html +57 -0
  52. data/_includes/search/index-data.json +9 -34
  53. data/_layouts/dataset.html +5 -3
  54. data/_layouts/default.html +16 -8
  55. data/_layouts/notebook.html +1 -0
  56. data/_layouts/package.html +4 -2
  57. data/_layouts/page.html +15 -0
  58. data/_layouts/portfolio.html +1 -0
  59. data/_layouts/post.html +102 -23
  60. data/_layouts/project.html +4 -3
  61. data/_layouts/research.html +20 -8
  62. data/_plugins/analytics_dashboard.rb +9 -3
  63. data/_plugins/authors.rb +133 -0
  64. data/_plugins/config_validator.rb +231 -23
  65. data/_plugins/critical_css_check.rb +42 -0
  66. data/_plugins/csp_generator.rb +18 -28
  67. data/_plugins/datalog_bibliography.rb +9 -7
  68. data/_plugins/datalog_comments.rb +8 -5
  69. data/_plugins/datalog_slides.rb +9 -8
  70. data/_plugins/i18n.rb +13 -12
  71. data/_plugins/image_optimizer.rb +241 -178
  72. data/_plugins/licenses.rb +135 -0
  73. data/_plugins/math_preprocessor.rb +59 -7
  74. data/_plugins/notebook_converter.rb +23 -4
  75. data/_plugins/plugin_loader.rb +3 -1
  76. data/_plugins/publications_generator.rb +8 -2
  77. data/_plugins/references.rb +238 -0
  78. data/_plugins/reproducibility.rb +150 -0
  79. data/_plugins/revisions.rb +101 -0
  80. data/_plugins/rouge_highlight_filter.rb +42 -0
  81. data/_plugins/scholarly.rb +50 -0
  82. data/_plugins/search_code_blocks.rb +30 -0
  83. data/_plugins/search_normalizer.rb +15 -53
  84. data/_plugins/search_pages.rb +3 -4
  85. data/_plugins/series.rb +104 -0
  86. data/_plugins/statements.rb +87 -0
  87. data/_sass/_academic-dashboard.scss +262 -0
  88. data/_sass/_base.scss +20 -1
  89. data/_sass/_comments-thread.scss +159 -0
  90. data/_sass/_components.scss +64 -1153
  91. data/_sass/_features.scss +17 -0
  92. data/_sass/_layout.scss +385 -1
  93. data/_sass/_mathematical.scss +27 -0
  94. data/_sass/_moderation.scss +222 -0
  95. data/_sass/_notebooks.scss +322 -0
  96. data/_sass/_open-science-badges.scss +56 -0
  97. data/_sass/{_phase1-enhancements.scss → _post-components.scss} +5 -3
  98. data/_sass/_print.scss +291 -0
  99. data/_sass/_reactions.scss +89 -0
  100. data/_sass/_reading-state.scss +290 -0
  101. data/_sass/_search-page.scss +530 -0
  102. data/_sass/_search.scss +46 -0
  103. data/_sass/_service-forms.scss +204 -0
  104. data/_sass/_subscriptions.scss +140 -0
  105. data/_sass/_syntax-highlighting.scss +212 -97
  106. data/_sass/_theme.scss +40 -19
  107. data/_sass/_typography.scss +130 -0
  108. data/_sass/_utilities.scss +5 -0
  109. data/_sass/_variables.scss +6 -0
  110. data/_sass/_webmentions.scss +125 -0
  111. data/assets/css/main.scss +14 -0
  112. data/assets/js/dist/academic.js +1 -1
  113. data/assets/js/dist/analytics-dashboard.js +1 -1
  114. data/assets/js/dist/chunks/chunk-2DYDWUFX.js +1 -0
  115. data/assets/js/dist/chunks/chunk-PATLC23F.js +1 -0
  116. data/assets/js/dist/chunks/chunk-V7734B2G.js +1 -0
  117. data/assets/js/dist/comments.js +2 -0
  118. data/assets/js/dist/contact.js +1 -0
  119. data/assets/js/dist/core.js +1 -1
  120. data/assets/js/dist/corrections.js +1 -0
  121. data/assets/js/dist/loader.js +1 -1
  122. data/assets/js/dist/math.js +1 -1
  123. data/assets/js/dist/moderation.js +1 -0
  124. data/assets/js/dist/notebook.js +1 -1
  125. data/assets/js/dist/reactions.js +1 -0
  126. data/assets/js/dist/reading-state.js +1 -0
  127. data/assets/js/dist/search.js +1 -1
  128. data/assets/js/dist/sources.json +52 -0
  129. data/assets/js/dist/subscriptions.js +1 -0
  130. data/assets/js/dist/visualizations.js +11 -2
  131. data/assets/js/dist/webmentions.js +1 -0
  132. data/assets/js/loader.js +37 -1
  133. data/datalog-theme.gemspec +35 -23
  134. data/lib/datalog/cli.rb +43 -15
  135. data/lib/datalog/critical_css.rb +168 -0
  136. data/lib/datalog/plugin_system/dependency_resolver.rb +0 -2
  137. data/lib/datalog/plugins/comments.rb +33 -3
  138. data/lib/datalog/theme/installed_files.rb +113 -0
  139. data/lib/datalog/theme/package.rb +57 -0
  140. data/lib/datalog/theme/repository_checkout.rb +94 -0
  141. data/lib/datalog/theme/version.rb +5 -1
  142. data/lib/datalog/warning_filter.rb +5 -11
  143. data/lib/datalog-theme.rb +6 -0
  144. metadata +96 -147
  145. data/_data/academic.yml +0 -217
  146. data/_data/config/author.yml +0 -121
  147. data/_data/datasets.yml +0 -28
  148. data/_data/js_meta.json +0 -371
  149. data/_data/navigation.yml +0 -145
  150. data/_data/projects.yml +0 -41
  151. data/_data/publications.yml +0 -28
  152. data/_data/social.yml +0 -73
  153. data/_data/visualizations.yml +0 -51
  154. data/_includes/components/advanced-search.html +0 -682
  155. data/_includes/components/bookmark-system.html +0 -96
  156. data/_includes/components/comments.html +0 -244
  157. data/_includes/components/content-recommendations.html +0 -228
  158. data/_includes/components/email-preferences.html +0 -200
  159. data/_includes/components/enhanced-metadata.html +0 -228
  160. data/_includes/components/language-switcher.html +0 -396
  161. data/_includes/components/navigation-enhancements.html +0 -454
  162. data/_includes/components/newsletter-signup.html +0 -178
  163. data/_includes/components/popular-posts.html +0 -233
  164. data/_includes/components/reading-progress.html +0 -133
  165. data/_includes/components/reading-time.html +0 -121
  166. data/_includes/components/series-navigation.html +0 -124
  167. data/_includes/components/social-proof.html +0 -34
  168. data/_includes/components/user-preferences.html +0 -566
  169. data/_includes/meta/syntax-config.html +0 -19
  170. data/_layouts/archive.html +0 -282
  171. data/_layouts/post-sidebar.html +0 -183
  172. data/_sass/_phase3-enhancements.scss +0 -874
  173. data/_sass/_phase4-enhancements.scss +0 -1214
  174. data/_sass/_phase5-enhancements.scss +0 -414
  175. data/assets/js/academic.js +0 -262
  176. data/assets/js/analytics-dashboard.js +0 -382
  177. data/assets/js/core/dark-mode.js +0 -79
  178. data/assets/js/core/github-cards.js +0 -123
  179. data/assets/js/core/language-filter.js +0 -69
  180. data/assets/js/core/navigation.js +0 -184
  181. data/assets/js/core/scroll-progress.js +0 -45
  182. data/assets/js/core/search-hotkeys.js +0 -62
  183. data/assets/js/core/skip-links.js +0 -62
  184. data/assets/js/dist/manifest.json +0 -22
  185. data/assets/js/dist/meta.json +0 -371
  186. data/assets/js/main.js +0 -23
  187. data/assets/js/math.js +0 -818
  188. data/assets/js/notebook.js +0 -158
  189. data/assets/js/search/analytics.js +0 -91
  190. data/assets/js/search/app.js +0 -271
  191. data/assets/js/search/autocomplete.js +0 -120
  192. data/assets/js/search/engine.js +0 -260
  193. data/assets/js/search/filters.js +0 -45
  194. data/assets/js/search/render.js +0 -217
  195. data/assets/js/search/utils.js +0 -99
  196. data/assets/js/search.js +0 -354
  197. data/assets/js/visualizations.js +0 -816
  198. data/assets/publications/datalog-publications.bib +0 -8
  199. data/assets/publications/datalog-publications.ris +0 -9
  200. data/assets/publications/publications.bib +0 -30
  201. data/assets/templates/diogo-ribeiro-cv.md +0 -31
  202. data/assets/templates/diogo-ribeiro-cv.tex +0 -32
  203. data/lib/datalog/theme/theme.rb +0 -18
data/assets/js/math.js DELETED
@@ -1,818 +0,0 @@
1
- /**
2
- * @fileoverview MathJax enhancements for LaTeX rendering.
3
- * Provides equation numbering, copy-to-clipboard, live editor, and accessibility features.
4
- * @module math
5
- */
6
-
7
- (function () {
8
- /**
9
- * MathToolkit object containing all math enhancement functionality.
10
- * @namespace MathToolkit
11
- */
12
- const MathToolkit = {
13
- init(MathJax) {
14
- this.MathJax = MathJax;
15
- this.displayCounter = 0;
16
- this.anchorCounter = 0;
17
- this.equationMap = new Map();
18
- this.editor = null;
19
- this.editorOpen = false;
20
- this.messageTimeout = null;
21
- this.initialized = true;
22
- this.pendingTypeset = [];
23
-
24
- this.setupEditor();
25
-
26
- MathJax.startup.promise
27
- .then(() => {
28
- this.decorateExisting(MathJax.startup.document.math || []);
29
- this.updateEquationReferences();
30
- if (this.pendingTypeset.length > 0) {
31
- const pending = [...this.pendingTypeset];
32
- this.pendingTypeset = [];
33
- pending.forEach((item) => {
34
- this.renderLatex(item.target, item.latex, item.options || {});
35
- });
36
- }
37
- })
38
- .catch((error) => {
39
- console.error('Failed to initialize MathJax enhancements', error);
40
- });
41
-
42
- document.addEventListener('click', (event) => this.handleClick(event));
43
- document.addEventListener('keydown', (event) => this.handleKeydown(event));
44
- },
45
-
46
- onPageReady() {
47
- this.updateEquationReferences();
48
- },
49
-
50
- decorateExisting(mathItems) {
51
- if (!mathItems || mathItems.length === 0) {
52
- return;
53
- }
54
-
55
- this.displayCounter = 0;
56
- this.anchorCounter = 0;
57
- this.equationMap.clear();
58
-
59
- mathItems.forEach((item) => {
60
- try {
61
- this.decorateMathItem(item);
62
- } catch (error) {
63
- console.warn('Unable to decorate math item', error);
64
- }
65
- });
66
- },
67
-
68
- decorateMathItem(item) {
69
- if (!item || !item.typesetRoot) {
70
- return;
71
- }
72
-
73
- const container = item.typesetRoot;
74
- const latex = item.math || '';
75
- const wrapper = container.closest('[data-math-alt]') || container;
76
-
77
- const altText = this.resolveAltText(wrapper, latex);
78
-
79
- container.dataset.mathLatex = latex;
80
- container.setAttribute('tabindex', '0');
81
- container.setAttribute('role', 'math');
82
-
83
- this.syncAltAttributes(wrapper, container, altText);
84
-
85
- if (item.display) {
86
- this.decorateDisplay(container, latex);
87
- } else {
88
- this.decorateInline(container, latex);
89
- }
90
- },
91
-
92
- decorateInline(container, latex) {
93
- if (!container) {
94
- return;
95
- }
96
- if (!container.classList.contains('math-expression__inline')) {
97
- container.classList.add('math-expression__inline');
98
- }
99
- container.dataset.mathLatexClean = this.cleanLatex(latex);
100
- },
101
-
102
- decorateDisplay(container, latex) {
103
- if (!container) {
104
- return;
105
- }
106
-
107
- const existingWrapper = container.parentElement && container.parentElement.classList.contains('math-expression')
108
- ? container.parentElement
109
- : null;
110
- let wrapper = existingWrapper;
111
-
112
- if (!wrapper) {
113
- wrapper = document.createElement('figure');
114
- wrapper.className = 'math-expression math-expression--display';
115
- container.parentNode.insertBefore(wrapper, container);
116
- wrapper.appendChild(container);
117
- }
118
-
119
- const info = this.extractEquationInfo(latex);
120
- this.anchorCounter += 1;
121
- const fallbackAnchor = `eq:auto-${this.anchorCounter}`;
122
- let equationNumber = info.tag || '';
123
-
124
- if (!info.skipNumber) {
125
- this.displayCounter += 1;
126
- if (!equationNumber) {
127
- equationNumber = String(this.displayCounter);
128
- }
129
- }
130
-
131
- const formattedNumber = equationNumber ? `(${equationNumber})` : '';
132
- const anchorId = this.buildAnchorId(info.label, fallbackAnchor);
133
-
134
- wrapper.dataset.mathLatex = latex;
135
- wrapper.dataset.mathLatexClean = this.cleanLatex(latex);
136
- if (container.dataset.mathAlt && !wrapper.dataset.mathAlt) {
137
- wrapper.dataset.mathAlt = container.dataset.mathAlt;
138
- }
139
- if (formattedNumber) {
140
- wrapper.dataset.equationNumber = formattedNumber;
141
- } else {
142
- wrapper.removeAttribute('data-equation-number');
143
- }
144
- wrapper.dataset.equationLabel = info.label || '';
145
-
146
- if (anchorId) {
147
- wrapper.id = anchorId;
148
- const displayText = formattedNumber || info.label || anchorId;
149
- if (formattedNumber || info.label) {
150
- this.setEquationReference(anchorId, displayText);
151
- if (info.label && info.label !== anchorId) {
152
- this.setEquationReference(info.label, displayText, anchorId);
153
- }
154
- if (info.label && info.label.startsWith('eq:')) {
155
- const plainLabel = info.label.replace(/^eq:/, '');
156
- this.setEquationReference(plainLabel, displayText, anchorId);
157
- }
158
- }
159
- }
160
-
161
- this.attachNumberBadge(wrapper, formattedNumber);
162
- this.attachToolbar(wrapper);
163
- },
164
-
165
- extractEquationInfo(latex) {
166
- const source = (latex || '').trim();
167
- const tagMatch = source.match(/\\tag\*?\{([^}]*)\}/);
168
- const labelMatch = source.match(/\\label\{([^}]*)\}/);
169
- const skipNumber = /\\notag|\\nonumber/.test(source);
170
-
171
- return {
172
- tag: tagMatch ? tagMatch[1].trim() : '',
173
- label: labelMatch ? labelMatch[1].trim() : '',
174
- skipNumber
175
- };
176
- },
177
-
178
- buildAnchorId(label, fallback) {
179
- if (label) {
180
- return label.startsWith('eq:') ? label : `eq:${label}`;
181
- }
182
- if (fallback) {
183
- return fallback;
184
- }
185
- return '';
186
- },
187
-
188
- setEquationReference(key, displayText, anchor) {
189
- if (!key) {
190
- return;
191
- }
192
- const target = anchor || key;
193
- const numberText = displayText || key;
194
- this.equationMap.set(key, {
195
- anchor: target,
196
- label: key,
197
- number: numberText
198
- });
199
- if (!key.startsWith('eq:')) {
200
- this.equationMap.set(`eq:${key}`, {
201
- anchor: target,
202
- label: key,
203
- number: numberText
204
- });
205
- }
206
- },
207
-
208
- attachNumberBadge(wrapper, formattedNumber) {
209
- let badge = wrapper.querySelector('.math-expression__number');
210
- let sr = wrapper.querySelector('.math-expression__sr');
211
-
212
- if (!formattedNumber) {
213
- if (badge) {
214
- badge.remove();
215
- }
216
- if (sr) {
217
- sr.remove();
218
- }
219
- return;
220
- }
221
-
222
- if (!badge) {
223
- badge = document.createElement('span');
224
- badge.className = 'math-expression__number';
225
- badge.setAttribute('aria-hidden', 'true');
226
- wrapper.appendChild(badge);
227
- }
228
- badge.textContent = formattedNumber;
229
-
230
- if (!sr) {
231
- sr = document.createElement('span');
232
- sr.className = 'visually-hidden math-expression__sr';
233
- wrapper.appendChild(sr);
234
- }
235
- sr.textContent = `Equation ${formattedNumber.replace(/[()]/g, '')}`;
236
- },
237
-
238
- attachToolbar(wrapper) {
239
- let toolbar = wrapper.querySelector('.math-expression__toolbar');
240
-
241
- if (!toolbar) {
242
- toolbar = document.createElement('div');
243
- toolbar.className = 'math-expression__toolbar';
244
-
245
- const copyButton = document.createElement('button');
246
- copyButton.type = 'button';
247
- copyButton.className = 'math-expression__button';
248
- copyButton.setAttribute('data-math-copy', 'true');
249
- copyButton.setAttribute('aria-label', 'Copy LaTeX code');
250
- const copyIcon = document.createElement('span');
251
- copyIcon.setAttribute('aria-hidden', 'true');
252
- copyIcon.textContent = '⧉';
253
- const copySr = document.createElement('span');
254
- copySr.className = 'visually-hidden';
255
- copySr.textContent = 'Copy equation';
256
- copyButton.appendChild(copyIcon);
257
- copyButton.appendChild(copySr);
258
-
259
- const editButton = document.createElement('button');
260
- editButton.type = 'button';
261
- editButton.className = 'math-expression__button';
262
- editButton.setAttribute('data-math-edit', 'true');
263
- editButton.setAttribute('aria-label', 'Open equation editor');
264
- const editIcon = document.createElement('span');
265
- editIcon.setAttribute('aria-hidden', 'true');
266
- editIcon.textContent = '✎';
267
- const editSr = document.createElement('span');
268
- editSr.className = 'visually-hidden';
269
- editSr.textContent = 'Edit equation';
270
- editButton.appendChild(editIcon);
271
- editButton.appendChild(editSr);
272
-
273
- const message = document.createElement('span');
274
- message.className = 'math-expression__message';
275
- message.setAttribute('aria-live', 'polite');
276
- message.hidden = true;
277
-
278
- toolbar.appendChild(copyButton);
279
- toolbar.appendChild(editButton);
280
- toolbar.appendChild(message);
281
- wrapper.appendChild(toolbar);
282
- }
283
- },
284
-
285
- handleClick(event) {
286
- const copyTrigger = event.target.closest('[data-math-copy]');
287
- if (copyTrigger) {
288
- event.preventDefault();
289
- const wrapper = copyTrigger.closest('.math-expression');
290
- const latex = wrapper ? wrapper.dataset.mathLatexClean || wrapper.dataset.mathLatex : null;
291
- this.copyLatex(latex, wrapper);
292
- return;
293
- }
294
-
295
- const editTrigger = event.target.closest('[data-math-edit]');
296
- if (editTrigger) {
297
- event.preventDefault();
298
- const wrapper = editTrigger.closest('.math-expression');
299
- const latex = wrapper ? wrapper.dataset.mathLatex || '' : '';
300
- this.openEditor(latex);
301
- return;
302
- }
303
-
304
- const closeTrigger = event.target.closest('[data-math-editor-close]');
305
- if (closeTrigger) {
306
- event.preventDefault();
307
- this.closeEditor();
308
- return;
309
- }
310
-
311
- const copyEditor = event.target.closest('[data-math-editor-copy]');
312
- if (copyEditor) {
313
- event.preventDefault();
314
- if (this.editor && this.editor.textarea) {
315
- this.copyLatex(this.editor.textarea.value, null, this.editor.status);
316
- }
317
- return;
318
- }
319
-
320
- const symbolTrigger = event.target.closest('[data-symbol-insert]');
321
- if (symbolTrigger) {
322
- event.preventDefault();
323
- const value = symbolTrigger.getAttribute('data-symbol-insert');
324
- this.insertSymbol(value || '');
325
- }
326
- },
327
-
328
- handleKeydown(event) {
329
- const active = document.activeElement;
330
- const typingContext =
331
- active &&
332
- (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.isContentEditable);
333
-
334
- if ((event.ctrlKey || event.metaKey) && event.altKey && (event.key === 'e' || event.key === 'E')) {
335
- event.preventDefault();
336
- const seedLatex = typingContext && active ? active.value || '' : '';
337
- this.openEditor(seedLatex);
338
- return;
339
- }
340
-
341
- if (event.key === 'Escape' && this.editorOpen) {
342
- event.preventDefault();
343
- this.closeEditor();
344
- }
345
- },
346
-
347
- copyLatex(latex, wrapper, statusTarget) {
348
- if (!latex) {
349
- return;
350
- }
351
-
352
- const showStatus = (message) => {
353
- if (statusTarget) {
354
- statusTarget.hidden = false;
355
- statusTarget.textContent = message;
356
- clearTimeout(this.messageTimeout);
357
- this.messageTimeout = window.setTimeout(() => {
358
- statusTarget.hidden = true;
359
- statusTarget.textContent = '';
360
- }, 2000);
361
- } else if (wrapper) {
362
- const toolbar = wrapper.querySelector('.math-expression__message');
363
- if (toolbar) {
364
- toolbar.hidden = false;
365
- toolbar.textContent = message;
366
- clearTimeout(this.messageTimeout);
367
- this.messageTimeout = window.setTimeout(() => {
368
- toolbar.hidden = true;
369
- toolbar.textContent = '';
370
- }, 2000);
371
- }
372
- }
373
- };
374
-
375
- const fallbackCopy = () => {
376
- const temp = document.createElement('textarea');
377
- temp.value = latex;
378
- temp.setAttribute('aria-hidden', 'true');
379
- temp.style.position = 'fixed';
380
- temp.style.opacity = '0';
381
- document.body.appendChild(temp);
382
- temp.select();
383
- try {
384
- document.execCommand('copy');
385
- showStatus('Copied equation');
386
- } catch (error) {
387
- console.warn('Unable to copy equation', error);
388
- showStatus('Unable to copy');
389
- }
390
- document.body.removeChild(temp);
391
- };
392
-
393
- if (navigator.clipboard && navigator.clipboard.writeText) {
394
- navigator.clipboard
395
- .writeText(latex)
396
- .then(() => showStatus('Copied equation'))
397
- .catch(() => fallbackCopy());
398
- } else {
399
- fallbackCopy();
400
- }
401
- },
402
-
403
- setupEditor() {
404
- if (this.editor) {
405
- return;
406
- }
407
-
408
- const overlay = document.createElement('div');
409
- overlay.id = 'math-tooling-panel';
410
- overlay.hidden = true;
411
-
412
- const dialog = document.createElement('div');
413
- dialog.className = 'math-tooling';
414
- dialog.setAttribute('role', 'dialog');
415
- dialog.setAttribute('aria-modal', 'true');
416
- dialog.setAttribute('aria-labelledby', 'math-tooling-title');
417
-
418
- const title = document.createElement('h2');
419
- title.id = 'math-tooling-title';
420
- title.className = 'math-tooling__title';
421
- title.textContent = 'Equation editor';
422
-
423
- const helper = document.createElement('p');
424
- helper.className = 'math-tooling__description';
425
- // innerHTML required for MathJax rendering
426
- helper.innerHTML =
427
- 'Craft LaTeX expressions with live preview, quick statistical symbols, and chemistry support.';
428
-
429
- const textarea = document.createElement('textarea');
430
- textarea.className = 'math-tooling__input';
431
- textarea.setAttribute('data-math-editor-input', 'true');
432
- textarea.setAttribute('spellcheck', 'false');
433
- textarea.rows = 10;
434
-
435
- const actions = document.createElement('div');
436
- actions.className = 'math-tooling__actions';
437
-
438
- const preview = document.createElement('div');
439
- preview.className = 'math-tooling__preview';
440
- preview.setAttribute('data-math-editor-preview', 'true');
441
- preview.setAttribute('aria-live', 'polite');
442
-
443
- const status = document.createElement('span');
444
- status.className = 'math-tooling__status';
445
- status.hidden = true;
446
-
447
- const library = document.createElement('div');
448
- library.className = 'math-tooling__library';
449
-
450
- const libraryTitle = document.createElement('h3');
451
- libraryTitle.className = 'math-tooling__library-title';
452
- libraryTitle.textContent = 'Quick insert';
453
-
454
- const symbolList = document.createElement('div');
455
- symbolList.className = 'math-tooling__symbols';
456
-
457
- const symbols = [
458
- { label: 'Mean μ', value: '\\mu' },
459
- { label: 'Variance σ²', value: '\\sigma^2' },
460
- { label: 'Std. dev σ', value: '\\sigma' },
461
- { label: 'Probability', value: '\\mathbb{P}(A)' },
462
- { label: 'Expectation', value: '\\mathbb{E}[X]' },
463
- { label: 'Covariance', value: '\\mathrm{Cov}(X, Y)' },
464
- { label: 'Vector', value: '\\mathbf{x}' },
465
- { label: 'Norm', value: '\\lVert x \\rVert_2' },
466
- { label: 'Inner product', value: '\\langle x, y \\rangle' },
467
- { label: 'Derivative', value: '\\frac{\\mathrm{d}}{\\mathrm{d}t}' },
468
- { label: 'Integral', value: '\\int_0^1 f(x) \\, \\mathrm{d}x' },
469
- { label: 'Bayes', value: '\\mathbb{P}(A \\mid B)' },
470
- { label: 'Chemical H₂O', value: '\\ce{H2O}' },
471
- { label: 'Chemical CO₂', value: '\\ce{CO2}' }
472
- ];
473
-
474
- symbols.forEach((symbol) => {
475
- const button = document.createElement('button');
476
- button.type = 'button';
477
- button.className = 'math-tooling__symbol';
478
- button.setAttribute('data-symbol-insert', symbol.value);
479
- button.textContent = symbol.label;
480
- symbolList.appendChild(button);
481
- });
482
-
483
- const closeButton = document.createElement('button');
484
- closeButton.type = 'button';
485
- closeButton.className = 'math-tooling__close';
486
- closeButton.setAttribute('data-math-editor-close', 'true');
487
- const closeIcon = document.createElement('span');
488
- closeIcon.setAttribute('aria-hidden', 'true');
489
- closeIcon.textContent = '✕';
490
- const closeSr = document.createElement('span');
491
- closeSr.className = 'visually-hidden';
492
- closeSr.textContent = 'Close equation editor';
493
- closeButton.appendChild(closeIcon);
494
- closeButton.appendChild(closeSr);
495
-
496
- const copyButton = document.createElement('button');
497
- copyButton.type = 'button';
498
- copyButton.className = 'math-tooling__copy';
499
- copyButton.setAttribute('data-math-editor-copy', 'true');
500
- copyButton.textContent = 'Copy LaTeX';
501
-
502
- actions.appendChild(copyButton);
503
- actions.appendChild(closeButton);
504
- actions.appendChild(status);
505
-
506
- library.appendChild(libraryTitle);
507
- library.appendChild(symbolList);
508
-
509
- dialog.appendChild(title);
510
- dialog.appendChild(helper);
511
- dialog.appendChild(textarea);
512
- dialog.appendChild(preview);
513
- dialog.appendChild(actions);
514
- dialog.appendChild(library);
515
-
516
- overlay.appendChild(dialog);
517
- document.body.appendChild(overlay);
518
-
519
- textarea.addEventListener('input', () => this.updateEditorPreview());
520
-
521
- this.editor = {
522
- overlay,
523
- dialog,
524
- textarea,
525
- preview,
526
- status
527
- };
528
- },
529
-
530
- openEditor(latex) {
531
- if (!this.editor) {
532
- return;
533
- }
534
-
535
- const value = (latex || '').trim();
536
- this.editor.overlay.hidden = false;
537
- document.body.classList.add('math-tooling-open');
538
- this.editor.textarea.value = value;
539
- this.editor.textarea.focus();
540
- this.editor.textarea.setSelectionRange(value.length, value.length);
541
- this.editorOpen = true;
542
- this.updateEditorPreview();
543
- },
544
-
545
- closeEditor() {
546
- if (!this.editor) {
547
- return;
548
- }
549
- this.editor.overlay.hidden = true;
550
- document.body.classList.remove('math-tooling-open');
551
- this.editorOpen = false;
552
- },
553
-
554
- updateEditorPreview() {
555
- if (!this.editor || !this.MathJax) {
556
- return;
557
- }
558
- const latex = this.editor.textarea.value.trim();
559
- if (!latex) {
560
- this.editor.preview.replaceChildren();
561
- const placeholder = document.createElement('p');
562
- placeholder.className = 'math-tooling__placeholder';
563
- placeholder.textContent = 'Preview will appear here.';
564
- this.editor.preview.appendChild(placeholder);
565
- return;
566
- }
567
- this.renderLatex(this.editor.preview, latex, { display: true, enhance: false });
568
- },
569
-
570
- insertSymbol(value) {
571
- if (!this.editor || !this.editor.textarea) {
572
- return;
573
- }
574
- const textarea = this.editor.textarea;
575
- const start = textarea.selectionStart || 0;
576
- const end = textarea.selectionEnd || 0;
577
- const current = textarea.value;
578
- const next = `${current.slice(0, start)}${value}${current.slice(end)}`;
579
- textarea.value = next;
580
- const caret = start + value.length;
581
- textarea.focus();
582
- textarea.setSelectionRange(caret, caret);
583
- this.updateEditorPreview();
584
- },
585
-
586
- updateEquationReferences() {
587
- if (!this.equationMap || this.equationMap.size === 0) {
588
- return;
589
- }
590
-
591
- const references = document.querySelectorAll('[data-equation-ref]');
592
- references.forEach((ref) => {
593
- const key = ref.getAttribute('data-equation-ref');
594
- if (!key) {
595
- return;
596
- }
597
- const lookup = this.lookupEquation(key.replace(/^#/, ''));
598
- if (!lookup) {
599
- return;
600
- }
601
- ref.setAttribute('href', `#${lookup.anchor}`);
602
- const text = ref.textContent.trim();
603
- if (!text || text === key || text === lookup.anchor || text === `(${key})`) {
604
- ref.textContent = lookup.number || lookup.anchor;
605
- }
606
- ref.classList.add('math-reference');
607
- ref.setAttribute('aria-label', `Jump to equation ${lookup.number || lookup.anchor}`);
608
- });
609
-
610
- const anchorRefs = document.querySelectorAll('a[href^="#eq:"]');
611
- anchorRefs.forEach((link) => {
612
- const href = link.getAttribute('href');
613
- if (!href) {
614
- return;
615
- }
616
- const key = href.replace(/^#/, '');
617
- const lookup = this.lookupEquation(key);
618
- if (!lookup) {
619
- return;
620
- }
621
- link.setAttribute('href', `#${lookup.anchor}`);
622
- const text = link.textContent.trim();
623
- if (!text || text === href || text === key) {
624
- link.textContent = lookup.number || lookup.anchor;
625
- }
626
- link.dataset.equationRef = lookup.anchor;
627
- link.classList.add('math-reference');
628
- });
629
- },
630
-
631
- lookupEquation(key) {
632
- if (!key) {
633
- return null;
634
- }
635
- if (this.equationMap.has(key)) {
636
- return this.equationMap.get(key);
637
- }
638
- const normalized = key.startsWith('eq:') ? key : `eq:${key}`;
639
- return this.equationMap.get(normalized) || null;
640
- },
641
-
642
- renderLatex(target, latex, options = {}) {
643
- if (!target) {
644
- return Promise.resolve();
645
- }
646
- const display = options.display !== false;
647
- if (!this.MathJax) {
648
- target.textContent = latex;
649
- if (!this.pendingTypeset) {
650
- this.pendingTypeset = [];
651
- }
652
- this.pendingTypeset.push({ target, latex, options });
653
- return Promise.resolve();
654
- }
655
- const wrapperLatex = display ? `\\[${latex}\\]` : `\\(${latex}\\)`;
656
- // innerHTML required for MathJax rendering
657
- target.innerHTML = wrapperLatex;
658
- return this.MathJax.typesetPromise([target]).catch((error) => {
659
- console.warn('MathJax failed to typeset preview', error);
660
- });
661
- },
662
-
663
- cleanLatex(latex) {
664
- return (latex || '')
665
- .replace(/%\s*alt:.*$/gm, '')
666
- .replace(/\\label\{[^}]*\}/g, '')
667
- .replace(/\\tag\*?\{[^}]*\}/g, '')
668
- .trim();
669
- },
670
-
671
- resolveAltText(wrapper, latex) {
672
- const sourceNode = wrapper || null;
673
- const datasetAlt = this.findDatasetAlt(sourceNode);
674
- if (datasetAlt) {
675
- return datasetAlt;
676
- }
677
-
678
- const commentAlt = this.extractAltComment(latex);
679
- if (commentAlt) {
680
- return commentAlt;
681
- }
682
-
683
- const sourceLatex = sourceNode && sourceNode.getAttribute ? sourceNode.getAttribute('data-math-source') : null;
684
- const targetLatex = sourceLatex || latex;
685
- return this.generateAltFromLatex(targetLatex);
686
- },
687
-
688
- findDatasetAlt(node) {
689
- let current = node;
690
- while (current) {
691
- if (current.dataset && current.dataset.mathAlt) {
692
- const alt = current.dataset.mathAlt.trim();
693
- if (alt) {
694
- return alt;
695
- }
696
- }
697
- current = current.parentElement;
698
- }
699
- return '';
700
- },
701
-
702
- extractAltComment(latex) {
703
- if (!latex) {
704
- return '';
705
- }
706
- const lines = String(latex).split(/\r?\n/);
707
- for (let i = 0; i < lines.length; i += 1) {
708
- const line = lines[i];
709
- if (!line) {
710
- continue;
711
- }
712
- const match = line.match(/%\s*alt:\s*(.+)$/i);
713
- if (match) {
714
- return match[1].trim();
715
- }
716
- }
717
- return '';
718
- },
719
-
720
- generateAltFromLatex(latex) {
721
- if (!latex) {
722
- return '';
723
- }
724
- let text = String(latex).replace(/%.*$/gm, '');
725
-
726
- text = text.replace(/\\frac\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g, (_match, numerator, denominator) => {
727
- const top = this.sanitizeSegment(numerator);
728
- const bottom = this.sanitizeSegment(denominator);
729
- return `${top} over ${bottom}`.trim();
730
- });
731
-
732
- text = text.replace(/\\int(?:_\{([^}]*)\}|_([^\s^{}]+))?(?:\^\{([^}]*)\}|\^([^\s_{}]+))?/g, (_match, lowerBraced, lowerSimple, upperBraced, upperSimple) => {
733
- const lower = this.sanitizeSegment(lowerBraced || lowerSimple);
734
- const upper = this.sanitizeSegment(upperBraced || upperSimple);
735
- let phrase = 'integral';
736
- if (lower) {
737
- phrase += ` from ${lower}`;
738
- }
739
- if (upper) {
740
- phrase += ` to ${upper}`;
741
- }
742
- return phrase;
743
- });
744
-
745
- text = text.replace(/\\sum(?:_\{([^}]*)\}|_([^\s^{}]+))?(?:\^\{([^}]*)\}|\^([^\s_{}]+))?/g, (_match, lowerBraced, lowerSimple, upperBraced, upperSimple) => {
746
- const lower = this.sanitizeSegment(lowerBraced || lowerSimple);
747
- const upper = this.sanitizeSegment(upperBraced || upperSimple);
748
- let phrase = 'summation';
749
- if (lower) {
750
- phrase += ` from ${lower}`;
751
- }
752
- if (upper) {
753
- phrase += ` to ${upper}`;
754
- }
755
- return phrase;
756
- });
757
-
758
- text = text.replace(/\\sqrt\s*\{([^{}]+)\}/g, (_match, radicand) => `square root of ${this.sanitizeSegment(radicand)}`);
759
- text = text.replace(/\\mathrm\s*\{([^{}]+)\}/g, (_match, content) => this.sanitizeSegment(content));
760
- text = text.replace(/\\operatorname\*?\s*\{([^{}]+)\}/g, (_match, content) => this.sanitizeSegment(content));
761
- text = text.replace(/\\[a-zA-Z]+/g, ' ');
762
- text = text.replace(/[{}]/g, ' ');
763
- text = text.replace(/\s+/g, ' ').trim();
764
-
765
- if (!text) {
766
- return 'Mathematical expression';
767
- }
768
- return text;
769
- },
770
-
771
- sanitizeSegment(segment) {
772
- if (!segment) {
773
- return '';
774
- }
775
- return String(segment)
776
- .replace(/\\[a-zA-Z]+/g, ' ')
777
- .replace(/[{}]/g, ' ')
778
- .replace(/\s+/g, ' ')
779
- .trim();
780
- },
781
-
782
- syncAltAttributes(wrapper, container, altText) {
783
- if (!altText) {
784
- return;
785
- }
786
- if (wrapper && wrapper.setAttribute) {
787
- wrapper.setAttribute('aria-label', altText);
788
- if (wrapper.dataset) {
789
- wrapper.dataset.mathAlt = altText;
790
- }
791
- }
792
- if (container && container.setAttribute) {
793
- container.setAttribute('aria-label', altText);
794
- if (container.dataset) {
795
- container.dataset.mathAlt = altText;
796
- }
797
- }
798
- }
799
- };
800
-
801
- if (typeof globalThis !== 'undefined') {
802
- globalThis.__DATALOG_MATH_INTERNALS__ = MathToolkit;
803
- }
804
-
805
- window.DatalogMath = {
806
- init(MathJax) {
807
- MathToolkit.init(MathJax);
808
- },
809
- onPageReady() {
810
- if (MathToolkit.initialized) {
811
- MathToolkit.onPageReady();
812
- }
813
- },
814
- renderLatex(target, latex, options) {
815
- return MathToolkit.renderLatex(target, latex, options);
816
- }
817
- };
818
- })();