@glossarist/concept-browser 0.2.13 → 0.3.1

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.13",
3
+ "version": "0.3.1",
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",
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import yaml from 'js-yaml';
4
4
  import { naturalSort } from 'glossarist';
5
5
  import { loadSiteConfig } from './load-site-config.mjs';
6
+ import { preRenderMath } from './math-prerender.mjs';
6
7
 
7
8
  const __dirname = path.dirname(new URL(import.meta.url).pathname);
8
9
  const ROOT = process.cwd();
@@ -55,7 +56,7 @@ function termToDesignation(term) {
55
56
  : term.type === 'abbreviation' ? 'gl:Abbreviation'
56
57
  : 'gl:Designation',
57
58
  'gl:normativeStatus': term.normative_status || 'preferred',
58
- 'gl:term': term.designation,
59
+ 'gl:term': preRenderMath(term.designation),
59
60
  };
60
61
  if (term.gender) doc['gl:gender'] = term.gender;
61
62
  if (term.plurality) doc['gl:plurality'] = term.plurality;
@@ -68,7 +69,7 @@ function defsToJsonLd(defs) {
68
69
  return defs
69
70
  .map(d => ({
70
71
  '@type': 'gl:DetailedDefinition',
71
- 'gl:content': d.content || '',
72
+ 'gl:content': preRenderMath(d.content || ''),
72
73
  }))
73
74
  .filter(d => d['gl:content']);
74
75
  }
@@ -255,7 +256,7 @@ function getPrimaryDesignation(conceptYaml) {
255
256
  if (lc && lc.terms && lc.terms.length > 0) {
256
257
  const preferredExpr = lc.terms.find(t => t.normative_status === 'preferred' && t.type === 'expression');
257
258
  const preferred = preferredExpr || lc.terms.find(t => t.normative_status === 'preferred') || lc.terms[0];
258
- descs[lang] = preferred.designation;
259
+ descs[lang] = preRenderMath(preferred.designation);
259
260
  }
260
261
  }
261
262
  return descs;
@@ -531,11 +532,17 @@ function processDataset(dir, register, opts) {
531
532
  status: c.status,
532
533
  }));
533
534
 
535
+ // Strip HTML from index summary for text display
536
+ const plainSummary = summary.map(c => ({
537
+ ...c,
538
+ eng: c.eng.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(),
539
+ }));
540
+
534
541
  const graphNodeEntries = concepts.map(c => {
535
542
  let term = '', lang = '';
536
543
  if (c.designations.eng) { term = c.designations.eng; lang = 'eng'; }
537
544
  else { for (const [l, t] of Object.entries(c.designations)) { if (t) { term = t; lang = l; break; } } }
538
- return [c.id, term, lang, c.status];
545
+ return [c.id, term.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(), lang, c.status];
539
546
  });
540
547
  fs.mkdirSync(path.join(DATA, register), { recursive: true });
541
548
  fs.writeFileSync(
@@ -553,7 +560,7 @@ function processDataset(dir, register, opts) {
553
560
  conceptCount: concepts.length,
554
561
  chunkSize: CHUNK_SIZE,
555
562
  chunks,
556
- concepts: summary,
563
+ concepts: plainSummary,
557
564
  });
558
565
 
