@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 +2 -2
- package/src/__tests__/math.test.ts +33 -3
- package/src/main.ts +0 -1
- package/src/style.css +8 -5
- package/src/utils/math.ts +72 -24
- package/vite.config.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.2.
|
|
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
|
|
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('
|
|
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
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
|
|
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
|
|
166
|
-
.math-bold
|
|
167
|
-
.math-bold
|
|
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
|
|
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
|
|
61
|
-
|
|
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, '&')
|
|
@@ -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
|
-
|
|
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
|
}
|