@glossarist/concept-browser 0.2.12 → 0.2.13
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 +4 -23
- package/src/main.ts +1 -0
- package/src/style.css +5 -8
- package/src/utils/math.ts +23 -49
- package/vite.config.ts +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
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",
|
|
23
22
|
"@vitejs/plugin-vue": "^5.2.3",
|
|
24
23
|
"autoprefixer": "^10.4.21",
|
|
25
24
|
"d3": "^7.9.0",
|
|
26
25
|
"glossarist": "^0.2.0",
|
|
27
26
|
"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,22 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
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
|
-
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
20
2
|
import { renderMath, cleanContent } from '../utils/math';
|
|
21
3
|
|
|
22
4
|
describe('renderMath', () => {
|
|
@@ -24,10 +6,10 @@ describe('renderMath', () => {
|
|
|
24
6
|
expect(renderMath('hello world')).toBe('hello world');
|
|
25
7
|
});
|
|
26
8
|
|
|
27
|
-
it('renders stem:[x^2] to
|
|
9
|
+
it('renders stem:[x^2] to KaTeX span', () => {
|
|
28
10
|
const result = renderMath('the value stem:[x^2]');
|
|
29
11
|
expect(result).toContain('math-inline');
|
|
30
|
-
expect(result).toContain('
|
|
12
|
+
expect(result).toContain('katex');
|
|
31
13
|
expect(result).not.toContain('math-bold');
|
|
32
14
|
});
|
|
33
15
|
|
|
@@ -40,8 +22,7 @@ describe('renderMath', () => {
|
|
|
40
22
|
it('renders latexmath:[...] with nested brackets', () => {
|
|
41
23
|
const result = renderMath('coords latexmath:[[u_0, u_1] \\leq 1.0] here');
|
|
42
24
|
expect(result).toContain('math-inline');
|
|
43
|
-
expect(result).toContain('
|
|
44
|
-
expect(result).toContain('u_0');
|
|
25
|
+
expect(result).toContain('katex');
|
|
45
26
|
expect(result).not.toContain('latexmath:');
|
|
46
27
|
});
|
|
47
28
|
|
package/src/main.ts
CHANGED
package/src/style.css
CHANGED
|
@@ -158,16 +158,13 @@
|
|
|
158
158
|
font-family: var(--font-body);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/* Math
|
|
162
|
-
.math-inline {
|
|
163
|
-
display: inline;
|
|
164
|
-
}
|
|
165
|
-
.math-inline math {
|
|
161
|
+
/* Math */
|
|
162
|
+
.math-inline .katex {
|
|
166
163
|
font-size: 1.05em;
|
|
167
164
|
}
|
|
168
|
-
.math-bold
|
|
169
|
-
.math-bold
|
|
170
|
-
.math-bold
|
|
165
|
+
.math-bold .katex .mord,
|
|
166
|
+
.math-bold .katex .mbin,
|
|
167
|
+
.math-bold .katex .mrel {
|
|
171
168
|
font-weight: bold;
|
|
172
169
|
}
|
|
173
170
|
.math-fallback {
|
package/src/utils/math.ts
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
-
}
|
|
1
|
+
import katex from 'katex';
|
|
14
2
|
|
|
15
3
|
export type XrefResolver = (uri: string, term: string) => string;
|
|
16
4
|
export type BibResolver = (refId: string, title: string) => string;
|
|
@@ -22,12 +10,7 @@ export interface RenderOptions {
|
|
|
22
10
|
figResolver?: FigResolver;
|
|
23
11
|
}
|
|
24
12
|
|
|
25
|
-
/**
|
|
26
|
-
* Convert `* item` lines into <ul><li> blocks.
|
|
27
|
-
* Also handles `1)` and `1.` numbered items into ordered lists.
|
|
28
|
-
*/
|
|
29
13
|
function convertLists(text: string): string {
|
|
30
|
-
// Unordered: * item (separated by \n or \n\n)
|
|
31
14
|
let result = text.replace(/(?:^|\n)((?:[ \t]*\* [^\n]+)(?:\n[ \t]*\* [^\n]+)*)/g, (_, block) => {
|
|
32
15
|
if (/^\*stem:\[/.test(block.trimStart())) return _;
|
|
33
16
|
const items: string[] = [];
|
|
@@ -41,7 +24,6 @@ function convertLists(text: string): string {
|
|
|
41
24
|
return `\n<ul class="concept-list">${lis}</ul>`;
|
|
42
25
|
});
|
|
43
26
|
|
|
44
|
-
// Ordered: 1) item or 1. item (numbered items)
|
|
45
27
|
result = result.replace(/(?:^|\n)((?:[ \t]*\d+[).][ \t]+[^\n]+)(?:\n[ \t]*\d+[).][ \t]+[^\n]+)*)/g, (_, block) => {
|
|
46
28
|
const items: string[] = [];
|
|
47
29
|
const re = /[ \t]*\d+[).][ \t]+([^\n]+)/g;
|
|
@@ -57,10 +39,6 @@ function convertLists(text: string): string {
|
|
|
57
39
|
return result;
|
|
58
40
|
}
|
|
59
41
|
|
|
60
|
-
/**
|
|
61
|
-
* Replace `prefix:[content]` where content may contain nested brackets.
|
|
62
|
-
* Handles `*prefix:[content]*` (bold) too.
|
|
63
|
-
*/
|
|
64
42
|
function replaceBracketed(
|
|
65
43
|
text: string,
|
|
66
44
|
prefix: string,
|
|
@@ -70,11 +48,8 @@ function replaceBracketed(
|
|
|
70
48
|
let i = 0;
|
|
71
49
|
const boldPrefix = '*' + prefix;
|
|
72
50
|
while (i < text.length) {
|
|
73
|
-
// Check for bold variant: *prefix:[...]
|
|
74
51
|
if (text.startsWith(boldPrefix + '[', i)) {
|
|
75
|
-
|
|
76
|
-
i += boldPrefix.length + 1; // skip *prefix:[
|
|
77
|
-
const depth = 1;
|
|
52
|
+
i += boldPrefix.length + 1;
|
|
78
53
|
let j = i;
|
|
79
54
|
let d = 1;
|
|
80
55
|
while (j < text.length && d > 0) {
|
|
@@ -83,13 +58,11 @@ function replaceBracketed(
|
|
|
83
58
|
j++;
|
|
84
59
|
}
|
|
85
60
|
const content = text.slice(i, j - 1);
|
|
86
|
-
// Check for closing *
|
|
87
61
|
let end = j;
|
|
88
62
|
if (end < text.length && text[end] === '*') end++;
|
|
89
63
|
result += render(content, true);
|
|
90
64
|
i = end;
|
|
91
65
|
}
|
|
92
|
-
// Check for normal variant: prefix:[...]
|
|
93
66
|
else if (text.startsWith(prefix + '[', i)) {
|
|
94
67
|
i += prefix.length + 1;
|
|
95
68
|
let j = i;
|
|
@@ -110,10 +83,6 @@ function replaceBracketed(
|
|
|
110
83
|
return result;
|
|
111
84
|
}
|
|
112
85
|
|
|
113
|
-
/**
|
|
114
|
-
* Render stem:[...] math notation to KaTeX HTML.
|
|
115
|
-
* Also handles cross-reference inline patterns (URN refs, bibliography, figures).
|
|
116
|
-
*/
|
|
117
86
|
export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | RenderOptions): string {
|
|
118
87
|
if (!text) return '';
|
|
119
88
|
let result = text;
|
|
@@ -122,15 +91,14 @@ export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | Ren
|
|
|
122
91
|
? { xrefResolver: xrefResolverOrOpts }
|
|
123
92
|
: (xrefResolverOrOpts ?? {});
|
|
124
93
|
|
|
125
|
-
result = replaceBracketed(result, 'stem:',
|
|
126
|
-
result = replaceBracketed(result, 'latexmath:',
|
|
94
|
+
result = replaceBracketed(result, 'stem:', renderKatexSpan);
|
|
95
|
+
result = replaceBracketed(result, 'latexmath:', renderKatexSpan);
|
|
127
96
|
|
|
128
97
|
result = convertLists(result);
|
|
129
98
|
|
|
130
99
|
result = result.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
131
100
|
result = result.replace(/~([^~]+)~/g, '<sub>$1</sub>');
|
|
132
101
|
|
|
133
|
-
// Handle AsciiDoc bibliography xrefs: <<ref_XX,title>>
|
|
134
102
|
result = result.replace(/<<([^,>]+),([^>]+)>>/g, (_, refId, title) => {
|
|
135
103
|
if (opts.bibResolver) {
|
|
136
104
|
return opts.bibResolver(refId.trim(), title.trim());
|
|
@@ -138,7 +106,6 @@ export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | Ren
|
|
|
138
106
|
return `<span class="bib-ref">${escapeHtml(title.trim())}</span>`;
|
|
139
107
|
});
|
|
140
108
|
|
|
141
|
-
// Handle AsciiDoc figure xrefs: <<fig_XX>>
|
|
142
109
|
result = result.replace(/<<(fig_[^>]+)>>/g, (_, figId) => {
|
|
143
110
|
if (opts.figResolver) {
|
|
144
111
|
return opts.figResolver(figId.trim());
|
|
@@ -146,30 +113,40 @@ export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | Ren
|
|
|
146
113
|
return `<span class="fig-ref">${escapeHtml(figId.trim())}</span>`;
|
|
147
114
|
});
|
|
148
115
|
|
|
149
|
-
// Handle URN inline refs: {{urn:...,term[,displayText]}} (double-braced)
|
|
150
116
|
result = result.replace(/\{\{(urn:[^,}]+),([^,}]+)(?:,([^}]+))?\}\}/g, (_, uri, term, display) => {
|
|
151
|
-
const
|
|
117
|
+
const t = (display || term).trim();
|
|
152
118
|
if (opts.xrefResolver) {
|
|
153
|
-
return opts.xrefResolver(uri,
|
|
119
|
+
return opts.xrefResolver(uri, t);
|
|
154
120
|
}
|
|
155
|
-
return
|
|
121
|
+
return t;
|
|
156
122
|
});
|
|
157
123
|
|
|
158
|
-
// Handle URN inline refs: {urn:...,term[,displayText]} (single-braced)
|
|
159
124
|
result = result.replace(/\{(urn:[^,}]+),([^,}]+)(?:,([^}]+))?\}/g, (_, uri, term, display) => {
|
|
160
|
-
const
|
|
125
|
+
const t = (display || term).trim();
|
|
161
126
|
if (opts.xrefResolver) {
|
|
162
|
-
return opts.xrefResolver(uri,
|
|
127
|
+
return opts.xrefResolver(uri, t);
|
|
163
128
|
}
|
|
164
|
-
return
|
|
129
|
+
return t;
|
|
165
130
|
});
|
|
166
131
|
|
|
167
|
-
// Handle any remaining {{...}} refs (fallback: show term before comma)
|
|
168
132
|
result = result.replace(/\{\{([^,}]+)(?:,\s*[^}]+)?\}\}/g, '$1');
|
|
169
133
|
|
|
170
134
|
return result;
|
|
171
135
|
}
|
|
172
136
|
|
|
137
|
+
function renderKatexSpan(math: string, bold: boolean): string {
|
|
138
|
+
try {
|
|
139
|
+
const html = katex.renderToString(math, {
|
|
140
|
+
throwOnError: false,
|
|
141
|
+
displayMode: false,
|
|
142
|
+
output: 'html',
|
|
143
|
+
});
|
|
144
|
+
return `<span class="math-inline${bold ? ' math-bold' : ''}">${html}</span>`;
|
|
145
|
+
} catch {
|
|
146
|
+
return `<code class="math-fallback">${escapeHtml(math)}</code>`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
173
150
|
function escapeHtml(text: string): string {
|
|
174
151
|
return text
|
|
175
152
|
.replace(/&/g, '&')
|
|
@@ -177,9 +154,6 @@ function escapeHtml(text: string): string {
|
|
|
177
154
|
.replace(/>/g, '>');
|
|
178
155
|
}
|
|
179
156
|
|
|
180
|
-
/**
|
|
181
|
-
* Clean content for plain text display (no math rendering).
|
|
182
|
-
*/
|
|
183
157
|
export function cleanContent(text: string): string {
|
|
184
158
|
if (!text) return '';
|
|
185
159
|
let result = text
|