@glossarist/concept-browser 0.2.10 → 0.2.12

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glossarist/concept-browser",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,12 +19,12 @@
19
19
  "test:watch": "vitest"
20
20
  },
21
21
  "dependencies": {
22
+ "@plurimath/plurimath": "^0.2.2",
22
23
  "@vitejs/plugin-vue": "^5.2.3",
23
24
  "autoprefixer": "^10.4.21",
24
25
  "d3": "^7.9.0",
25
26
  "glossarist": "^0.2.0",
26
27
  "js-yaml": "^4.1.0",
27
- "katex": "^0.16.45",
28
28
  "pinia": "^2.3.1",
29
29
  "postcss": "^8.5.3",
30
30
  "tailwindcss": "^3.4.17",
@@ -1,4 +1,22 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, it, expect, vi } from 'vitest';
2
+
3
+ // Mock @plurimath/plurimath for test environment
4
+ vi.mock('@plurimath/plurimath', () => {
5
+ return {
6
+ default: class MockPlurimath {
7
+ private data: string;
8
+ private format: string;
9
+ constructor(data: string, format: string) {
10
+ this.data = data;
11
+ this.format = format;
12
+ }
13
+ toMathml() {
14
+ return `<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>${this.data}</mi></math>`;
15
+ }
16
+ },
17
+ };
18
+ });
19
+
2
20
  import { renderMath, cleanContent } from '../utils/math';
3
21
 
