datalog-theme 0.6.1 → 0.8.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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +175 -0
  3. data/CITATION.cff +2 -2
  4. data/README.md +26 -19
  5. data/_data/cdn-integrity.yml +0 -30
  6. data/_includes/analytics/dashboard.html +3 -1
  7. data/_includes/components/api-function.html +20 -1
  8. data/_includes/components/author-bio.html +2 -2
  9. data/_includes/components/enhanced-code-block.html +1 -1
  10. data/_includes/components/hero.html +19 -1
  11. data/_includes/csp-meta.html +115 -11
  12. data/_includes/footer.html +19 -20
  13. data/_includes/head.html +61 -29
  14. data/_includes/header/navigation.html +12 -15
  15. data/_includes/header.html +29 -20
  16. data/_includes/layouts/default/article.html +5 -1
  17. data/_includes/meta/math-config.html +27 -1
  18. data/_includes/meta/schema.html +5 -2
  19. data/_includes/meta/scripts-loader.html +16 -31
  20. data/_includes/post/related-posts.html +4 -7
  21. data/_includes/scripts.html +1 -9
  22. data/_includes/search/index-data.json +131 -0
  23. data/_includes/search/page.html +136 -0
  24. data/_layouts/dataset.html +1 -0
  25. data/_layouts/default.html +16 -8
  26. data/_layouts/home.html +6 -0
  27. data/_layouts/notebook.html +1 -0
  28. data/_layouts/package.html +1 -0
  29. data/_layouts/portfolio.html +1 -0
  30. data/_layouts/post.html +20 -5
  31. data/_layouts/project.html +2 -1
  32. data/_plugins/analytics_dashboard.rb +9 -3
  33. data/_plugins/config_validator.rb +12 -7
  34. data/_plugins/csp_generator.rb +18 -28
  35. data/_plugins/datalog_bibliography.rb +9 -7
  36. data/_plugins/datalog_comments.rb +8 -5
  37. data/_plugins/datalog_slides.rb +9 -8
  38. data/_plugins/i18n.rb +7 -8
  39. data/_plugins/image_optimizer.rb +17 -6
  40. data/_plugins/math_preprocessor.rb +36 -3
  41. data/_plugins/notebook_converter.rb +23 -43
  42. data/_plugins/plugin_loader.rb +3 -1
  43. data/_plugins/publications_generator.rb +8 -2
  44. data/_plugins/rouge_highlight_filter.rb +42 -0
  45. data/_plugins/search_code_blocks.rb +30 -0
  46. data/_plugins/search_normalizer.rb +15 -53
  47. data/_plugins/search_pages.rb +72 -0
  48. data/_sass/_academic-dashboard.scss +262 -0
  49. data/_sass/_base.scss +19 -1
  50. data/_sass/_components.scss +77 -1144
  51. data/_sass/_features.scss +17 -0
  52. data/_sass/_font-fallbacks.scss +43 -0
  53. data/_sass/_header.scss +10 -0
  54. data/_sass/_layout.scss +18 -12
  55. data/_sass/_mathematical.scss +1 -1
  56. data/_sass/_notebooks.scss +322 -0
  57. data/_sass/_open-science-badges.scss +56 -0
  58. data/_sass/{_phase1-enhancements.scss → _post-components.scss} +46 -1
  59. data/_sass/_search-page.scss +530 -0
  60. data/_sass/_search.scss +47 -1
  61. data/_sass/_syntax-highlighting.scss +212 -97
  62. data/_sass/_theme.scss +42 -18
  63. data/_sass/_typography.scss +14 -0
  64. data/_sass/_variables.scss +7 -4
  65. data/assets/css/main.scss +14 -0
  66. data/assets/img/hero-detail-640.webp +0 -0
  67. data/assets/img/hero-detail.webp +0 -0
  68. data/assets/img/social-card.png +0 -0
  69. data/assets/js/dist/academic.js +1 -0
  70. data/assets/js/dist/analytics-dashboard.js +1 -0
  71. data/assets/js/dist/chunks/chunk-225H5YXE.js +1 -0
  72. data/assets/js/dist/core.js +1 -0
  73. data/assets/js/dist/loader.js +1 -0
  74. data/assets/js/dist/math.js +1 -0
  75. data/assets/js/dist/notebook.js +1 -0
  76. data/assets/js/dist/search.js +1 -0
  77. data/assets/js/dist/visualizations.js +11 -0
  78. data/assets/js/loader.js +3 -1
  79. data/datalog-theme.gemspec +43 -22
  80. data/lib/datalog/cli.rb +53 -15
  81. data/lib/datalog/plugin_system/dependency_resolver.rb +0 -2
  82. data/lib/datalog/theme/package.rb +57 -0
  83. data/lib/datalog/theme/repository_checkout.rb +90 -0
  84. data/lib/datalog/theme/version.rb +1 -1
  85. data/lib/datalog/warning_filter.rb +5 -11
  86. data/lib/datalog-theme.rb +21 -0
  87. metadata +75 -146
  88. data/_data/academic.yml +0 -217
  89. data/_data/config/author.yml +0 -121
  90. data/_data/config/features.yml +0 -262
  91. data/_data/config/site.yml +0 -42
  92. data/_data/config/theme.yml +0 -181
  93. data/_data/datasets.yml +0 -28
  94. data/_data/js_meta.json +0 -371
  95. data/_data/navigation.yml +0 -145
  96. data/_data/projects.yml +0 -41
  97. data/_data/publications.yml +0 -28
  98. data/_data/social.yml +0 -73
  99. data/_data/visualizations.yml +0 -51
  100. data/_includes/components/advanced-search.html +0 -682
  101. data/_includes/components/bookmark-system.html +0 -96
  102. data/_includes/components/comments.html +0 -244
  103. data/_includes/components/content-recommendations.html +0 -228
  104. data/_includes/components/email-preferences.html +0 -200
  105. data/_includes/components/enhanced-metadata.html +0 -228
  106. data/_includes/components/language-switcher.html +0 -396
  107. data/_includes/components/navigation-enhancements.html +0 -454
  108. data/_includes/components/newsletter-signup.html +0 -178
  109. data/_includes/components/popular-posts.html +0 -233
  110. data/_includes/components/reading-progress.html +0 -133
  111. data/_includes/components/reading-time.html +0 -121
  112. data/_includes/components/series-navigation.html +0 -124
  113. data/_includes/components/social-proof.html +0 -34
  114. data/_includes/components/user-preferences.html +0 -566
  115. data/_layouts/archive.html +0 -282
  116. data/_layouts/post-sidebar.html +0 -183
  117. data/_sass/_phase3-enhancements.scss +0 -874
  118. data/_sass/_phase4-enhancements.scss +0 -1214
  119. data/_sass/_phase5-enhancements.scss +0 -414
  120. data/assets/img/20220607123041_detail.001.png +0 -0
  121. data/assets/js/academic.js +0 -262
  122. data/assets/js/analytics-dashboard.js +0 -382
  123. data/assets/js/core/dark-mode.js +0 -79
  124. data/assets/js/core/github-cards.js +0 -123
  125. data/assets/js/core/language-filter.js +0 -69
  126. data/assets/js/core/navigation.js +0 -184
  127. data/assets/js/core/scroll-progress.js +0 -45
  128. data/assets/js/core/search-hotkeys.js +0 -62
  129. data/assets/js/core/skip-links.js +0 -62
  130. data/assets/js/main.js +0 -23
  131. data/assets/js/math.js +0 -818
  132. data/assets/js/notebook.js +0 -158
  133. data/assets/js/search/analytics.js +0 -91
  134. data/assets/js/search/app.js +0 -271
  135. data/assets/js/search/autocomplete.js +0 -120
  136. data/assets/js/search/engine.js +0 -260
  137. data/assets/js/search/filters.js +0 -38
  138. data/assets/js/search/render.js +0 -217
  139. data/assets/js/search/utils.js +0 -99
  140. data/assets/js/search.js +0 -354
  141. data/assets/js/visualizations.js +0 -816
  142. data/assets/publications/datalog-publications.bib +0 -8
  143. data/assets/publications/datalog-publications.ris +0 -9
  144. data/assets/publications/publications.bib +0 -30
  145. data/assets/templates/diogo-ribeiro-cv.md +0 -31
  146. data/assets/templates/diogo-ribeiro-cv.tex +0 -32
  147. 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
- })();