@glossarist/concept-browser 0.7.127 → 0.7.129
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.7.
|
|
3
|
+
"version": "0.7.129",
|
|
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,80 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { conceptFromJson } from '../../adapters/model-bridge';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Regression test: sources stored at the localization level
|
|
6
|
+
* (gl:localizedConcept.eng.gl:source) must be accessible via
|
|
7
|
+
* concept.localization(lang).sources so the citeResolver can find them.
|
|
8
|
+
*
|
|
9
|
+
* Without this, {{cite:sourceId}} mentions render as plain text
|
|
10
|
+
* instead of links — the exact bug reported on cie-eilv concept 17-21-001.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function makeConceptWithLocalizationSources() {
|
|
14
|
+
return {
|
|
15
|
+
'@type': 'skos:Concept',
|
|
16
|
+
'@id': 'https://example.com/dataset/cie-2020/concept/17-21-001',
|
|
17
|
+
'gl:identifier': '17-21-001',
|
|
18
|
+
'gl:localizedConcept': {
|
|
19
|
+
eng: {
|
|
20
|
+
'gl:languageCode': 'eng',
|
|
21
|
+
'gl:designation': [{ '@type': 'gl:Expression', 'gl:term': 'quantity', 'gl:normativeStatus': 'preferred' }],
|
|
22
|
+
'gl:definition': [{ 'gl:content': 'property of a phenomenon' }],
|
|
23
|
+
'gl:source': [
|
|
24
|
+
{
|
|
25
|
+
'@type': 'gl:ConceptSource',
|
|
26
|
+
'gl:id': '845-01-01',
|
|
27
|
+
'gl:sourceType': 'authoritative',
|
|
28
|
+
'gl:origin': {
|
|
29
|
+
'@type': 'gl:Citation',
|
|
30
|
+
'gl:ref': { '@type': 'gl:Ref', 'gl:source': 'IEV', 'gl:id': '845-01-01' },
|
|
31
|
+
'gl:link': 'https://www.electropedia.org/iev/iev.nsf/display?openform&ievref=845-01-01',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
'@type': 'gl:ConceptSource',
|
|
36
|
+
'gl:sourceType': 'authoritative',
|
|
37
|
+
'gl:origin': {
|
|
38
|
+
'@type': 'gl:Citation',
|
|
39
|
+
'gl:ref': { '@type': 'gl:Ref', 'gl:source': 'CIE S 017:2011', 'gl:id': '17-370' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('localization-level sources accessible for citeResolver', () => {
|
|
49
|
+
it('concept.localization("eng").sources has the localization sources', () => {
|
|
50
|
+
const concept = conceptFromJson(makeConceptWithLocalizationSources() as any);
|
|
51
|
+
const lc = concept.localization('eng');
|
|
52
|
+
expect(lc).toBeTruthy();
|
|
53
|
+
expect(lc!.sources).toBeDefined();
|
|
54
|
+
expect(lc!.sources!.length).toBeGreaterThanOrEqual(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('source with gl:id is findable by id', () => {
|
|
58
|
+
const concept = conceptFromJson(makeConceptWithLocalizationSources() as any);
|
|
59
|
+
const lc = concept.localization('eng');
|
|
60
|
+
const source = lc!.sources!.find(s => s.id === '845-01-01');
|
|
61
|
+
expect(source).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('source origin link is preserved', () => {
|
|
65
|
+
const concept = conceptFromJson(makeConceptWithLocalizationSources() as any);
|
|
66
|
+
const lc = concept.localization('eng');
|
|
67
|
+
const source = lc!.sources!.find(s => s.id === '845-01-01');
|
|
68
|
+
expect(source).toBeDefined();
|
|
69
|
+
expect(source!.origin?.link).toBeTruthy();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('merging concept + localization sources gives complete set', () => {
|
|
73
|
+
const concept = conceptFromJson(makeConceptWithLocalizationSources() as any);
|
|
74
|
+
const conceptSources = concept.sources ?? [];
|
|
75
|
+
const lcSources = concept.localization('eng')?.sources ?? [];
|
|
76
|
+
const merged = [...conceptSources, ...lcSources];
|
|
77
|
+
expect(merged.length).toBeGreaterThanOrEqual(2);
|
|
78
|
+
expect(merged.find(s => s.id === '845-01-01')).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for citeResolver DATASET:ID matching logic.
|
|
5
|
+
*
|
|
6
|
+
* The citeResolver must parse {{cite:IEV:702-02-07}} and match it
|
|
7
|
+
* against sources that have:
|
|
8
|
+
* - id: "702-02-07" (the ID part)
|
|
9
|
+
* - origin.ref: { source: "IEV", id: "702-02-07" }
|
|
10
|
+
*
|
|
11
|
+
* Previously, it only matched s.id === key, so "IEV:702-02-07"
|
|
12
|
+
* never matched source.id "702-02-07".
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface SourceLike {
|
|
16
|
+
id: string | null;
|
|
17
|
+
origin?: {
|
|
18
|
+
ref?: { source?: string; id?: string } | null;
|
|
19
|
+
link?: string | null;
|
|
20
|
+
} | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function findSource(key: string, sources: SourceLike[]): SourceLike | undefined {
|
|
24
|
+
const lastColon = key.lastIndexOf(':');
|
|
25
|
+
const idPart = lastColon > 0 ? key.slice(lastColon + 1) : key;
|
|
26
|
+
const datasetPart = lastColon > 0 ? key.slice(0, lastColon) : '';
|
|
27
|
+
|
|
28
|
+
return sources.find(s => {
|
|
29
|
+
if (s.id === key || s.id === idPart) return true;
|
|
30
|
+
const ref = s.origin?.ref;
|
|
31
|
+
if (ref) {
|
|
32
|
+
if (datasetPart && ref.source === datasetPart && ref.id === idPart) return true;
|
|
33
|
+
if (ref.id === key || ref.id === idPart) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('citeResolver DATASET:ID matching', () => {
|
|
40
|
+
const sources: SourceLike[] = [
|
|
41
|
+
{ id: null, origin: { ref: { source: 'IEV', id: '845-01-01' }, link: 'https://electropedia.org/...' } },
|
|
42
|
+
{ id: '845-01-01', origin: { ref: { source: 'IEV', id: '845-01-01' }, link: 'https://electropedia.org/...' } },
|
|
43
|
+
{ id: '702-02-07', origin: { ref: { source: 'IEV', id: '702-02-07' }, link: 'https://electropedia.org/...' } },
|
|
44
|
+
{ id: null, origin: { ref: { source: 'CIE S 017:2011', id: '17-370' } } },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
it('matches cite:IEV:845-01-01 via origin.ref', () => {
|
|
48
|
+
const s = findSource('IEV:845-01-01', sources);
|
|
49
|
+
expect(s).toBeDefined();
|
|
50
|
+
expect(s!.origin!.ref!.id).toBe('845-01-01');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('matches cite:IEV:702-02-07 via source id', () => {
|
|
54
|
+
const s = findSource('IEV:702-02-07', sources);
|
|
55
|
+
expect(s).toBeDefined();
|
|
56
|
+
expect(s!.id).toBe('702-02-07');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('matches cite:845-01-01 (bare ID without dataset prefix)', () => {
|
|
60
|
+
const s = findSource('845-01-01', sources);
|
|
61
|
+
expect(s).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('matches cite with CIE dataset prefix', () => {
|
|
65
|
+
const s = findSource('CIE S 017:2011:17-370', sources);
|
|
66
|
+
expect(s).toBeDefined();
|
|
67
|
+
expect(s!.origin!.ref!.id).toBe('17-370');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('returns undefined for unknown source', () => {
|
|
71
|
+
const s = findSource('UNKNOWN:999', sources);
|
|
72
|
+
expect(s).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -119,7 +119,17 @@ const conceptTags = computed(() => props.concept?.tags ?? []);
|
|
|
119
119
|
const factory = getFactory();
|
|
120
120
|
const { ready: bibReady, ensureBibLoaded, bibResolver, citeResolver, nonVerbalRefResolver } = useRenderOptions(
|
|
121
121
|
() => props.registerId,
|
|
122
|
-
() =>
|
|
122
|
+
() => {
|
|
123
|
+
// Collect sources from ALL localizations (shown separately per language,
|
|
124
|
+
// but the citeResolver needs access to all of them since content from
|
|
125
|
+
// every language is rendered on the same page).
|
|
126
|
+
const sources: any[] = [];
|
|
127
|
+
for (const lang of props.concept?.languages ?? []) {
|
|
128
|
+
const lc = props.concept?.localization(lang);
|
|
129
|
+
if (lc?.sources) sources.push(...lc.sources);
|
|
130
|
+
}
|
|
131
|
+
return sources;
|
|
132
|
+
},
|
|
123
133
|
);
|
|
124
134
|
|
|
125
135
|
const renderOpts = computed<RenderOptions>(() => {
|
|
@@ -65,7 +65,24 @@ export function useRenderOptions(
|
|
|
65
65
|
return `<span class="cite-ref">${escapeHtml(label ?? key)}</span>`;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// Parse DATASET:ID format. {{cite:IEV:702-02-07}} → dataset="IEV", idPart="702-02-07"
|
|
69
|
+
// Match against: raw id, id part, or origin.ref { source: dataset, id: idPart }
|
|
70
|
+
const lastColon = key.lastIndexOf(':');
|
|
71
|
+
const idPart = lastColon > 0 ? key.slice(lastColon + 1) : key;
|
|
72
|
+
const datasetPart = lastColon > 0 ? key.slice(0, lastColon) : '';
|
|
73
|
+
|
|
74
|
+
const source = sources.find(s => {
|
|
75
|
+
// Match by source id (raw or id part)
|
|
76
|
+
if (s.id === key || s.id === idPart) return true;
|
|
77
|
+
// Match by origin.ref
|
|
78
|
+
const ref = s.origin?.ref;
|
|
79
|
+
if (ref) {
|
|
80
|
+
if (datasetPart && ref.source === datasetPart && ref.id === idPart) return true;
|
|
81
|
+
if (ref.id === key || ref.id === idPart) return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
});
|
|
85
|
+
|
|
69
86
|
if (!source?.origin) {
|
|
70
87
|
return `<span class="cite-unresolved">${escapeHtml(label ?? key)}</span>`;
|
|
71
88
|
}
|