@glossarist/concept-browser 0.7.113 → 0.7.115
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 +87 -2
- package/cli/index.mjs +15 -15
- package/cli/index.ts +9 -11
- package/data/concept-model/shapes/glossarist.concept.yaml.schema.json +255 -0
- package/package.json +14 -12
- package/scripts/__tests__/{bibliography.test.mjs → bibliography.test.ts} +1 -1
- package/scripts/__tests__/{cli-script-contract.test.mjs → cli-script-contract.test.ts} +3 -3
- package/scripts/__tests__/{concept-groups.test.mjs → concept-groups.test.ts} +1 -1
- package/scripts/__tests__/{doctor.test.mjs → doctor.test.ts} +1 -1
- package/scripts/__tests__/{fetch-datasets.test.mjs → fetch-datasets.test.ts} +1 -1
- package/scripts/__tests__/{first-non-empty.test.mjs → first-non-empty.test.ts} +1 -1
- package/scripts/__tests__/{process-about-pages.test.mjs → process-about-pages.test.ts} +1 -1
- package/scripts/{bridge-to-astro.mjs → bridge-to-astro.ts} +2 -4
- package/scripts/{build-edges.js → build-edges.ts} +11 -13
- package/scripts/{check-syntax.mjs → check-syntax.ts} +20 -13
- package/scripts/{fetch-datasets.mjs → fetch-datasets.ts} +2 -2
- package/scripts/{generate-data.mjs → generate-data.ts} +73 -460
- package/scripts/{generate-ontology-schema.mjs → generate-ontology-schema.ts} +2 -2
- package/scripts/lib/{build-cache.mjs → build-cache.ts} +3 -2
- package/scripts/lib/concept-formats.ts +206 -0
- package/scripts/lib/concept-uri.ts +14 -0
- package/scripts/lib/{dataset-turtle.mjs → dataset-turtle.ts} +4 -3
- package/scripts/lib/harmonize-concept.ts +86 -0
- package/scripts/lib/jsonld-emitter.ts +169 -0
- package/scripts/lib/{local-path-safety.mjs → local-path-safety.ts} +6 -2
- package/scripts/lib/yaml-types.ts +262 -0
- package/scripts/{process-about-pages.mjs → process-about-pages.ts} +1 -1
- package/scripts/{smoke.mjs → smoke.ts} +4 -3
- package/scripts/{validate-shacl.mjs → validate-shacl.ts} +5 -5
- package/scripts/validate-yaml.ts +144 -0
- package/src/__tests__/extract-source-refs.test.ts +2 -3
- package/src/__tests__/perf/serialization-perf.test.ts +5 -5
- package/src/__tests__/scripts/agents-turtle.test.ts +1 -1
- package/src/__tests__/scripts/bibliography-turtle.test.ts +1 -1
- package/src/__tests__/scripts/build-activity-turtle.test.ts +1 -1
- package/src/__tests__/scripts/build-cache.test.ts +1 -1
- package/src/__tests__/scripts/build-integration.test.ts +19 -19
- package/src/__tests__/scripts/dataset-turtle.test.ts +1 -1
- package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +1 -1
- package/src/__tests__/scripts/normalize-yaml.test.ts +1 -1
- package/src/__tests__/scripts/turtle-escape.test.ts +1 -1
- package/src/__tests__/scripts/validate-yaml.test.ts +76 -0
- package/src/__tests__/scripts/version-turtle.test.ts +1 -1
- package/src/__tests__/scripts/vocab-turtle.test.ts +1 -1
- package/src/__tests__/scripts-syntax.test.ts +2 -2
- package/src/__tests__/use-rdf-document.test.ts +2 -3
- package/src/__tests__/utils/content-renderer-xss.test.ts +57 -0
- package/src/__tests__/utils/inline-content-kinds.test.ts +139 -0
- package/src/__tests__/validate-shacl.test.ts +3 -3
- package/src/adapters/wire/index.ts +69 -0
- package/src/pages/[...path].astro +1 -0
- package/src/router/index.ts +5 -0
- package/src/utils/content-renderer.ts +106 -23
- package/src/utils/url-safety.ts +1 -5
- package/src/views/learn/LearnInlineContentView.vue +128 -0
- package/tsconfig.json +2 -2
- package/vite.config.ts +1 -1
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +0 -167
- package/src/__tests__/composables/use-concept-edges-v2.test.ts.bak +0 -166
- package/src/types/agents-version-turtle.d.ts +0 -27
- package/src/types/bibliography-turtle.d.ts +0 -12
- package/src/types/build-activity-turtle.d.ts +0 -16
- package/src/types/build-cache.d.ts +0 -20
- package/src/types/dataset-turtle.d.ts +0 -32
- package/src/types/normalize-yaml.d.ts +0 -16
- package/src/types/turtle-escape.d.ts +0 -6
- package/src/types/vocab-turtle.d.ts +0 -13
- /package/scripts/{doctor.mjs → doctor.ts} +0 -0
- /package/scripts/{extract-source-refs.js → extract-source-refs.ts} +0 -0
- /package/scripts/{generate-404.js → generate-404.ts} +0 -0
- /package/scripts/{generate-ontology-data.mjs → generate-ontology-data.ts} +0 -0
- /package/scripts/lib/{agents-turtle.mjs → agents-turtle.ts} +0 -0
- /package/scripts/lib/{bibliography-turtle.mjs → bibliography-turtle.ts} +0 -0
- /package/scripts/lib/{bibliography.mjs → bibliography.ts} +0 -0
- /package/scripts/lib/build/{image-assets.mjs → image-assets.ts} +0 -0
- /package/scripts/lib/build/{non-verbal-consumer.mjs → non-verbal-consumer.ts} +0 -0
- /package/scripts/lib/{build-activity-turtle.mjs → build-activity-turtle.ts} +0 -0
- /package/scripts/lib/{concept-groups.mjs → concept-groups.ts} +0 -0
- /package/scripts/lib/{first-non-empty.mjs → first-non-empty.ts} +0 -0
- /package/scripts/lib/{turtle-escape.mjs → turtle-escape.ts} +0 -0
- /package/scripts/lib/{version-turtle.mjs → version-turtle.ts} +0 -0
- /package/scripts/lib/{vocab-turtle.mjs → vocab-turtle.ts} +0 -0
- /package/scripts/{load-site-config.mjs → load-site-config.ts} +0 -0
- /package/scripts/{migrate-v1-to-v3.mjs → migrate-v1-to-v3.ts} +0 -0
- /package/scripts/{normalize-yaml.mjs → normalize-yaml.ts} +0 -0
- /package/scripts/{sync-concept-model.mjs → sync-concept-model.ts} +0 -0
|
@@ -5,19 +5,28 @@
|
|
|
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.
|
|
19
27
|
*/
|
|
20
28
|
import { escapeHtml, escapeAttr } from './escape';
|
|
29
|
+
import { sanitizeUrl, isSafeUrl } from './url-safety';
|
|
21
30
|
import { parseMention } from 'glossarist';
|
|
22
31
|
import type { NonVerbalKind } from '../adapters/non-verbal/types';
|
|
23
32
|
import { entityKindFromMentionKind } from '../adapters/non-verbal/kind';
|
|
@@ -29,6 +38,8 @@ export type BibResolver = (refId: string, title: string) => string;
|
|
|
29
38
|
export type CiteResolver = (key: string, label: string | null) => string;
|
|
30
39
|
export type ConceptRefResolver = (conceptId: string, term: string) => string;
|
|
31
40
|
export type NonVerbalRefResolver = (kind: NonVerbalKind, entityId: string, display?: string) => string;
|
|
41
|
+
export type LinkResolver = (url: string, label: string) => string;
|
|
42
|
+
export type ImageResolver = (src: string, alt: string) => string;
|
|
32
43
|
|
|
33
44
|
export interface RenderOptions {
|
|
34
45
|
xrefResolver?: XrefResolver;
|
|
@@ -36,6 +47,8 @@ export interface RenderOptions {
|
|
|
36
47
|
conceptRefResolver?: ConceptRefResolver;
|
|
37
48
|
citeResolver?: CiteResolver;
|
|
38
49
|
nonVerbalRefResolver?: NonVerbalRefResolver;
|
|
50
|
+
linkResolver?: LinkResolver;
|
|
51
|
+
imageResolver?: ImageResolver;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
// ── Math placeholders ────────────────────────────────────────────────────
|
|
@@ -123,7 +136,7 @@ function convertLists(text: string): string {
|
|
|
123
136
|
items.push(m[1].trim());
|
|
124
137
|
}
|
|
125
138
|
if (!items.length) return _;
|
|
126
|
-
const lis = items.map(item => `<li>${item}</li>`).join('');
|
|
139
|
+
const lis = items.map(item => `<li>${escapeHtml(item)}</li>`).join('');
|
|
127
140
|
return `\n<ul class="concept-list">${lis}</ul>`;
|
|
128
141
|
});
|
|
129
142
|
|
|
@@ -135,7 +148,7 @@ function convertLists(text: string): string {
|
|
|
135
148
|
items.push(m[1].trim());
|
|
136
149
|
}
|
|
137
150
|
if (!items.length) return _;
|
|
138
|
-
const lis = items.map(item => `<li>${item}</li>`).join('');
|
|
151
|
+
const lis = items.map(item => `<li>${escapeHtml(item)}</li>`).join('');
|
|
139
152
|
return `\n<ol class="concept-list concept-list-ordered">${lis}</ol>`;
|
|
140
153
|
});
|
|
141
154
|
|
|
@@ -144,12 +157,30 @@ function convertLists(text: string): string {
|
|
|
144
157
|
|
|
145
158
|
// ── Inline reference resolution ──────────────────────────────────────────
|
|
146
159
|
|
|
147
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Deprecated AsciiDoc xref syntax: <<refId, title>>
|
|
162
|
+
*
|
|
163
|
+
* This was historically overloaded for:
|
|
164
|
+
* 1. Non-concept entity xrefs (should use {{fig/table/formula:id, label}})
|
|
165
|
+
* 2. Bibliography lookups (should use {{bib:id}} or {{cite:id}})
|
|
166
|
+
* 3. Concept citations (should use {{cite:id}})
|
|
167
|
+
*
|
|
168
|
+
* All three uses are wrong. This function emits a deprecation warning and
|
|
169
|
+
* renders the title as plain text. Datasets should migrate to the unified
|
|
170
|
+
* {{kind:target}} syntax.
|
|
171
|
+
*/
|
|
172
|
+
function resolveLegacyXref(text: string): string {
|
|
148
173
|
return text.replace(/<<([^,>]+),([^>]+)>>/g, (_, refId, title) => {
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
const rid = refId.trim();
|
|
175
|
+
const lbl = title.trim();
|
|
176
|
+
if (typeof console !== 'undefined') {
|
|
177
|
+
console.warn(
|
|
178
|
+
`[glossarist] <<${rid},${lbl}>> is deprecated. ` +
|
|
179
|
+
`Use {{fig/table/formula:${rid}, ${lbl}}} for non-concept entities, ` +
|
|
180
|
+
`{{cite:${rid}}} for concept citations, or {{bib:${rid}}} for bibliography.`,
|
|
181
|
+
);
|
|
151
182
|
}
|
|
152
|
-
return `<span class="
|
|
183
|
+
return `<span class="legacy-xref" title="Deprecated: use {{kind:target}} syntax">${escapeHtml(lbl)}</span>`;
|
|
153
184
|
});
|
|
154
185
|
}
|
|
155
186
|
|
|
@@ -180,6 +211,48 @@ function resolveUrnRefs(text: string, opts: RenderOptions): string {
|
|
|
180
211
|
|
|
181
212
|
function resolveMentions(text: string, opts: RenderOptions): string {
|
|
182
213
|
return text.replace(/\{\{([^{}]+?)\}\}/g, (_orig, body) => {
|
|
214
|
+
|
|
215
|
+
// ── New kinds: link, image, bib (pre-parsed before parseMention) ──
|
|
216
|
+
// These follow the same {{kind:target[, label]}} convention as
|
|
217
|
+
// fig/table/formula but are handled here because glossarist's
|
|
218
|
+
// parseMention doesn't recognize them yet. When glossarist-js adds
|
|
219
|
+
// support (per PROMPT-NOW.md), these pre-parse cases can be removed
|
|
220
|
+
// and the switch below will handle them natively.
|
|
221
|
+
|
|
222
|
+
const linkMatch = body.match(/^link:(.+)$/i);
|
|
223
|
+
if (linkMatch) {
|
|
224
|
+
const rest = linkMatch[1];
|
|
225
|
+
const commaIdx = rest.indexOf(',');
|
|
226
|
+
const url = (commaIdx > 0 ? rest.slice(0, commaIdx).trim() : rest.trim());
|
|
227
|
+
const label = commaIdx > 0 ? rest.slice(commaIdx + 1).trim() : url;
|
|
228
|
+
if (opts.linkResolver) return opts.linkResolver(url, label);
|
|
229
|
+
const safeUrl = sanitizeUrl(url);
|
|
230
|
+
if (!safeUrl) return escapeHtml(label);
|
|
231
|
+
return `<a href="${escapeAttr(safeUrl)}" target="_blank" rel="noopener" class="ext-link">${escapeHtml(label)}</a>`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const imageMatch = body.match(/^image:(.+)$/i);
|
|
235
|
+
if (imageMatch) {
|
|
236
|
+
const rest = imageMatch[1];
|
|
237
|
+
const commaIdx = rest.indexOf(',');
|
|
238
|
+
const src = (commaIdx > 0 ? rest.slice(0, commaIdx).trim() : rest.trim());
|
|
239
|
+
const alt = commaIdx > 0 ? rest.slice(commaIdx + 1).trim() : '';
|
|
240
|
+
if (opts.imageResolver) return opts.imageResolver(src, alt);
|
|
241
|
+
if (!isSafeUrl(src)) return escapeHtml(alt || src);
|
|
242
|
+
return `<img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}" class="inline-image" />`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const bibMatch = body.match(/^bib:(.+)$/i);
|
|
246
|
+
if (bibMatch) {
|
|
247
|
+
const rest = bibMatch[1];
|
|
248
|
+
const commaIdx = rest.indexOf(',');
|
|
249
|
+
const id = (commaIdx > 0 ? rest.slice(0, commaIdx).trim() : rest.trim());
|
|
250
|
+
const label = commaIdx > 0 ? rest.slice(commaIdx + 1).trim() : id;
|
|
251
|
+
if (opts.bibResolver) return opts.bibResolver(id, label);
|
|
252
|
+
return `<span class="bib-ref">${escapeHtml(label)}</span>`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── Existing kinds: handled by glossarist's parseMention ──
|
|
183
256
|
const parsed = parseMention(body);
|
|
184
257
|
const p = parsed as unknown as Record<string, unknown>;
|
|
185
258
|
|
|
@@ -253,10 +326,10 @@ function resolveMentions(text: string, opts: RenderOptions): string {
|
|
|
253
326
|
* 2. AsciiDoc tables
|
|
254
327
|
* 3. Bullet and numbered lists
|
|
255
328
|
* 4. Text formatting (bold, italic, subscript)
|
|
256
|
-
* 5.
|
|
257
|
-
* 6.
|
|
258
|
-
* 7. Mention dispatcher —
|
|
259
|
-
* (cite-ref, urn-ref, numeric, designation)
|
|
329
|
+
* 5. Legacy AsciiDoc xrefs (<<ref,title>> → deprecated, renders as plain text)
|
|
330
|
+
* 6. URN inline references ({urn:...})
|
|
331
|
+
* 7. Mention dispatcher — link/image/bib (pre-parsed), then parseMention
|
|
332
|
+
* (fig/table/formula, cite-ref, urn-ref, numeric, designation)
|
|
260
333
|
*/
|
|
261
334
|
export function renderContent(text: string, xrefResolverOrOpts?: XrefResolver | RenderOptions): string {
|
|
262
335
|
if (!text) return '';
|
|
@@ -280,7 +353,7 @@ export function renderContent(text: string, xrefResolverOrOpts?: XrefResolver |
|
|
|
280
353
|
result = result.replace(/~([^~]+)~/g, '<sub>$1</sub>');
|
|
281
354
|
|
|
282
355
|
// Stage 4: Reference resolution
|
|
283
|
-
result =
|
|
356
|
+
result = resolveLegacyXref(result);
|
|
284
357
|
result = resolveUrnRefs(result, opts);
|
|
285
358
|
|
|
286
359
|
// Stage 5: Mention dispatcher (non-verbal first, then parseMention SSOT)
|
|
@@ -296,18 +369,28 @@ export function renderContent(text: string, xrefResolverOrOpts?: XrefResolver |
|
|
|
296
369
|
export function cleanContent(text: string): string {
|
|
297
370
|
if (!text) return '';
|
|
298
371
|
let result = text
|
|
372
|
+
// Legacy xrefs first (before HTML tag stripping eats <<)
|
|
373
|
+
.replace(/<<([^,>]+),([^>]+)>>/g, '$2')
|
|
299
374
|
.replace(/<[^>]+>/g, '')
|
|
300
375
|
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
301
376
|
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '$1')
|
|
302
377
|
.replace(/~([^~]+)~/g, '_$1')
|
|
303
378
|
.replace(/\n[ \t]*\* /g, '; ')
|
|
304
|
-
|
|
305
|
-
// Non-verbal mentions: {{fig:id, display}} → display; {{fig:id}} → id
|
|
379
|
+
// Entity mentions: {{fig:id, display}} → display; {{fig:id}} → id
|
|
306
380
|
.replace(/\{\{(?:fig|figure|table|tbl|formula|eq):([^,}]+),\s*([^}]+)\}\}/g, '$2')
|
|
307
381
|
.replace(/\{\{(?:fig|figure|table|tbl|formula|eq):([^}]+)\}\}/g, '$1')
|
|
382
|
+
// Link mentions: {{link:URL, label}} → label; {{link:URL}} → URL
|
|
383
|
+
.replace(/\{\{link:([^,}]+),\s*([^}]+)\}\}/gi, '$2')
|
|
384
|
+
.replace(/\{\{link:([^}]+)\}\}/gi, '$1')
|
|
385
|
+
// Image mentions: {{image:src, alt}} → alt; {{image:src}} → empty
|
|
386
|
+
.replace(/\{\{image:[^,}]+,\s*([^}]+)\}\}/gi, '$1')
|
|
387
|
+
.replace(/\{\{image:([^}]+)\}\}/gi, '')
|
|
388
|
+
// Bib mentions: {{bib:id, label}} → label; {{bib:id}} → id
|
|
389
|
+
.replace(/\{\{bib:([^,}]+),\s*([^}]+)\}\}/gi, '$2')
|
|
390
|
+
.replace(/\{\{bib:([^}]+)\}\}/gi, '$1')
|
|
308
391
|
// 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() : '')
|
|
392
|
+
.replace(/\{\{urn:[^,}]+,([^,}]+),([^}]+)\}\}/g, '$1')
|
|
393
|
+
.replace(/\{\{urn:[^,}]+(?:,([^}]+))?\}\}/g, (_, label) => label ? label.trim() : '')
|
|
311
394
|
.replace(/\{urn:[^,}]+,([^,}]+)(?:,[^}]+)?\}/g, '$1')
|
|
312
395
|
// Cite refs — show render term (or empty if bare)
|
|
313
396
|
.replace(/\{\{cite:[^,}]+(?:,([^}]+))?\}\}/g, (_, label) => label ? label.trim() : '')
|
package/src/utils/url-safety.ts
CHANGED
|
@@ -18,11 +18,7 @@ export function isSafeUrl(url: string): boolean {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return false;
|
|
21
|
+
return true;
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
export function sanitizeUrl(url: string): string {
|
|
@@ -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>
|
package/tsconfig.json
CHANGED
|
@@ -20,6 +20,6 @@
|
|
|
20
20
|
},
|
|
21
21
|
"baseUrl": "."
|
|
22
22
|
},
|
|
23
|
-
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "
|
|
24
|
-
"exclude": ["src/content.config.ts", "src/pages/**", "src/layouts/**", "src/astro-islands/**", "src/lib/site-config.ts"]
|
|
23
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "env.d.ts", ".astro/types.d.ts"],
|
|
24
|
+
"exclude": ["src/content.config.ts", "src/pages/**", "src/layouts/**", "src/astro-islands/**", "src/lib/site-config.ts", "src/__tests__/**", "scripts/**", "cli/**"]
|
|
25
25
|
}
|
package/vite.config.ts
CHANGED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck — TODO.typescript/12: remove after glossarist TS migration
|
|
2
|
-
import { describe, it, expect } from 'vitest';
|
|
3
|
-
// glossarist 0.4.33 renamed PartitiveRelation → PartitiveHyperedge.
|
|
4
|
-
// Import from glossarist/models (extended by our local augmentation).
|
|
5
|
-
import type {
|
|
6
|
-
PartitiveHyperedge,
|
|
7
|
-
PartitiveMember,
|
|
8
|
-
ConceptRef,
|
|
9
|
-
} from 'glossarist/models';
|
|
10
|
-
import {
|
|
11
|
-
PartitiveHyperedge as PartitiveHyperedgeModel,
|
|
12
|
-
PartitiveMember as PartitiveMemberModel,
|
|
13
|
-
ConceptRef as ConceptRefModel,
|
|
14
|
-
} from 'glossarist/models';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Verifies glossarist-js 0.4.26's MECE-native PartitiveRelation model
|
|
18
|
-
* (ISO 704:2022 presence × count + is_delimiting).
|
|
19
|
-
*
|
|
20
|
-
* The concept-browser consumes these fields directly (no migration
|
|
21
|
-
* bridge). These specs pin the runtime contract so a regression in
|
|
22
|
-
* upstream is caught here.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
function makeRef(source: string, id: string): ConceptRef {
|
|
26
|
-
return ConceptRefModel.fromJSON({ source, id }) as ConceptRef;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function makePartitive(
|
|
30
|
-
source: string,
|
|
31
|
-
id: string,
|
|
32
|
-
fields: { presence?: 'required' | 'optional'; count?: 'exactly_one' | 'at_least_one' | 'multiple'; isDelimiting?: boolean } = {},
|
|
33
|
-
): PartitiveMember {
|
|
34
|
-
const data: {
|
|
35
|
-
ref: ConceptRef;
|
|
36
|
-
presence?: 'required' | 'optional';
|
|
37
|
-
count?: 'exactly_one' | 'at_least_one' | 'multiple';
|
|
38
|
-
is_delimiting?: boolean;
|
|
39
|
-
} = { ref: makeRef(source, id) };
|
|
40
|
-
if (fields.presence) data.presence = fields.presence;
|
|
41
|
-
if (fields.count) data.count = fields.count;
|
|
42
|
-
if (fields.isDelimiting) data.is_delimiting = fields.isDelimiting;
|
|
43
|
-
return new (PartitiveMemberModel as unknown as new (data: unknown) => PartitiveMember)(data) as PartitiveMember;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function makeRelation(opts: {
|
|
47
|
-
comprehensive: ConceptRef;
|
|
48
|
-
partitives: PartitiveMember[];
|
|
49
|
-
completeness?: 'complete' | 'partial';
|
|
50
|
-
criterion?: Record<string, string>;
|
|
51
|
-
}): PartitiveHyperedge {
|
|
52
|
-
return new (PartitiveHyperedgeModel as unknown as new (data: unknown) => PartitiveHyperedge)(opts) as PartitiveHyperedge;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
describe('useConceptEdges — MECE PartitiveRelation (glossarist 0.4.26)', () => {
|
|
56
|
-
describe('PartitiveMember (ISO 704:2022 presence × count + delimiting)', () => {
|
|
57
|
-
it('presence defaults to required, count defaults to exactly_one', () => {
|
|
58
|
-
const m = makePartitive('test', '1.1');
|
|
59
|
-
expect(m.presence).toBe('required');
|
|
60
|
-
expect(m.presence === "required").toBe(true);
|
|
61
|
-
expect(m.presence === "optional").toBe(false);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('count defaults to exactly_one', () => {
|
|
65
|
-
const m = makePartitive('test', '1.1');
|
|
66
|
-
expect(m.count).toBe('exactly_one');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('presence=optional is preserved', () => {
|
|
70
|
-
const m = makePartitive('test', '1.1', { presence: 'optional' });
|
|
71
|
-
expect(m.presence).toBe('optional');
|
|
72
|
-
expect(m.presence === "optional").toBe(true);
|
|
73
|
-
expect(m.presence === "required").toBe(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('count=multiple is preserved', () => {
|
|
77
|
-
const m = makePartitive('test', '1.1', { count: 'multiple' });
|
|
78
|
-
expect(m.count).toBe('multiple');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('count=at_least_one is preserved', () => {
|
|
82
|
-
const m = makePartitive('test', '1.1', { count: 'at_least_one' });
|
|
83
|
-
expect(m.count).toBe('at_least_one');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('is_delimiting defaults to false', () => {
|
|
87
|
-
const m = makePartitive('test', '1.1');
|
|
88
|
-
expect((m as any).is_delimiting).toBe(false);
|
|
89
|
-
expect((m as any).is_delimiting).toBe(false);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('is_delimiting is preserved when set to true', () => {
|
|
93
|
-
const m = makePartitive('test', '1.1', { isDelimiting: true });
|
|
94
|
-
expect((m as any).is_delimiting).toBe(true);
|
|
95
|
-
expect((m as any).is_delimiting).toBe(true);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('rejects the vacuous (presence=optional, count=at_least_one)', () => {
|
|
99
|
-
expect(() =>
|
|
100
|
-
makePartitive('test', '1.1', { presence: 'optional', count: 'at_least_one' }),
|
|
101
|
-
).toThrow(/collapses to optional \+ multiple/);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe('PartitiveRelation', () => {
|
|
106
|
-
it('accepts 2 members with default axes', () => {
|
|
107
|
-
const r = makeRelation({
|
|
108
|
-
comprehensive: makeRef('test', '1.3'),
|
|
109
|
-
partitives: [makePartitive('test', '1.4'), makePartitive('test', '1.5')],
|
|
110
|
-
});
|
|
111
|
-
expect((r as any).members).toHaveLength(2);
|
|
112
|
-
expect((r as any).completeness).toBe('complete');
|
|
113
|
-
expect((r as any).isComplete).toBe(true);
|
|
114
|
-
expect((r as any).isPartial).toBe(false);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('completeness=partial is preserved', () => {
|
|
118
|
-
const r = makeRelation({
|
|
119
|
-
comprehensive: makeRef('test', '1.3'),
|
|
120
|
-
partitives: [makePartitive('test', '1.4'), makePartitive('test', '1.5')],
|
|
121
|
-
completeness: 'partial',
|
|
122
|
-
});
|
|
123
|
-
expect((r as any).completeness).toBe('partial');
|
|
124
|
-
expect((r as any).isPartial).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('isCoordinate true when ≥2 partitives', () => {
|
|
128
|
-
const r = makeRelation({
|
|
129
|
-
comprehensive: makeRef('test', '1.3'),
|
|
130
|
-
partitives: [makePartitive('test', '1.4'), makePartitive('test', '1.5')],
|
|
131
|
-
});
|
|
132
|
-
expect((r as any).isCoordinate).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('rejects < 2 partitives (ISO 704 requirement)', () => {
|
|
136
|
-
expect(() => makeRelation({
|
|
137
|
-
comprehensive: makeRef('test', '1.3'),
|
|
138
|
-
partitives: [makePartitive('test', '1.4')],
|
|
139
|
-
})).toThrow(/≥2 partitives/);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('round-trips through toJSON with MECE fields', () => {
|
|
143
|
-
const r = makeRelation({
|
|
144
|
-
comprehensive: makeRef('test', '1.3'),
|
|
145
|
-
partitives: [
|
|
146
|
-
makePartitive('test', '1.4', { presence: 'required', count: 'multiple' }),
|
|
147
|
-
makePartitive('test', '1.5', { presence: 'optional', isDelimiting: true }),
|
|
148
|
-
],
|
|
149
|
-
completeness: 'partial',
|
|
150
|
-
criterion: { eng: 'physical structure' },
|
|
151
|
-
});
|
|
152
|
-
const json = r.toJSON() as {
|
|
153
|
-
comprehensive: unknown;
|
|
154
|
-
partitives: Array<{ presence?: string; count?: string; is_delimiting?: boolean; ref: unknown }>;
|
|
155
|
-
completeness: string;
|
|
156
|
-
criterion?: Record<string, string>;
|
|
157
|
-
};
|
|
158
|
-
expect(json.comprehensive).toBeDefined();
|
|
159
|
-
expect(json.partitives).toHaveLength(2);
|
|
160
|
-
expect(json.partitives[0].count).toBe('multiple');
|
|
161
|
-
expect(json.partitives[1].presence).toBe('optional');
|
|
162
|
-
expect(json.partitives[1].is_delimiting).toBe(true);
|
|
163
|
-
expect(json.completeness).toBe('partial');
|
|
164
|
-
expect(json.criterion).toEqual({ eng: 'physical structure' });
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
});
|