559
566
  writeJson(path.join(DATA, register, 'index-meta.json'), {
@@ -0,0 +1,80 @@
1
+ import Plurimath from '@plurimath/plurimath';
2
+
3
+ function renderToMathML(math, format) {
4
+ try {
5
+ const p = new Plurimath(math, format);
6
+ return p.toMathml().replace('display="block"', 'display="inline"').trim();
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+
12
+ function escapeHtml(text) {
13
+ return text
14
+ .replace(/&/g, '&amp;')
15
+ .replace(/</g, '&lt;')
16
+ .replace(/>/g, '&gt;');
17
+ }
18
+
19
+ function replaceBracketed(text, prefix, render) {
20
+ let result = '';
21
+ let i = 0;
22
+ const boldPrefix = '*' + prefix;
23
+ while (i < text.length) {
24
+ if (text.startsWith(boldPrefix + '[', i)) {
25
+ i += boldPrefix.length + 1;
26
+ let j = i;
27
+ let d = 1;
28
+ while (j < text.length && d > 0) {
29
+ if (text[j] === '[') d++;
30
+ else if (text[j] === ']') d--;
31
+ j++;
32
+ }
33
+ const content = text.slice(i, j - 1);
34
+ let end = j;
35
+ if (end < text.length && text[end] === '*') end++;
36
+ result += render(content, true);
37
+ i = end;
38
+ } else if (text.startsWith(prefix + '[', i)) {
39
+ i += prefix.length + 1;
40
+ let j = i;
41
+ let d = 1;
42
+ while (j < text.length && d > 0) {
43
+ if (text[j] === '[') d++;
44
+ else if (text[j] === ']') d--;
45
+ j++;
46
+ }
47
+ const content = text.slice(i, j - 1);
48
+ result += render(content, false);
49
+ i = j;
50
+ } else {
51
+ result += text[i];
52
+ i++;
53
+ }
54
+ }
55
+ return result;
56
+ }
57
+
58
+ function renderStem(math, bold) {
59
+ const mathml = renderToMathML(math, 'asciimath');
60
+ if (mathml) {
61
+ return `<span class="math-inline${bold ? ' math-bold' : ''}">${mathml}</span>`;
62
+ }
63
+ return `<code class="math-fallback">${escapeHtml(math)}</code>`;
64
+ }
65
+
66
+ function renderLatexmath(math, bold) {
67
+ const mathml = renderToMathML(math, 'latex');
68
+ if (mathml) {
69
+ return `<span class="math-inline${bold ? ' math-bold' : ''}">${mathml}</span>`;
70
+ }
71
+ return `<code class="math-fallback">${escapeHtml(math)}</code>`;
72
+ }
73
+
74
+ export function preRenderMath(text) {
75
+ if (!text) return '';
76
+ let result = text;
77
+ result = replaceBracketed(result, 'stem:', renderStem);
78
+ result = replaceBracketed(result, 'latexmath:', renderLatexmath);
79
+ return result;
80
+ }
@@ -6,24 +6,24 @@ describe('renderMath', () => {
6
6
  expect(renderMath('hello world')).toBe('hello world');
7
7
  });
8
8
 
9
- it('renders stem:[x^2] to KaTeX span', () => {
10
- const result = renderMath('the value stem:[x^2]');
11
- expect(result).toContain('math-inline');
12
- expect(result).toContain('katex');
13
- expect(result).not.toContain('math-bold');
9
+ it('passes through pre-rendered MathML unchanged', () => {
10
+ const preRendered = 'value <span class="math-inline"><math><mi>x</mi></math></span> here';
11
+ expect(renderMath(preRendered)).toBe(preRendered);
14
12
  });
15
13
 
16
- it('renders *stem:[x]* (bold math) with math-bold class', () => {
17
- const result = renderMath('the value *stem:[x]*');
18
- expect(result).toContain('math-inline');
19
- expect(result).toContain('math-bold');
14
+ it('still converts italic in mixed pre-rendered MathML content', () => {
15
+ const preRendered = '<span class="math-inline"><math><mi>x</mi></math></span> and *italic*';
16
+ expect(renderMath(preRendered)).toBe(
17
+ '<span class="math-inline"><math><mi>x</mi></math></span> and <em>italic</em>',
18
+ );
19
+ });
20
+
21
+ it('converts *text* to <em> (italic) for non-pre-rendered content', () => {
22
+ expect(renderMath('some *italic* text')).toBe('some <em>italic</em> text');
20
23
  });
21
24
 
22
- it('renders latexmath:[...] with nested brackets', () => {
23
- const result = renderMath('coords latexmath:[[u_0, u_1] \\leq 1.0] here');
24
- expect(result).toContain('math-inline');
25
- expect(result).toContain('katex');
26
- expect(result).not.toContain('latexmath:');
25
+ it('converts ~text~ to <sub> (subscript)', () => {
26
+ expect(renderMath('H~2~O')).toBe('H<sub>2</sub>O');
27
27
  });
28
28
 
29
29
  it('converts bullet lines to <ul><li>', () => {
@@ -33,20 +33,6 @@ describe('renderMath', () => {
33
33
  expect(result).toContain('<li>second item</li>');
34
34
  });
35
35
 
36
- it('does NOT convert *stem:[...] lines to list items', () => {
37
- const result = renderMath('*stem:[x]*');
38
- expect(result).not.toContain('<ul');
39
- expect(result).toContain('math-bold');
40
- });
41
-
42
- it('converts *text* to <em> (italic)', () => {
43
- expect(renderMath('some *italic* text')).toBe('some <em>italic</em> text');
44
- });
45
-
46
- it('converts ~text~ to <sub> (subscript)', () => {
47
- expect(renderMath('H~2~O')).toBe('H<sub>2</sub>O');
48
- });
49
-
50
36
  it('resolves URN inline refs via xrefResolver', () => {
51
37
  const resolver = (uri: string, term: string) => `[${term}→${uri}]`;
52
38
  const result = renderMath(
@@ -98,6 +84,14 @@ describe('renderMath', () => {
98
84
  expect(result).toBe('see some term');
99
85
  });
100
86
 
87
+ it('resolves cross-refs even in pre-rendered content', () => {
88
+ const resolver = (uri: string, term: string) => `[${term}→${uri}]`;
89
+ const preRendered = '<span class="math-inline"><math><mi>x</mi></math></span> and {{urn:iso:std:iso:14812:3.1.1.1,entity}}';
90
+ expect(renderMath(preRendered, resolver)).toBe(
91
+ '<span class="math-inline"><math><mi>x</mi></math></span> and [entity→urn:iso:std:iso:14812:3.1.1.1]',
92
+ );
93
+ });
94
+
101
95
  it('handles empty input', () => {
102
96
  expect(renderMath('')).toBe('');
103
97
  });
@@ -109,16 +103,9 @@ describe('renderMath', () => {
109
103
  });
110
104
 
111
105
  describe('cleanContent', () => {
112
- it('strips stem:[...] to raw math text', () => {
113
- expect(cleanContent('value stem:[x^2] here')).toBe('value x^2 here');
114
- });
115
-
116
- it('strips bold stem', () => {
117
- expect(cleanContent('value *stem:[x]* here')).toBe('value x here');
118
- });
119
-
120
- it('strips latexmath:[...] with nested brackets', () => {
121
- expect(cleanContent('coords latexmath:[[u_0, u_1] \\leq 1.0] end')).toBe('coords [u_0, u_1] \\leq 1.0 end');
106
+ it('strips pre-rendered HTML/MathML tags', () => {
107
+ expect(cleanContent('value <span class="math-inline"><math><mi>x</mi></math></span> here'))
108
+ .toBe('value x here');
122
109
  });
123
110
 
124
111
  it('strips *text* to plain text', () => {
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
@@ -159,12 +159,13 @@
159
159
  }
160
160
 
161
161
  /* Math */
162
- .math-inline .katex {
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-inline math {
168
169
  font-weight: bold;
169
170
  }
170
171
  .math-fallback {
package/src/utils/math.ts CHANGED
@@ -1,5 +1,3 @@
1
- import katex from 'katex';
2
-
3
1
  export type XrefResolver = (uri: string, term: string) => string;
4
2
  export type BibResolver = (refId: string, title: string) => string;
5
3
  export type FigResolver = (figId: string) => string;
@@ -39,50 +37,6 @@ function convertLists(text: string): string {
39
37
  return result;
40
38
  }
41
39
 
42
- function replaceBracketed(
43
- text: string,
44
- prefix: string,
45
- render: (math: string, bold: boolean) => string,
46
- ): string {
47
- let result = '';
48
- let i = 0;
49
- const boldPrefix = '*' + prefix;
50
- while (i < text.length) {
51
- if (text.startsWith(boldPrefix + '[', i)) {
52
- i += boldPrefix.length + 1;
53
- let j = i;
54
- let d = 1;
55
- while (j < text.length && d > 0) {
56
- if (text[j] === '[') d++;
57
- else if (text[j] === ']') d--;
58
- j++;
59
- }
60
- const content = text.slice(i, j - 1);
61
- let end = j;
62
- if (end < text.length && text[end] === '*') end++;
63
- result += render(content, true);
64
- i = end;
65
- }
66
- else if (text.startsWith(prefix + '[', i)) {
67
- i += prefix.length + 1;
68
- let j = i;
69
- let d = 1;
70
- while (j < text.length && d > 0) {
71
- if (text[j] === '[') d++;
72
- else if (text[j] === ']') d--;
73
- j++;
74
- }
75
- const content = text.slice(i, j - 1);
76
- result += render(content, false);
77
- i = j;
78
- } else {
79
- result += text[i];
80
- i++;
81
- }
82
- }
83
- return result;
84
- }
85
-
86
40
  export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | RenderOptions): string {
87
41
  if (!text) return '';
88
42
  let result = text;
@@ -91,11 +45,8 @@ export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | Ren
91
45
  ? { xrefResolver: xrefResolverOrOpts }
92
46
  : (xrefResolverOrOpts ?? {});
93
47
 
94
- result = replaceBracketed(result, 'stem:', renderKatexSpan);
95
- result = replaceBracketed(result, 'latexmath:', renderKatexSpan);
96
-
48
+ // Math (stem/latexmath) is pre-rendered at build time. Only process text formatting.
97
49
  result = convertLists(result);
98
-
99
50
  result = result.replace(/\*([^*]+)\*/g, '<em>$1</em>');
100
51
  result = result.replace(/~([^~]+)~/g, '<sub>$1</sub>');
101
52
 
@@ -134,19 +85,6 @@ export function renderMath(text: string, xrefResolverOrOpts?: XrefResolver | Ren
134
85
  return result;
135
86
  }
136
87
 
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
-
150
88
  function escapeHtml(text: string): string {
151
89
  return text
152
90
  .replace(/&/g, '&amp;')
@@ -157,6 +95,7 @@ function escapeHtml(text: string): string {
157
95
  export function cleanContent(text: string): string {
158
96
  if (!text) return '';
159
97
  let result = text
98
+ .replace(/<[^>]+>/g, '') // strip pre-rendered HTML/MathML
160
99
  .replace(/\*([^*]+)\*/g, '$1')
161
100
  .replace(/~([^~]+)~/g, '_$1')
162
101
  .replace(/\n[ \t]*\* /g, '; ')
@@ -165,7 +104,5 @@ export function cleanContent(text: string): string {
165
104
  .replace(/\{\{urn:[^,}]+,([^,}]+)(?:,[^}]+)?\}\}/g, '$1')
166
105
  .replace(/\{urn:[^,}]+,([^,}]+)(?:,[^}]+)?\}/g, '$1')
167
106
  .replace(/\{\{([^,}]+)(?:,\s*[^}]+)?\}\}/g, '$1');
168
- result = replaceBracketed(result, 'stem:', (math) => math);
169
- result = replaceBracketed(result, 'latexmath:', (math) => math);
170
107
  return result;
171
108
  }