4
22
  describe('renderMath', () => {
@@ -6,10 +24,10 @@ describe('renderMath', () => {
6
24
  expect(renderMath('hello world')).toBe('hello world');
7
25
  });
8
26
 
9
- it('renders stem:[x^2] to KaTeX span', () => {
27
+ it('renders stem:[x^2] to MathML span', () => {
10
28
  const result = renderMath('the value stem:[x^2]');
11
29
  expect(result).toContain('math-inline');
12
- expect(result).toContain('katex');
30
+ expect(result).toContain('<math');
13
31
  expect(result).not.toContain('math-bold');
14
32
  });
15
33
 
@@ -19,6 +37,14 @@ describe('renderMath', () => {
19
37
  expect(result).toContain('math-bold');
20
38
  });
21
39
 
40
+ it('renders latexmath:[...] with nested brackets', () => {
41
+ const result = renderMath('coords latexmath:[[u_0, u_1] \\leq 1.0] here');
42
+ expect(result).toContain('math-inline');
43
+ expect(result).toContain('<math');
44
+ expect(result).toContain('u_0');
45
+ expect(result).not.toContain('latexmath:');
46
+ });
47
+
22
48
  it('converts bullet lines to <ul><li>', () => {
23
49
  const result = renderMath('* first item\n\n* second item');
24
50
  expect(result).toContain('<ul class="concept-list">');
@@ -110,6 +136,10 @@ describe('cleanContent', () => {
110
136
  expect(cleanContent('value *stem:[x]* here')).toBe('value x here');
111
137
  });
112
138
 
139
+ it('strips latexmath:[...] with nested brackets', () => {
140
+ expect(cleanContent('coords latexmath:[[u_0, u_1] \\leq 1.0] end')).toBe('coords [u_0, u_1] \\leq 1.0 end');
141
+ });
142
+
113
143
  it('strips *text* to plain text', () => {
114
144
  expect(cleanContent('some *italic* text')).toBe('some italic text');
115
145
  });
package/src/main.ts CHANGED
@@ -2,7 +2,6 @@ import { createApp } from 'vue';
2
2
  import { createPinia } from 'pinia';
3
3
  import App from './App.vue';
4
4
  import router from './router';
5
- import 'katex/dist/katex.min.css';
6
5
  import './style.css';
7
6
 
8
7
  const app = createApp(App);
package/src/style.css CHANGED
@@ -158,13 +158,16 @@
158
158
  font-family: var(--font-body);
159
159
  }
160
160
 
161
- /* Math */
162
- .math-inline .katex {
161
+ /* Math (Plurimath MathML output) */
162
+ .math-inline {
163
+ display: inline;
164
+ }
165
+ .math-inline math {
163
166
  font-size: 1.05em;
164
167
  }
165
- .math-bold .katex .mord,
166
- .math-bold .katex .mbin,
167
- .math-bold .katex .mrel {
168
+ .math-bold math mi,
169
+ .math-bold math mn,
170
+ .math-bold math mo {
168
171
  font-weight: bold;
169
172
  }
170
173
  .math-fallback {
package/src/utils/math.ts CHANGED
@@ -1,4 +1,16 @@
1
- import katex from 'katex';
1
+ import Plurimath from '@plurimath/plurimath';
2
+
3
+ type MathFormat = 'asciimath' | 'latex' | 'mathml' | 'html' | 'mahtml' | 'omml';
4
+
5
+ function renderMathSpan(math: string, format: MathFormat, bold: boolean): string {
6
+ try {
7
+ const p = new Plurimath(math, format);
8
+ const mathml = p.toMathml();
9
+ return `<span class="math-inline${bold ? ' math-bold' : ''}">${mathml}</span>`;
10
+ } catch {
11
+ return `<code class="math-fallback">${escapeHtml(math)}</code>`;
12
+ }
13
+ }
2
14
 
3
15
  export type XrefResolver = (uri: string, term: string) => string;
4
16
  export type BibResolver = (refId: string, title: string) => string;
@@ -45,6 +57,59 @@ function convertLists(text: string): string {
45
57
  return result;
46
58
  }
47
59
 
60
+ /**
61
+ * Replace `prefix:[content]` where content may contain nested brackets.
62
+ * Handles `*prefix:[content]*` (bold) too.
63
+ */
64
+ function replaceBracketed(
65
+ text: string,
66
+ prefix: string,
67
+ render: (math: string, bold: boolean) => string,
68
+ ): string {
69
+ let result = '';
70
+ let i = 0;
71
+ const boldPrefix = '*' + prefix;
72
+ while (i < text.length) {
73
+ // Check for bold variant: *prefix:[...]
74
+ if (text.startsWith(boldPrefix + '[', i)) {
75
+ const start = i;
76
+ i += boldPrefix.length + 1; // skip *prefix:[
77
+ const depth = 1;
78
+ let j = i;
79
+ let d = 1;
80
+ while (j < text.length && d > 0) {
81
+ if (text[j] === '[') d++;
82
+ else if (text[j] === ']') d--;
83
+ j++;
84
+ }
85
+ const content = text.slice(i, j - 1);
86
+ // Check for closing *
87
+ let end = j;
88
+ if (end < text.length && text[end] === '*') end++;
89
+ result += render(content, true);
90
+ i = end;
91
+ }
92
+ // Check for normal variant: prefix:[...]
93
+ else if (text.startsWith(prefix + '[', i)) {
94
+ i += prefix.length + 1;
95
+ let j = i;
96
+ let d = 1;
97
+ while (j < text.length && d > 0) {
98
+ if (text[j] === '[') d++;
99
+ else if (text[j] === ']') d--;
100
+ j++;
101
+ }
102
+ const content = text.slice(i, j - 1);
103
+ result += render(content, false);
104
+ i = j;
105
+ } else {
106
+ result += text[i];
107
+ i++;
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+
48
113
  /**
49
114
  * Render stem:[...] math notation to KaTeX HTML.
50
115
  * Also handles cross-reference inline patterns (URN refs, bibliography, figures).
@@ -57,13 +122,8 @@ export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | Ren
57
122
  ? { xrefResolver: xrefResolverOrOpts }
58
123
  : (xrefResolverOrOpts ?? {});
59
124
 
60
- result = result.replace(/\*stem:\[([^\]]*)\]\*/g, (_, math) => {
61
- return renderKatexSpan(math, true);
62
- });
63
-
64
- result = result.replace(/stem:\[([^\]]*)\]/g, (_, math) => {
65
- return renderKatexSpan(math, false);
66
- });
125
+ result = replaceBracketed(result, 'stem:', (math, bold) => renderMathSpan(math, 'asciimath', bold));
126
+ result = replaceBracketed(result, 'latexmath:', (math, bold) => renderMathSpan(math, 'latex', bold));
67
127
 
68
128
  result = convertLists(result);
69
129
 
@@ -110,19 +170,6 @@ export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | Ren
110
170
  return result;
111
171
  }
112
172
 
113
- function renderKatexSpan(math: string, bold: boolean): string {
114
- try {
115
- const html = katex.renderToString(math, {
116
- throwOnError: false,
117
- displayMode: false,
118
- output: 'html',
119
- });
120
- return `<span class="math-inline${bold ? ' math-bold' : ''}">${html}</span>`;
121
- } catch {
122
- return `<code class="math-fallback">${escapeHtml(math)}</code>`;
123
- }
124
- }
125
-
126
173
  function escapeHtml(text: string): string {
127
174
  return text
128
175
  .replace(/&/g, '&amp;')
@@ -135,9 +182,7 @@ function escapeHtml(text: string): string {
135
182
  */
136
183
  export function cleanContent(text: string): string {
137
184
  if (!text) return '';
138
- return text
139
- .replace(/\*stem:\[([^\]]*)\]\*/g, '$1')
140
- .replace(/stem:\[([^\]]*)\]/g, '$1')
185
+ let result = text
141
186
  .replace(/\*([^*]+)\*/g, '$1')
142
187
  .replace(/~([^~]+)~/g, '_$1')
143
188
  .replace(/\n[ \t]*\* /g, '; ')
@@ -146,4 +191,7 @@ export function cleanContent(text: string): string {
146
191
  .replace(/\{\{urn:[^,}]+,([^,}]+)(?:,[^}]+)?\}\}/g, '$1')
147
192
  .replace(/\{urn:[^,}]+,([^,}]+)(?:,[^}]+)?\}/g, '$1')
148
193
  .replace(/\{\{([^,}]+)(?:,\s*[^}]+)?\}\}/g, '$1');
194
+ result = replaceBracketed(result, 'stem:', (math) => math);
195
+ result = replaceBracketed(result, 'latexmath:', (math) => math);
196
+ return result;
149
197
  }
package/vite.config.ts CHANGED
@@ -20,6 +20,9 @@ export default defineConfig({
20
20
  '@': resolve(__dirname, 'src'),
21
21
  },
22
22
  },
23
+ optimizeDeps: {
24
+ include: ['@plurimath/plurimath'],
25
+ },
23
26
  test: {
24
27
  environment: 'happy-dom',
25
28
  globals: true,