@glossarist/concept-browser 0.7.113 → 0.7.114
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/README.md
CHANGED
|
@@ -293,6 +293,48 @@ The page appears in the sidebar navigation automatically.
|
|
|
293
293
|
|
|
294
294
|
See [`site-config.example.yml`](site-config.example.yml) for all options including UI languages, fonts, features, and dataset groups.
|
|
295
295
|
|
|
296
|
+
## Data/Deployment Boundary
|
|
297
|
+
|
|
298
|
+
Concept-browser enforces a strict separation between **data** (authored by dataset authors) and **deployment** (configured by deployers):
|
|
299
|
+
|
|
300
|
+
- **Dataset authors** write concepts, sources, bibliography, and inline mentions. They don't know where their dataset will be deployed or what other datasets will be co-deployed.
|
|
301
|
+
- **Deployers** write `site-config.yml` to register datasets, declare URI patterns, and optionally add routing entries for external datasets. They never edit dataset content.
|
|
302
|
+
- **Concept-browser** resolves every cross-reference at runtime via a fixed cascade, making multiple datasets behave as one coherent whole.
|
|
303
|
+
|
|
304
|
+
### The resolution cascade
|
|
305
|
+
|
|
306
|
+
Every `{{cite:...}}` and `{{urn:...}}` mention walks this cascade at render time:
|
|
307
|
+
|
|
308
|
+
1. **`uriPatterns`** — is there a co-deployed dataset that matches? → internal link (case 1)
|
|
309
|
+
2. **`routing[]`** — is there a routing entry for the URI? → external link (case 2)
|
|
310
|
+
3. **`citation.link`** — does the source have a canonical link? → flat bib record (case 3)
|
|
311
|
+
4. **Unresolved** — plain text
|
|
312
|
+
|
|
313
|
+
The same YAML renders differently in different deployments. The data never changes.
|
|
314
|
+
|
|
315
|
+
## Inline Content Syntax
|
|
316
|
+
|
|
317
|
+
All inline references in concept text use the unified `{{kind:target}}` notation:
|
|
318
|
+
|
|
319
|
+
| Kind | Example | What it does |
|
|
320
|
+
|---|---|---|
|
|
321
|
+
| `cite` | `{{cite:sourceId}}` | Cite a `ConceptSource` from this concept's `sources[]` — walks the full resolution cascade |
|
|
322
|
+
| `urn` | `{{urn:iso:std:iso:704}}` | Reference a concept via URN routing |
|
|
323
|
+
| `fig` | `{{fig:diagram_3}}` | Reference a figure entity in the same dataset |
|
|
324
|
+
| `table` | `{{table:units}}` | Reference a table entity |
|
|
325
|
+
| `formula` | `{{formula:ohm_law}}` | Reference a formula entity |
|
|
326
|
+
| `bib` | `{{bib:ref_1}}` | Reference a bibliography entry (case-3-only, no underlying concept) |
|
|
327
|
+
| `link` | `{{link:https://example.com}}` | External URL (canonical, deployment-independent) |
|
|
328
|
+
| `image` | `{{image:src, alt}}` | Inline image embed |
|
|
329
|
+
| *(none)* | `{{measurement unit}}` | Designation match in same dataset |
|
|
330
|
+
| *(none)* | `{{112-01-10}}` | Numeric ID match in same dataset |
|
|
331
|
+
|
|
332
|
+
Each kind accepts an optional label: `{{kind:target, label}}`.
|
|
333
|
+
|
|
334
|
+
**Deprecated:** `<<ref,title>>` (AsciiDoc xref syntax) emits a deprecation warning. Migrate to `{{kind:target}}`.
|
|
335
|
+
|
|
336
|
+
See `/learn/inline-content` on any deployed site for a full interactive reference.
|
|
337
|
+
|
|
296
338
|
## CLI Commands
|
|
297
339
|
|
|
298
340
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.114",
|
|
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": {
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { renderContent, cleanContent, type RenderOptions } from '../../utils/content-renderer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unified mention syntax contract — {{kind:target[, label]}}.
|
|
6
|
+
*
|
|
7
|
+
* Pins the dispatch for every kind and the deprecation behavior for
|
|
8
|
+
* the legacy <<ref,title>> AsciiDoc xref syntax.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
describe('content-renderer — unified mention kinds', () => {
|
|
12
|
+
|
|
13
|
+
describe('{{link:URL}} — external link', () => {
|
|
14
|
+
it('renders bare URL as external anchor', () => {
|
|
15
|
+
const html = renderContent('{{link:https://example.com}}');
|
|
16
|
+
expect(html).toContain('<a href="https://example.com"');
|
|
17
|
+
expect(html).toContain('target="_blank"');
|
|
18
|
+
expect(html).toContain('rel="noopener"');
|
|
19
|
+
expect(html).toContain('https://example.com');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders URL with label', () => {
|
|
23
|
+
const html = renderContent('{{link:https://example.com, click here}}');
|
|
24
|
+
expect(html).toContain('click here');
|
|
25
|
+
expect(html).toContain('href="https://example.com"');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('delegates to linkResolver when provided', () => {
|
|
29
|
+
const opts: RenderOptions = { linkResolver: (url, label) => `<a href="${url}" class="custom">${label}</a>` };
|
|
30
|
+
const html = renderContent('{{link:https://example.com, test}}', opts);
|
|
31
|
+
expect(html).toContain('class="custom"');
|
|
32
|
+
expect(html).toContain('test');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('{{image:src}} — inline image embed', () => {
|
|
37
|
+
it('renders bare src as img tag', () => {
|
|
38
|
+
const html = renderContent('{{image:photo.png}}');
|
|
39
|
+
expect(html).toContain('<img');
|
|
40
|
+
expect(html).toContain('src="photo.png"');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders src with alt text', () => {
|
|
44
|
+
const html = renderContent('{{image:photo.png, A photo of the setup}}');
|
|
45
|
+
expect(html).toContain('src="photo.png"');
|
|
46
|
+
expect(html).toContain('alt="A photo of the setup"');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('delegates to imageResolver when provided', () => {
|
|
50
|
+
const opts: RenderOptions = { imageResolver: (src, alt) => `<img src="/base/${src}" alt="${alt}" class="custom-img" />` };
|
|
51
|
+
const html = renderContent('{{image:photo.png, test}}', opts);
|
|
52
|
+
expect(html).toContain('src="/base/photo.png"');
|
|
53
|
+
expect(html).toContain('class="custom-img"');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('{{bib:id}} — bibliography entry', () => {
|
|
58
|
+
it('renders id as bib-ref span when no resolver', () => {
|
|
59
|
+
const html = renderContent('{{bib:ref_1}}');
|
|
60
|
+
expect(html).toContain('bib-ref');
|
|
61
|
+
expect(html).toContain('ref_1');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('renders label when provided', () => {
|
|
65
|
+
const html = renderContent('{{bib:ref_1, ISO 704}}');
|
|
66
|
+
expect(html).toContain('ISO 704');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('delegates to bibResolver when provided', () => {
|
|
70
|
+
const opts: RenderOptions = { bibResolver: (id, title) => `<a href="/bib/${id}" class="bib-link">${title}</a>` };
|
|
71
|
+
const html = renderContent('{{bib:ref_1, ISO 704}}', opts);
|
|
72
|
+
expect(html).toContain('href="/bib/ref_1"');
|
|
73
|
+
expect(html).toContain('ISO 704');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('cleanContent — new kinds stripped correctly', () => {
|
|
78
|
+
it('strips {{link:URL}} to the URL', () => {
|
|
79
|
+
expect(cleanContent('{{link:https://example.com}}')).toBe('https://example.com');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('strips {{link:URL, label}} to the label', () => {
|
|
83
|
+
expect(cleanContent('{{link:https://example.com, click here}}')).toBe('click here');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('strips {{image:src}} to empty string', () => {
|
|
87
|
+
expect(cleanContent('{{image:photo.png}}')).toBe('');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('strips {{image:src, alt}} to the alt text', () => {
|
|
91
|
+
expect(cleanContent('{{image:photo.png, A photo}}')).toBe('A photo');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('strips {{bib:id}} to the id', () => {
|
|
95
|
+
expect(cleanContent('{{bib:ref_1}}')).toBe('ref_1');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('strips {{bib:id, label}} to the label', () => {
|
|
99
|
+
expect(cleanContent('{{bib:ref_1, ISO 704}}')).toBe('ISO 704');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('content-renderer — legacy <<ref,title>> deprecation', () => {
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
vi.restoreAllMocks();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('emits a deprecation warning via console.warn', () => {
|
|
110
|
+
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
111
|
+
renderContent('See <<845-01-01, 845-01-01>> for details.');
|
|
112
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
113
|
+
expect(spy.mock.calls[0][0]).toContain('deprecated');
|
|
114
|
+
expect(spy.mock.calls[0][0]).toContain('<<845-01-01,845-01-01>>');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('renders the title as plain text in a legacy-xref span', () => {
|
|
118
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
119
|
+
const html = renderContent('See <<845-01-01, 845-01-01>> for details.');
|
|
120
|
+
expect(html).toContain('legacy-xref');
|
|
121
|
+
expect(html).toContain('845-01-01');
|
|
122
|
+
// Must NOT contain bib-ref or bib-link (the old wrong behavior)
|
|
123
|
+
expect(html).not.toContain('bib-ref');
|
|
124
|
+
expect(html).not.toContain('bib-link');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('does NOT call bibResolver (cascade is not bypassed)', () => {
|
|
128
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
129
|
+
const bibResolver = vi.fn(() => '<a class="bib-link">mock</a>');
|
|
130
|
+
const opts: RenderOptions = { bibResolver };
|
|
131
|
+
const html = renderContent('<<845-01-01, 845-01-01>>', opts);
|
|
132
|
+
expect(bibResolver).not.toHaveBeenCalled();
|
|
133
|
+
expect(html).not.toContain('bib-link');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('cleanContent extracts the title from <<ref,title>>', () => {
|
|
137
|
+
expect(cleanContent('See <<845-01-01, 845-01-01>>')).toContain('845-01-01');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -17,6 +17,7 @@ export async function getStaticPaths() {
|
|
|
17
17
|
{ params: { path: 'learn/relationships' } },
|
|
18
18
|
{ params: { path: 'learn/designations' } },
|
|
19
19
|
{ params: { path: 'learn/statuses' } },
|
|
20
|
+
{ params: { path: 'learn/inline-content' } },
|
|
20
21
|
];
|
|
21
22
|
for (const ds of datasets) {
|
|
22
23
|
paths.push({ params: { path: `dataset/${ds.data.id}/about` } });
|
package/src/router/index.ts
CHANGED
|
@@ -118,6 +118,11 @@ export const routes: RouteRecordRaw[] = [
|
|
|
118
118
|
name: 'learn-statuses',
|
|
119
119
|
component: () => import('../views/learn/LearnStatusesView.vue'),
|
|
120
120
|
},
|
|
121
|
+
{
|
|
122
|
+
path: '/learn/inline-content',
|
|
123
|
+
name: 'learn-inline-content',
|
|
124
|
+
component: () => import('../views/learn/LearnInlineContentView.vue'),
|
|
125
|
+
},
|
|
121
126
|
{
|
|
122
127
|
path: '/about',
|
|
123
128
|
name: 'about-global',
|
|
@@ -5,14 +5,22 @@
|
|
|
5
5
|
* math placeholders, tables, lists, and text formatting. This is the single
|
|
6
6
|
* source of truth for content rendering in the browser.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - {{
|
|
12
|
-
* - {{
|
|
13
|
-
* - {{
|
|
14
|
-
* - {{
|
|
15
|
-
* - {{
|
|
8
|
+
* Unified mention syntax: {{kind:target[, label]}}
|
|
9
|
+
* Every inline reference is a typed mention with a kind prefix:
|
|
10
|
+
*
|
|
11
|
+
* - {{cite:key[, label]}} → citeResolver (concept source citation)
|
|
12
|
+
* - {{urn:...[, label]}} → xrefResolver (URN routing)
|
|
13
|
+
* - {{fig:id[, label]}} → nonVerbalRefResolver (figure)
|
|
14
|
+
* - {{table:id[, label]}} → nonVerbalRefResolver (table)
|
|
15
|
+
* - {{formula:id[, label]}} → nonVerbalRefResolver (formula)
|
|
16
|
+
* - {{bib:id[, label]}} → bibResolver (bibliography entry, case-3-only)
|
|
17
|
+
* - {{link:URL[, label]}} → linkResolver (external URL)
|
|
18
|
+
* - {{image:src[, alt]}} → imageResolver (inline image embed)
|
|
19
|
+
* - {{designation[, label]}} → conceptRefResolver (designation match)
|
|
20
|
+
* - {{numeric_id[, label]}} → conceptRefResolver (numeric ID match)
|
|
21
|
+
*
|
|
22
|
+
* Legacy `<<ref,title>>` (AsciiDoc xref) is deprecated; emits a console
|
|
23
|
+
* warning and renders as plain text. Migrate to {{kind:target}} syntax.
|
|
16
24
|
*
|
|
17
25
|
* Math-specific helpers (replaceBracketed, mathPlaceholder) are internal.
|
|
18
26
|
* The v-math directive upgrades the placeholders to Plurimath at runtime.
|
|
@@ -29,6 +37,8 @@ export type BibResolver = (refId: string, title: string) => string;
|
|
|
29
37
|
export type CiteResolver = (key: string, label: string | null) => string;
|
|
30
38
|
export type ConceptRefResolver = (conceptId: string, term: string) => string;
|
|
31
39
|
export type NonVerbalRefResolver = (kind: NonVerbalKind, entityId: string, display?: string) => string;
|
|
40
|
+
export type LinkResolver = (url: string, label: string) => string;
|
|
41
|
+
export type ImageResolver = (src: string, alt: string) => string;
|
|
32
42
|
|
|
33
43
|
export interface RenderOptions {
|
|
34
44
|
xrefResolver?: XrefResolver;
|
|
@@ -36,6 +46,8 @@ export interface RenderOptions {
|
|
|
36
46
|
conceptRefResolver?: ConceptRefResolver;
|
|
37
47
|
citeResolver?: CiteResolver;
|
|
38
48
|
nonVerbalRefResolver?: NonVerbalRefResolver;
|
|
49
|
+
linkResolver?: LinkResolver;
|
|
50
|
+
imageResolver?: ImageResolver;
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
// ── Math placeholders ────────────────────────────────────────────────────
|
|
@@ -144,12 +156,30 @@ function convertLists(text: string): string {
|
|
|
144
156
|
|
|
145
157
|
// ── Inline reference resolution ──────────────────────────────────────────
|
|
146
158
|
|
|
147
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Deprecated AsciiDoc xref syntax: <<refId, title>>
|
|
161
|
+
*
|
|
162
|
+
* This was historically overloaded for:
|
|
163
|
+
* 1. Non-concept entity xrefs (should use {{fig/table/formula:id, label}})
|
|
164
|
+
* 2. Bibliography lookups (should use {{bib:id}} or {{cite:id}})
|
|
165
|
+
* 3. Concept citations (should use {{cite:id}})
|
|
166
|
+
*
|
|
167
|
+
* All three uses are wrong. This function emits a deprecation warning and
|
|
168
|
+
* renders the title as plain text. Datasets should migrate to the unified
|
|
169
|
+
* {{kind:target}} syntax.
|
|
170
|
+
*/
|
|
171
|
+
function resolveLegacyXref(text: string): string {
|
|
148
172
|
return text.replace(/<<([^,>]+),([^>]+)>>/g, (_, refId, title) => {
|
|
149
|
-
|
|
150
|
-
|
|
173
|
+
const rid = refId.trim();
|
|
174
|
+
const lbl = title.trim();
|
|
175
|
+
if (typeof console !== 'undefined') {
|
|
176
|
+
console.warn(
|
|
177
|
+
`[glossarist] <<${rid},${lbl}>> is deprecated. ` +
|
|
178
|
+
`Use {{fig/table/formula:${rid}, ${lbl}}} for non-concept entities, ` +
|
|
179
|
+
`{{cite:${rid}}} for concept citations, or {{bib:${rid}}} for bibliography.`,
|
|
180
|
+
);
|
|
151
181
|
}
|
|
152
|
-
return `<span class="
|
|
182
|
+
return `<span class="legacy-xref" title="Deprecated: use {{kind:target}} syntax">${escapeHtml(lbl)}</span>`;
|
|
153
183
|
});
|
|
154
184
|
}
|
|
155
185
|
|
|
@@ -180,6 +210,45 @@ function resolveUrnRefs(text: string, opts: RenderOptions): string {
|
|
|
180
210
|
|
|
181
211
|
function resolveMentions(text: string, opts: RenderOptions): string {
|
|
182
212
|
return text.replace(/\{\{([^{}]+?)\}\}/g, (_orig, body) => {
|
|
213
|
+
|
|
214
|
+
// ── New kinds: link, image, bib (pre-parsed before parseMention) ──
|
|
215
|
+
// These follow the same {{kind:target[, label]}} convention as
|
|
216
|
+
// fig/table/formula but are handled here because glossarist's
|
|
217
|
+
// parseMention doesn't recognize them yet. When glossarist-js adds
|
|
218
|
+
// support (per PROMPT-NOW.md), these pre-parse cases can be removed
|
|
219
|
+
// and the switch below will handle them natively.
|
|
220
|
+
|
|
221
|
+
const linkMatch = body.match(/^link:(.+)$/i);
|
|
222
|
+
if (linkMatch) {
|
|
223
|
+
const rest = linkMatch[1];
|
|
224
|
+
const commaIdx = rest.indexOf(',');
|
|
225
|
+
const url = (commaIdx > 0 ? rest.slice(0, commaIdx).trim() : rest.trim());
|
|
226
|
+
const label = commaIdx > 0 ? rest.slice(commaIdx + 1).trim() : url;
|
|
227
|
+
if (opts.linkResolver) return opts.linkResolver(url, label);
|
|
228
|
+
return `<a href="${escapeAttr(url)}" target="_blank" rel="noopener" class="ext-link">${escapeHtml(label)}</a>`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const imageMatch = body.match(/^image:(.+)$/i);
|
|
232
|
+
if (imageMatch) {
|
|
233
|
+
const rest = imageMatch[1];
|
|
234
|
+
const commaIdx = rest.indexOf(',');
|
|
235
|
+
const src = (commaIdx > 0 ? rest.slice(0, commaIdx).trim() : rest.trim());
|
|
236
|
+
const alt = commaIdx > 0 ? rest.slice(commaIdx + 1).trim() : '';
|
|
237
|
+
if (opts.imageResolver) return opts.imageResolver(src, alt);
|
|
238
|
+
return `<img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}" class="inline-image" />`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const bibMatch = body.match(/^bib:(.+)$/i);
|
|
242
|
+
if (bibMatch) {
|
|
243
|
+
const rest = bibMatch[1];
|
|
244
|
+
const commaIdx = rest.indexOf(',');
|
|
245
|
+
const id = (commaIdx > 0 ? rest.slice(0, commaIdx).trim() : rest.trim());
|
|
246
|
+
const label = commaIdx > 0 ? rest.slice(commaIdx + 1).trim() : id;
|
|
247
|
+
if (opts.bibResolver) return opts.bibResolver(id, label);
|
|
248
|
+
return `<span class="bib-ref">${escapeHtml(label)}</span>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── Existing kinds: handled by glossarist's parseMention ──
|
|
183
252
|
const parsed = parseMention(body);
|
|
184
253
|
const p = parsed as unknown as Record<string, unknown>;
|
|
185
254
|
|
|
@@ -253,10 +322,10 @@ function resolveMentions(text: string, opts: RenderOptions): string {
|
|
|
253
322
|
* 2. AsciiDoc tables
|
|
254
323
|
* 3. Bullet and numbered lists
|
|
255
324
|
* 4. Text formatting (bold, italic, subscript)
|
|
256
|
-
* 5.
|
|
257
|
-
* 6.
|
|
258
|
-
* 7. Mention dispatcher —
|
|
259
|
-
* (cite-ref, urn-ref, numeric, designation)
|
|
325
|
+
* 5. Legacy AsciiDoc xrefs (<<ref,title>> → deprecated, renders as plain text)
|
|
326
|
+
* 6. URN inline references ({urn:...})
|
|
327
|
+
* 7. Mention dispatcher — link/image/bib (pre-parsed), then parseMention
|
|
328
|
+
* (fig/table/formula, cite-ref, urn-ref, numeric, designation)
|
|
260
329
|
*/
|
|
261
330
|
export function renderContent(text: string, xrefResolverOrOpts?: XrefResolver | RenderOptions): string {
|
|
262
331
|
if (!text) return '';
|
|
@@ -280,7 +349,7 @@ export function renderContent(text: string, xrefResolverOrOpts?: XrefResolver |
|
|
|
280
349
|
result = result.replace(/~([^~]+)~/g, '<sub>$1</sub>');
|
|
281
350
|
|
|
282
351
|
// Stage 4: Reference resolution
|
|
283
|
-
result =
|
|
352
|
+
result = resolveLegacyXref(result);
|
|
284
353
|
result = resolveUrnRefs(result, opts);
|
|
285
354
|
|
|
286
355
|
// Stage 5: Mention dispatcher (non-verbal first, then parseMention SSOT)
|
|
@@ -296,18 +365,28 @@ export function renderContent(text: string, xrefResolverOrOpts?: XrefResolver |
|
|
|
296
365
|
export function cleanContent(text: string): string {
|
|
297
366
|
if (!text) return '';
|
|
298
367
|
let result = text
|
|
368
|
+
// Legacy xrefs first (before HTML tag stripping eats <<)
|
|
369
|
+
.replace(/<<([^,>]+),([^>]+)>>/g, '$2')
|
|
299
370
|
.replace(/<[^>]+>/g, '')
|
|
300
371
|
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
301
372
|
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '$1')
|
|
302
373
|
.replace(/~([^~]+)~/g, '_$1')
|
|
303
374
|
.replace(/\n[ \t]*\* /g, '; ')
|
|
304
|
-
|
|
305
|
-
// Non-verbal mentions: {{fig:id, display}} → display; {{fig:id}} → id
|
|
375
|
+
// Entity mentions: {{fig:id, display}} → display; {{fig:id}} → id
|
|
306
376
|
.replace(/\{\{(?:fig|figure|table|tbl|formula|eq):([^,}]+),\s*([^}]+)\}\}/g, '$2')
|
|
307
377
|
.replace(/\{\{(?:fig|figure|table|tbl|formula|eq):([^}]+)\}\}/g, '$1')
|
|
378
|
+
// Link mentions: {{link:URL, label}} → label; {{link:URL}} → URL
|
|
379
|
+
.replace(/\{\{link:([^,}]+),\s*([^}]+)\}\}/gi, '$2')
|
|
380
|
+
.replace(/\{\{link:([^}]+)\}\}/gi, '$1')
|
|
381
|
+
// Image mentions: {{image:src, alt}} → alt; {{image:src}} → empty
|
|
382
|
+
.replace(/\{\{image:[^,}]+,\s*([^}]+)\}\}/gi, '$1')
|
|
383
|
+
.replace(/\{\{image:([^}]+)\}\}/gi, '')
|
|
384
|
+
// Bib mentions: {{bib:id, label}} → label; {{bib:id}} → id
|
|
385
|
+
.replace(/\{\{bib:([^,}]+),\s*([^}]+)\}\}/gi, '$2')
|
|
386
|
+
.replace(/\{\{bib:([^}]+)\}\}/gi, '$1')
|
|
308
387
|
// URN refs — show render term (second part for two-arg, third part for three-arg)
|
|
309
|
-
.replace(/\{\{urn:[^,}]+,([^,}]+),([^}]+)\}\}/g, '$1')
|
|
310
|
-
.replace(/\{\{urn:[^,}]+(?:,([^}]+))?\}\}/g, (_, label) => label ? label.trim() : '')
|
|
388
|
+
.replace(/\{\{urn:[^,}]+,([^,}]+),([^}]+)\}\}/g, '$1')
|
|
389
|
+
.replace(/\{\{urn:[^,}]+(?:,([^}]+))?\}\}/g, (_, label) => label ? label.trim() : '')
|
|
311
390
|
.replace(/\{urn:[^,}]+,([^,}]+)(?:,[^}]+)?\}/g, '$1')
|
|
312
391
|
// Cite refs — show render term (or empty if bare)
|
|
313
392
|
.replace(/\{\{cite:[^,}]+(?:,([^}]+))?\}\}/g, (_, label) => label ? label.trim() : '')
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LearnInlineContentView — unified inline content syntax reference.
|
|
4
|
+
*
|
|
5
|
+
* Documents every {{kind:target}} mention kind with examples,
|
|
6
|
+
* explains the data/deployment boundary for citations, and shows
|
|
7
|
+
* the resolution cascade.
|
|
8
|
+
*/
|
|
9
|
+
import { computed } from 'vue';
|
|
10
|
+
import { useI18n } from '../../i18n';
|
|
11
|
+
import LearnCard from '../../components/learn/LearnCard.vue';
|
|
12
|
+
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
|
|
15
|
+
const introText = computed(() =>
|
|
16
|
+
t('learn.inlineContent.intro') ||
|
|
17
|
+
'Every inline reference in concept text uses the unified mustache-kind:target notation. The first word (before the colon) tells you what kind of reference it is.',
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const mentionKinds = [
|
|
21
|
+
{ kind: 'cite', syntax: '{{cite:sourceId}}', example: '{{cite:iso7301}}', desc: 'Cite a ConceptSource from this concept\'s sources[] list. Walks the full resolution cascade (co-deployed → routed → bib).' },
|
|
22
|
+
{ kind: 'cite + label', syntax: '{{cite:sourceId, label}}', example: '{{cite:iso7301, rice}}', desc: 'Same as cite, with an explicit display label.' },
|
|
23
|
+
{ kind: 'urn', syntax: '{{urn:URN}}', example: '{{urn:iso:std:iso:704}}', desc: 'Reference a concept via URN routing. The deployment decides which dataset (if any) handles this URN.' },
|
|
24
|
+
{ kind: 'fig', syntax: '{{fig:id}}', example: '{{fig:diagram_3}}', desc: 'Reference a figure entity in the same dataset.' },
|
|
25
|
+
{ kind: 'table', syntax: '{{table:id}}', example: '{{table:unit_list}}', desc: 'Reference a table entity.' },
|
|
26
|
+
{ kind: 'formula', syntax: '{{formula:id}}', example: '{{formula:ohm_law}}', desc: 'Reference a formula entity.' },
|
|
27
|
+
{ kind: 'bib', syntax: '{{bib:id}}', example: '{{bib:ref_1}}', desc: 'Bibliography entry (case-3-only — no underlying concept). Renders as a flat reference.' },
|
|
28
|
+
{ kind: 'link', syntax: '{{link:URL}}', example: '{{link:https://example.com}}', desc: 'External URL. The URL is canonical; the deployment has no say.' },
|
|
29
|
+
{ kind: 'link + label', syntax: '{{link:URL, label}}', example: '{{link:https://example.com, click here}}', desc: 'External URL with display label.' },
|
|
30
|
+
{ kind: 'image', syntax: '{{image:src}}', example: '{{image:diagram.png}}', desc: 'Inline image embed. Local images get basePath-prefixed.' },
|
|
31
|
+
{ kind: 'image + alt', syntax: '{{image:src, alt}}', example: '{{image:diagram.png, The diagram}}', desc: 'Image with accessibility alt text.' },
|
|
32
|
+
{ kind: 'designation', syntax: '{{designation}}', example: '{{measurement unit}}', desc: 'Reference a concept by matching its designation text (same dataset).' },
|
|
33
|
+
{ kind: 'numeric', syntax: '{{numeric_id}}', example: '{{112-01-10}}', desc: 'Reference a concept by numeric ID (same dataset).' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const cascade = [
|
|
37
|
+
{ step: 1, name: 'uriPatterns', desc: 'Is there a co-deployed dataset that matches this URI?', result: 'Internal link (case 1)' },
|
|
38
|
+
{ step: 2, name: 'routing[]', desc: 'Is there a routing entry for this URI in site-config.yml?', result: 'External link (case 2)' },
|
|
39
|
+
{ step: 3, name: 'citation.link', desc: 'Does the source have a canonical link?', result: 'Flat bib record (case 3)' },
|
|
40
|
+
{ step: 4, name: 'unresolved', desc: 'No match anywhere', result: 'Plain text' },
|
|
41
|
+
];
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
|
46
|
+
<h1 class="font-title text-3xl text-ink-800 dark:text-ink-50 mb-2">
|
|
47
|
+
{{ t('learn.inlineContent.title') || 'Inline Content Syntax' }}
|
|
48
|
+
</h1>
|
|
49
|
+
<p class="text-ink-500 dark:text-ink-400">
|
|
50
|
+
{{ introText }}
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<LearnCard title="Mention Kinds">
|
|
54
|
+
<template #body>
|
|
55
|
+
<div class="overflow-x-auto">
|
|
56
|
+
<table class="w-full text-sm">
|
|
57
|
+
<thead>
|
|
58
|
+
<tr class="border-b border-ink-100 dark:border-ink-700 text-left">
|
|
59
|
+
<th class="py-2 pr-4">Kind</th>
|
|
60
|
+
<th class="py-2 pr-4">Syntax</th>
|
|
61
|
+
<th class="py-2 pr-4">Example</th>
|
|
62
|
+
<th class="py-2">Description</th>
|
|
63
|
+
</tr>
|
|
64
|
+
</thead>
|
|
65
|
+
<tbody>
|
|
66
|
+
<tr v-for="row in mentionKinds" :key="row.syntax" class="border-b border-ink-50 dark:border-ink-800">
|
|
67
|
+
<td class="py-2 pr-4 font-mono text-xs text-ink-600 dark:text-ink-300">{{ row.kind }}</td>
|
|
68
|
+
<td class="py-2 pr-4 font-mono text-xs text-ink-700 dark:text-ink-200">{{ row.syntax }}</td>
|
|
69
|
+
<td class="py-2 pr-4 font-mono text-xs text-emerald-700 dark:text-emerald-300">{{ row.example }}</td>
|
|
70
|
+
<td class="py-2 text-xs text-ink-500 dark:text-ink-400">{{ row.desc }}</td>
|
|
71
|
+
</tr>
|
|
72
|
+
</tbody>
|
|
73
|
+
</table>
|
|
74
|
+
</div>
|
|
75
|
+
</template>
|
|
76
|
+
</LearnCard>
|
|
77
|
+
|
|
78
|
+
<LearnCard title="Data / Deployment Boundary">
|
|
79
|
+
<template #body>
|
|
80
|
+
<div class="space-y-3 text-sm text-ink-600 dark:text-ink-300">
|
|
81
|
+
<p>
|
|
82
|
+
<strong>Dataset authors</strong> write citations in a deployment-agnostic way.
|
|
83
|
+
They don't know where their dataset will be deployed or what other datasets will be co-deployed.
|
|
84
|
+
</p>
|
|
85
|
+
<p>
|
|
86
|
+
<strong>Deployers</strong> write <code class="text-xs bg-ink-50 dark:bg-ink-800 px-1 rounded">site-config.yml</code>
|
|
87
|
+
to register datasets and declare URI patterns. They never edit dataset content.
|
|
88
|
+
</p>
|
|
89
|
+
<p>
|
|
90
|
+
<strong>Concept-browser</strong> resolves every citation at runtime. The same YAML works in any deployment.
|
|
91
|
+
</p>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
</LearnCard>
|
|
95
|
+
|
|
96
|
+
<LearnCard title="Resolution Cascade">
|
|
97
|
+
<template #body>
|
|
98
|
+
<div class="space-y-2">
|
|
99
|
+
<div v-for="step in cascade" :key="step.step" class="flex items-start gap-3 py-2">
|
|
100
|
+
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-ink-100 dark:bg-ink-700 text-ink-600 dark:text-ink-300 text-xs font-mono flex items-center justify-center">{{ step.step }}</span>
|
|
101
|
+
<div class="flex-1">
|
|
102
|
+
<div class="font-mono text-xs text-ink-700 dark:text-ink-200">{{ step.name }}</div>
|
|
103
|
+
<div class="text-xs text-ink-500 dark:text-ink-400">{{ step.desc }}</div>
|
|
104
|
+
<div class="text-xs text-emerald-700 dark:text-emerald-300 mt-0.5">{{ step.result }}</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</template>
|
|
109
|
+
</LearnCard>
|
|
110
|
+
|
|
111
|
+
<LearnCard title="Deprecated: <<ref,title>>">
|
|
112
|
+
<template #body>
|
|
113
|
+
<div class="text-sm text-ink-600 dark:text-ink-300 space-y-2">
|
|
114
|
+
<p>
|
|
115
|
+
The AsciiDoc xref syntax <code class="text-xs bg-amber-50 dark:bg-amber-900/20 px-1 rounded"><<ref,title>></code>
|
|
116
|
+
is deprecated. It emits a console warning and renders as plain text.
|
|
117
|
+
</p>
|
|
118
|
+
<p>Migrate to the unified syntax:</p>
|
|
119
|
+
<ul class="list-disc list-inside text-xs space-y-1">
|
|
120
|
+
<li><code><<fig_3,Figure 3>></code> → <code class="text-emerald-700 dark:text-emerald-300" v-text="'{{fig:fig_3, Figure 3}}'"></code></li>
|
|
121
|
+
<li><code><<845-01-01, 845-01-01>></code> → <code class="text-emerald-700 dark:text-emerald-300" v-text="'{{cite:iev_845-01-01}}'"></code></li>
|
|
122
|
+
<li><code><<ref_1, ISO 704>></code> → <code class="text-emerald-700 dark:text-emerald-300" v-text="'{{bib:ref_1, ISO 704}}'"></code></li>
|
|
123
|
+
</ul>
|
|
124
|
+
</div>
|
|
125
|
+
</template>
|
|
126
|
+
</LearnCard>
|
|
127
|
+
</div>
|
|
128
|
+
</template>
|