datalog-theme 0.6.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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +162 -0
  3. data/CITATION.cff +29 -0
  4. data/LICENSE +21 -0
  5. data/README.md +257 -0
  6. data/_data/academic.yml +217 -0
  7. data/_data/cdn-integrity.yml +51 -0
  8. data/_data/config/author.yml +121 -0
  9. data/_data/config/features.yml +262 -0
  10. data/_data/config/site.yml +42 -0
  11. data/_data/config/theme.yml +181 -0
  12. data/_data/datasets.yml +28 -0
  13. data/_data/i18n/en.yml +438 -0
  14. data/_data/i18n/es.yml +438 -0
  15. data/_data/i18n/pt.yml +438 -0
  16. data/_data/js_manifest.json +22 -0
  17. data/_data/js_meta.json +371 -0
  18. data/_data/navigation.yml +145 -0
  19. data/_data/projects.yml +41 -0
  20. data/_data/publications.yml +28 -0
  21. data/_data/social.yml +73 -0
  22. data/_data/visualizations.yml +51 -0
  23. data/_includes/analytics/dashboard.html +149 -0
  24. data/_includes/analytics.html +9 -0
  25. data/_includes/components/academic-dashboard.html +347 -0
  26. data/_includes/components/advanced-search.html +682 -0
  27. data/_includes/components/api-function.html +94 -0
  28. data/_includes/components/author-bio.html +166 -0
  29. data/_includes/components/bookmark-system.html +96 -0
  30. data/_includes/components/breadcrumbs.html +89 -0
  31. data/_includes/components/citation-tools.html +94 -0
  32. data/_includes/components/comments.html +244 -0
  33. data/_includes/components/content-provenance.html +43 -0
  34. data/_includes/components/content-recommendations.html +228 -0
  35. data/_includes/components/difficulty-badge.html +84 -0
  36. data/_includes/components/email-preferences.html +200 -0
  37. data/_includes/components/enhanced-code-block.html +212 -0
  38. data/_includes/components/enhanced-metadata.html +228 -0
  39. data/_includes/components/enhanced-toc.html +209 -0
  40. data/_includes/components/hero.html +15 -0
  41. data/_includes/components/language-switcher.html +396 -0
  42. data/_includes/components/math-fallback.html +19 -0
  43. data/_includes/components/navigation-enhancements.html +454 -0
  44. data/_includes/components/newsletter-signup.html +178 -0
  45. data/_includes/components/open-science-badges.html +16 -0
  46. data/_includes/components/package-install.html +194 -0
  47. data/_includes/components/performance-monitor.html +170 -0
  48. data/_includes/components/popular-posts.html +233 -0
  49. data/_includes/components/post-hero.html +62 -0
  50. data/_includes/components/reading-progress.html +133 -0
  51. data/_includes/components/reading-time.html +121 -0
  52. data/_includes/components/responsive-image.html +63 -0
  53. data/_includes/components/search-facets.html +307 -0
  54. data/_includes/components/series-navigation.html +124 -0
  55. data/_includes/components/social-proof.html +34 -0
  56. data/_includes/components/social-share.html +119 -0
  57. data/_includes/components/user-preferences.html +566 -0
  58. data/_includes/components/visualization-card.html +24 -0
  59. data/_includes/components/viz-table-fallback.html +19 -0
  60. data/_includes/critical-css/default.html +0 -0
  61. data/_includes/critical-css/home.html +0 -0
  62. data/_includes/critical-css/post.html +0 -0
  63. data/_includes/csp-meta.html +17 -0
  64. data/_includes/footer/nav-column.html +31 -0
  65. data/_includes/footer.html +76 -0
  66. data/_includes/head.html +337 -0
  67. data/_includes/header/navigation.html +48 -0
  68. data/_includes/header.html +70 -0
  69. data/_includes/helpers/array-to-sentence.html +21 -0
  70. data/_includes/helpers/date-format.html +16 -0
  71. data/_includes/helpers/link-with-icon.html +26 -0
  72. data/_includes/layouts/default/article.html +67 -0
  73. data/_includes/meta/language-attributes.html +25 -0
  74. data/_includes/meta/math-config.html +21 -0
  75. data/_includes/meta/schema.html +153 -0
  76. data/_includes/meta/scripts-loader.html +52 -0
  77. data/_includes/post/related-posts.html +63 -0
  78. data/_includes/scripts.html +72 -0
  79. data/_includes/skip-link.html +1 -0
  80. data/_includes/toc.html +2 -0
  81. data/_layouts/archive.html +282 -0
  82. data/_layouts/dataset.html +52 -0
  83. data/_layouts/default.html +33 -0
  84. data/_layouts/home.html +48 -0
  85. data/_layouts/notebook.html +173 -0
  86. data/_layouts/package.html +238 -0
  87. data/_layouts/page.html +16 -0
  88. data/_layouts/portfolio.html +41 -0
  89. data/_layouts/post-sidebar.html +183 -0
  90. data/_layouts/post.html +338 -0
  91. data/_layouts/project.html +252 -0
  92. data/_layouts/research.html +504 -0
  93. data/_plugins/analytics_dashboard.rb +315 -0
  94. data/_plugins/config_validator.rb +397 -0
  95. data/_plugins/csp_generator.rb +115 -0
  96. data/_plugins/datalog_bibliography.rb +19 -0
  97. data/_plugins/datalog_comments.rb +18 -0
  98. data/_plugins/datalog_slides.rb +20 -0
  99. data/_plugins/front_matter_compat.rb +109 -0
  100. data/_plugins/i18n.rb +160 -0
  101. data/_plugins/image_optimizer.rb +445 -0
  102. data/_plugins/math_preprocessor.rb +185 -0
  103. data/_plugins/notebook_converter.rb +1107 -0
  104. data/_plugins/plugin_loader.rb +185 -0
  105. data/_plugins/publications_generator.rb +234 -0
  106. data/_plugins/reading_time.rb +15 -0
  107. data/_plugins/search_normalizer.rb +74 -0
  108. data/_plugins/sri_filter.rb +18 -0
  109. data/_plugins/toc_filter.rb +68 -0
  110. data/_plugins/warning_filter.rb +3 -0
  111. data/_sass/_base.scss +142 -0
  112. data/_sass/_components.scss +2380 -0
  113. data/_sass/_header.scss +470 -0
  114. data/_sass/_interactive.scss +344 -0
  115. data/_sass/_layout.scss +1760 -0
  116. data/_sass/_mathematical.scss +368 -0
  117. data/_sass/_mixins.scss +99 -0
  118. data/_sass/_package-docs.scss +715 -0
  119. data/_sass/_phase1-enhancements.scss +721 -0
  120. data/_sass/_phase3-enhancements.scss +874 -0
  121. data/_sass/_phase4-enhancements.scss +1214 -0
  122. data/_sass/_phase5-enhancements.scss +414 -0
  123. data/_sass/_search.scss +654 -0
  124. data/_sass/_syntax-highlighting.scss +128 -0
  125. data/_sass/_theme.scss +79 -0
  126. data/_sass/_typography.scss +148 -0
  127. data/_sass/_utilities.scss +334 -0
  128. data/_sass/_variables.scss +153 -0
  129. data/_sass/_visualizations.scss +242 -0
  130. data/assets/css/main.scss +4 -0
  131. data/assets/img/20220607123041_detail.001.png +0 -0
  132. data/assets/img/favicons/android-chrome-192x192.png +0 -0
  133. data/assets/img/favicons/android-chrome-512x512.png +0 -0
  134. data/assets/img/favicons/apple-touch-icon.png +0 -0
  135. data/assets/img/favicons/favicon-16x16.png +0 -0
  136. data/assets/img/favicons/favicon-32x32.png +0 -0
  137. data/assets/img/favicons/favicon.ico +0 -0
  138. data/assets/img/favicons/site.webmanifest +21 -0
  139. data/assets/img/portfolio-placeholder.svg +20 -0
  140. data/assets/js/academic.js +262 -0
  141. data/assets/js/analytics-dashboard.js +382 -0
  142. data/assets/js/core/dark-mode.js +79 -0
  143. data/assets/js/core/github-cards.js +123 -0
  144. data/assets/js/core/language-filter.js +69 -0
  145. data/assets/js/core/navigation.js +184 -0
  146. data/assets/js/core/scroll-progress.js +45 -0
  147. data/assets/js/core/search-hotkeys.js +62 -0
  148. data/assets/js/core/skip-links.js +62 -0
  149. data/assets/js/loader.js +163 -0
  150. data/assets/js/main.js +23 -0
  151. data/assets/js/math.js +818 -0
  152. data/assets/js/notebook.js +158 -0
  153. data/assets/js/search/analytics.js +91 -0
  154. data/assets/js/search/app.js +271 -0
  155. data/assets/js/search/autocomplete.js +120 -0
  156. data/assets/js/search/engine.js +260 -0
  157. data/assets/js/search/filters.js +38 -0
  158. data/assets/js/search/render.js +217 -0
  159. data/assets/js/search/utils.js +99 -0
  160. data/assets/js/search.js +354 -0
  161. data/assets/js/visualizations.js +816 -0
  162. data/assets/publications/datalog-publications.bib +8 -0
  163. data/assets/publications/datalog-publications.ris +9 -0
  164. data/assets/publications/publications.bib +30 -0
  165. data/assets/templates/diogo-ribeiro-cv.md +31 -0
  166. data/assets/templates/diogo-ribeiro-cv.tex +32 -0
  167. data/bin/datalog +26 -0
  168. data/datalog-theme.gemspec +72 -0
  169. data/lib/datalog/cli.rb +563 -0
  170. data/lib/datalog/plugin_system/dependency_resolver.rb +223 -0
  171. data/lib/datalog/plugin_system.rb +237 -0
  172. data/lib/datalog/plugins/citations.rb +182 -0
  173. data/lib/datalog/plugins/comments.rb +183 -0
  174. data/lib/datalog/plugins/search.rb +45 -0
  175. data/lib/datalog/plugins/slides.rb +121 -0
  176. data/lib/datalog/theme/theme.rb +18 -0
  177. data/lib/datalog/theme/version.rb +7 -0
  178. data/lib/datalog/warning_filter.rb +28 -0
  179. data/lib/datalog-theme.rb +17 -0
  180. metadata +555 -0
data/assets/js/math.js ADDED
@@ -0,0 +1,818 @@
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
+ })();