@glossarist/concept-browser 0.7.26 → 0.7.28
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 +1 -1
- package/scripts/generate-data.mjs +20 -1
- package/src/__tests__/annotations-bridge.test.ts +87 -0
- package/src/__tests__/dataset-adapter.test.ts +58 -0
- package/src/__tests__/designation-relationship.test.ts +211 -0
- package/src/__tests__/graph.test.ts +15 -0
- package/src/__tests__/relationship-comprehensive.test.ts +162 -0
- package/src/__tests__/taxonomy-colors.test.ts +71 -0
- package/src/adapters/model-bridge.ts +251 -27
- package/src/adapters/ontology-registry.ts +30 -2
- package/src/components/AppSidebar.vue +1 -1
- package/src/components/ConceptDetail.vue +32 -64
- package/src/components/DesignationList.vue +78 -0
- package/src/data/ontology-schema.json +66 -8
- package/src/data/taxonomies.json +261 -89
- package/src/i18n/locales/eng.yml +2 -0
- package/src/i18n/locales/fra.yml +2 -0
- package/src/stores/vocabulary.ts +3 -5
- package/src/style.css +8 -0
- package/src/utils/concept-helpers.ts +2 -20
- package/src/utils/designation-registry.ts +3 -26
- package/src/utils/relationship-categories.ts +68 -122
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
NonVerbRep,
|
|
24
24
|
RELATIONSHIP_TYPES,
|
|
25
25
|
DATE_TYPES,
|
|
26
|
+
ConceptRef,
|
|
26
27
|
} from 'glossarist';
|
|
27
28
|
import {
|
|
28
29
|
LetterSymbol,
|
|
@@ -36,6 +37,218 @@ import {
|
|
|
36
37
|
} from 'glossarist/models';
|
|
37
38
|
import type { ConceptSummary } from './types';
|
|
38
39
|
|
|
40
|
+
// ── JSON-LD wire-format types ─────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
interface JsonLdContent {
|
|
43
|
+
'gl:content'?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface JsonLdDate {
|
|
47
|
+
'gl:date'?: string;
|
|
48
|
+
'gl:dateType'?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface JsonLdPronunciation {
|
|
52
|
+
'gl:content'?: string;
|
|
53
|
+
'gl:language'?: string;
|
|
54
|
+
'gl:script'?: string;
|
|
55
|
+
'gl:system'?: string;
|
|
56
|
+
'gl:country'?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface JsonLdGrammarInfo {
|
|
60
|
+
'gl:gender'?: string;
|
|
61
|
+
'gl:number'?: string;
|
|
62
|
+
'gl:partOfSpeech'?: string;
|
|
63
|
+
'gl:noun'?: boolean;
|
|
64
|
+
'gl:verb'?: boolean;
|
|
65
|
+
'gl:adj'?: boolean;
|
|
66
|
+
'gl:adverb'?: boolean;
|
|
67
|
+
'gl:preposition'?: boolean;
|
|
68
|
+
'gl:participle'?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface JsonLdRef {
|
|
72
|
+
'gl:source'?: string;
|
|
73
|
+
'gl:id'?: string;
|
|
74
|
+
'gl:version'?: string;
|
|
75
|
+
'gl:text'?: string;
|
|
76
|
+
'source'?: string;
|
|
77
|
+
'id'?: string;
|
|
78
|
+
'version'?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface JsonLdLocality {
|
|
82
|
+
'gl:localityType'?: string;
|
|
83
|
+
'gl:referenceFrom'?: string;
|
|
84
|
+
'gl:referenceTo'?: string;
|
|
85
|
+
'type'?: string;
|
|
86
|
+
'reference_from'?: string;
|
|
87
|
+
'reference_to'?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface JsonLdOrigin {
|
|
91
|
+
'gl:ref'?: string | JsonLdRef;
|
|
92
|
+
'gl:locality'?: JsonLdLocality;
|
|
93
|
+
'gl:link'?: string;
|
|
94
|
+
'gl:id'?: string;
|
|
95
|
+
'gl:version'?: string;
|
|
96
|
+
'gl:source'?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface JsonLdSource {
|
|
100
|
+
'gl:sourceType'?: string;
|
|
101
|
+
'gl:sourceStatus'?: string;
|
|
102
|
+
'gl:modification'?: string;
|
|
103
|
+
'gl:origin'?: JsonLdOrigin;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface JsonLdRelated {
|
|
107
|
+
'gl:relationshipType'?: string;
|
|
108
|
+
'gl:ref'?: JsonLdRef;
|
|
109
|
+
'@id'?: string;
|
|
110
|
+
'gl:term'?: string;
|
|
111
|
+
'gl:target'?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface JsonLdDesignation {
|
|
115
|
+
'@type'?: string;
|
|
116
|
+
'gl:term'?: string;
|
|
117
|
+
'gl:normativeStatus'?: string;
|
|
118
|
+
'gl:absent'?: unknown;
|
|
119
|
+
'gl:fieldOfApplication'?: string;
|
|
120
|
+
'gl:usageInfo'?: string;
|
|
121
|
+
'gl:geographicalArea'?: string;
|
|
122
|
+
'gl:language'?: string;
|
|
123
|
+
'gl:script'?: string;
|
|
124
|
+
'gl:system'?: string;
|
|
125
|
+
'gl:international'?: boolean;
|
|
126
|
+
'gl:termType'?: string;
|
|
127
|
+
'gl:pronunciation'?: JsonLdPronunciation[];
|
|
128
|
+
'gl:source'?: JsonLdSource[];
|
|
129
|
+
'gl:related'?: JsonLdRelated[];
|
|
130
|
+
'gl:prefix'?: string;
|
|
131
|
+
'gl:gender'?: string;
|
|
132
|
+
'gl:grammarInfo'?: JsonLdGrammarInfo[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface JsonLdLocalizedConcept {
|
|
136
|
+
'gl:languageCode'?: string;
|
|
137
|
+
'gl:entryStatus'?: string;
|
|
138
|
+
'gl:classification'?: string;
|
|
139
|
+
'gl:reviewType'?: string;
|
|
140
|
+
'gl:domain'?: string;
|
|
141
|
+
'gl:release'?: string;
|
|
142
|
+
'gl:lineageSourceSimilarity'?: number;
|
|
143
|
+
'gl:script'?: string;
|
|
144
|
+
'gl:system'?: string;
|
|
145
|
+
'gl:designation'?: JsonLdDesignation[];
|
|
146
|
+
'gl:definition'?: JsonLdContent[];
|
|
147
|
+
'gl:notes'?: JsonLdContent[];
|
|
148
|
+
'gl:annotations'?: JsonLdContent[];
|
|
149
|
+
'gl:examples'?: JsonLdContent[];
|
|
150
|
+
'gl:source'?: JsonLdSource[];
|
|
151
|
+
'gl:dates'?: JsonLdDate[];
|
|
152
|
+
'gl:references'?: JsonLdRelated[];
|
|
153
|
+
'gl:reviewDate'?: string;
|
|
154
|
+
'gl:reviewDecisionDate'?: string;
|
|
155
|
+
'gl:reviewDecisionEvent'?: string;
|
|
156
|
+
'gl:reviewStatus'?: string;
|
|
157
|
+
'gl:reviewDecision'?: string;
|
|
158
|
+
'gl:reviewDecisionNotes'?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface JsonLdConcept {
|
|
162
|
+
'@type'?: string;
|
|
163
|
+
'@id'?: string;
|
|
164
|
+
'gl:identifier'?: string | number;
|
|
165
|
+
'gl:term'?: string;
|
|
166
|
+
'gl:localizedConcept'?: Record<string, JsonLdLocalizedConcept>;
|
|
167
|
+
'gl:related'?: JsonLdRelated[];
|
|
168
|
+
'gl:tags'?: string[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Bridges for fields not yet in glossarist-js ────────────────────────────
|
|
172
|
+
// Remove each bridge when glossarist-js publishes native support.
|
|
173
|
+
|
|
174
|
+
// Annotations: LocalizedConcept.annotations
|
|
175
|
+
const extraAnnotations = new WeakMap<LocalizedConcept, DetailedDefinition[]>();
|
|
176
|
+
|
|
177
|
+
export function getAnnotations(lc: LocalizedConcept): DetailedDefinition[] {
|
|
178
|
+
return extraAnnotations.get(lc) ?? [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Designation relationship targets: RelatedConcept.target (string)
|
|
182
|
+
const designationTargets = new WeakMap<RelatedConcept, string>();
|
|
183
|
+
|
|
184
|
+
export function getDesignationTarget(rc: RelatedConcept): string | null {
|
|
185
|
+
return designationTargets.get(rc) ?? null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ConceptRef text: human-readable label alongside source/id
|
|
189
|
+
const refTexts = new WeakMap<ConceptRef, string>();
|
|
190
|
+
|
|
191
|
+
export function getRefText(ref: ConceptRef): string | null {
|
|
192
|
+
return refTexts.get(ref) ?? null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Relationship types whose target is a designation string, not a concept ref.
|
|
196
|
+
const DESIGNATION_REL_TYPES = new Set(['abbreviated_form_for', 'short_form_for']);
|
|
197
|
+
|
|
198
|
+
function attachAnnotations(concept: Concept, localizations: Record<string, unknown>): void {
|
|
199
|
+
for (const lang of concept.languages) {
|
|
200
|
+
const lc = concept.localization(lang);
|
|
201
|
+
const raw = localizations[lang];
|
|
202
|
+
if (!lc || !raw || typeof raw !== 'object') continue;
|
|
203
|
+
const rawObj = raw as Record<string, unknown>;
|
|
204
|
+
|
|
205
|
+
// Annotations
|
|
206
|
+
const annList = rawObj.annotations;
|
|
207
|
+
if (Array.isArray(annList) && annList.length > 0) {
|
|
208
|
+
extraAnnotations.set(lc, annList.map((a: Record<string, unknown>) =>
|
|
209
|
+
DetailedDefinition.fromJSON({ content: (a.content as string) ?? '' }) as DetailedDefinition,
|
|
210
|
+
));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Designation-level relationship targets and ref text
|
|
214
|
+
const rawTerms = rawObj.terms;
|
|
215
|
+
if (Array.isArray(rawTerms)) {
|
|
216
|
+
for (let i = 0; i < lc.terms.length && i < rawTerms.length; i++) {
|
|
217
|
+
const rawTerm = rawTerms[i] as Record<string, unknown>;
|
|
218
|
+
const rawRelated = rawTerm.related;
|
|
219
|
+
if (!Array.isArray(rawRelated)) continue;
|
|
220
|
+
const designation = lc.terms[i];
|
|
221
|
+
for (let j = 0; j < designation.related.length && j < rawRelated.length; j++) {
|
|
222
|
+
const rawRel = rawRelated[j] as Record<string, unknown>;
|
|
223
|
+
const rc = designation.related[j];
|
|
224
|
+
if (rawRel.target && typeof rawRel.target === 'string') {
|
|
225
|
+
designationTargets.set(rc, rawRel.target);
|
|
226
|
+
}
|
|
227
|
+
if (rc.ref) {
|
|
228
|
+
const rawRef = rawRel.ref as Record<string, unknown> | undefined;
|
|
229
|
+
if (rawRef?.text && typeof rawRef.text === 'string') {
|
|
230
|
+
refTexts.set(rc.ref, rawRef.text);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Localization-level ref text
|
|
238
|
+
const rawRelated = rawObj.related;
|
|
239
|
+
if (Array.isArray(rawRelated)) {
|
|
240
|
+
for (let i = 0; i < lc.related.length && i < rawRelated.length; i++) {
|
|
241
|
+
const rc = lc.related[i];
|
|
242
|
+
const rawRel = rawRelated[i] as Record<string, unknown>;
|
|
243
|
+
const rawRef = rawRel.ref as Record<string, unknown> | undefined;
|
|
244
|
+
if (rc.ref && rawRef?.text && typeof rawRef.text === 'string') {
|
|
245
|
+
refTexts.set(rc.ref, rawRef.text);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
39
252
|
// ── Detection ─────────────────────────────────────────────────────────────
|
|
40
253
|
|
|
41
254
|
function isJsonLd(doc: Record<string, unknown>): boolean {
|
|
@@ -44,11 +257,10 @@ function isJsonLd(doc: Record<string, unknown>): boolean {
|
|
|
44
257
|
|
|
45
258
|
// ── JSON-LD → Glossarist native mapping ───────────────────────────────────
|
|
46
259
|
|
|
47
|
-
function mapDesignationFromJsonLd(d:
|
|
260
|
+
function mapDesignationFromJsonLd(d: JsonLdDesignation): Record<string, unknown> {
|
|
48
261
|
const result: Record<string, unknown> = {};
|
|
49
|
-
const rawType =
|
|
262
|
+
const rawType = d['@type'] || '';
|
|
50
263
|
|
|
51
|
-
// Map type
|
|
52
264
|
if (rawType.includes('Abbreviation')) result.type = 'abbreviation';
|
|
53
265
|
else if (rawType.includes('LetterSymbol')) result.type = 'letter_symbol';
|
|
54
266
|
else if (rawType.includes('GraphicalSymbol')) result.type = 'graphical_symbol';
|
|
@@ -69,7 +281,7 @@ function mapDesignationFromJsonLd(d: any): Record<string, unknown> {
|
|
|
69
281
|
if (d['gl:termType']) result.term_type = d['gl:termType'];
|
|
70
282
|
|
|
71
283
|
if (d['gl:pronunciation']?.length) {
|
|
72
|
-
result.pronunciation = d['gl:pronunciation'].map(
|
|
284
|
+
result.pronunciation = d['gl:pronunciation'].map(p => ({
|
|
73
285
|
content: p['gl:content'] ?? null,
|
|
74
286
|
language: p['gl:language'] ?? null,
|
|
75
287
|
script: p['gl:script'] ?? null,
|
|
@@ -83,16 +295,21 @@ function mapDesignationFromJsonLd(d: any): Record<string, unknown> {
|
|
|
83
295
|
}
|
|
84
296
|
|
|
85
297
|
if (d['gl:related']?.length) {
|
|
86
|
-
result.related = d['gl:related'].map(
|
|
298
|
+
result.related = d['gl:related'].map(r => {
|
|
299
|
+
const relType = r['gl:relationshipType'] ?? 'references';
|
|
300
|
+
if (DESIGNATION_REL_TYPES.has(relType) && r['gl:target']) {
|
|
301
|
+
return { type: relType, target: r['gl:target'] };
|
|
302
|
+
}
|
|
303
|
+
return mapRelatedFromJsonLd(r);
|
|
304
|
+
});
|
|
87
305
|
}
|
|
88
306
|
|
|
89
|
-
// Expression-specific
|
|
90
307
|
if (d['gl:prefix'] != null) result.prefix = d['gl:prefix'];
|
|
91
308
|
if (d['gl:gender']) {
|
|
92
309
|
result.grammar_info = [{ gender: d['gl:gender'] }];
|
|
93
310
|
}
|
|
94
311
|
if (d['gl:grammarInfo']?.length) {
|
|
95
|
-
result.grammar_info = d['gl:grammarInfo'].map(
|
|
312
|
+
result.grammar_info = d['gl:grammarInfo'].map(gi => ({
|
|
96
313
|
gender: gi['gl:gender'] ?? null,
|
|
97
314
|
number: gi['gl:number'] ?? null,
|
|
98
315
|
part_of_speech: gi['gl:partOfSpeech'] ?? null,
|
|
@@ -108,7 +325,7 @@ function mapDesignationFromJsonLd(d: any): Record<string, unknown> {
|
|
|
108
325
|
return result;
|
|
109
326
|
}
|
|
110
327
|
|
|
111
|
-
function mapSourceFromJsonLd(s:
|
|
328
|
+
function mapSourceFromJsonLd(s: JsonLdSource): Record<string, unknown> {
|
|
112
329
|
const result: Record<string, unknown> = {};
|
|
113
330
|
if (s['gl:sourceType']) result.type = s['gl:sourceType'];
|
|
114
331
|
if (s['gl:sourceStatus']) result.status = s['gl:sourceStatus'];
|
|
@@ -120,7 +337,6 @@ function mapSourceFromJsonLd(s: any): Record<string, unknown> {
|
|
|
120
337
|
if (o['gl:ref']) {
|
|
121
338
|
const rawRef = o['gl:ref'];
|
|
122
339
|
if (typeof rawRef === 'string') {
|
|
123
|
-
// Legacy format: gl:ref is a plain string (e.g. "ISO/TS 14812:2022")
|
|
124
340
|
origin.ref = { source: rawRef };
|
|
125
341
|
} else {
|
|
126
342
|
const refObj: Record<string, unknown> = {};
|
|
@@ -154,7 +370,7 @@ function mapSourceFromJsonLd(s: any): Record<string, unknown> {
|
|
|
154
370
|
return result;
|
|
155
371
|
}
|
|
156
372
|
|
|
157
|
-
function mapRelatedFromJsonLd(r:
|
|
373
|
+
function mapRelatedFromJsonLd(r: JsonLdRelated): Record<string, unknown> {
|
|
158
374
|
const result: Record<string, unknown> = { type: 'references' };
|
|
159
375
|
|
|
160
376
|
if (r['gl:relationshipType']) {
|
|
@@ -168,11 +384,12 @@ function mapRelatedFromJsonLd(r: any): Record<string, unknown> {
|
|
|
168
384
|
if (ref['gl:id']) refObj.id = ref['gl:id'];
|
|
169
385
|
if (ref['source']) refObj.source = ref['source'];
|
|
170
386
|
if (ref['id']) refObj.id = ref['id'];
|
|
387
|
+
if (ref['gl:text']) refObj.text = ref['gl:text'];
|
|
171
388
|
if (Object.keys(refObj).length > 0) result.ref = refObj;
|
|
172
389
|
}
|
|
173
390
|
|
|
174
391
|
if (!result.ref && r['@id']) {
|
|
175
|
-
const uri = r['@id']
|
|
392
|
+
const uri = r['@id'];
|
|
176
393
|
const idMatch = uri.match(/\/concept\/([^/]+)$/);
|
|
177
394
|
result.ref = idMatch
|
|
178
395
|
? { source: uri.split('/').slice(-3, -2)[0] || '', id: idMatch[1] }
|
|
@@ -182,7 +399,7 @@ function mapRelatedFromJsonLd(r: any): Record<string, unknown> {
|
|
|
182
399
|
return result;
|
|
183
400
|
}
|
|
184
401
|
|
|
185
|
-
function mapLocalizedFromJsonLd(lc:
|
|
402
|
+
function mapLocalizedFromJsonLd(lc: JsonLdLocalizedConcept): Record<string, unknown> {
|
|
186
403
|
const data: Record<string, unknown> = {};
|
|
187
404
|
|
|
188
405
|
if (lc['gl:languageCode']) data.language_code = lc['gl:languageCode'];
|
|
@@ -200,18 +417,21 @@ function mapLocalizedFromJsonLd(lc: any): Record<string, unknown> {
|
|
|
200
417
|
}
|
|
201
418
|
|
|
202
419
|
if (lc['gl:definition']?.length) {
|
|
203
|
-
data.definition = lc['gl:definition'].map(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
});
|
|
420
|
+
data.definition = lc['gl:definition'].map(d => ({
|
|
421
|
+
content: d['gl:content'] ?? '',
|
|
422
|
+
}));
|
|
207
423
|
}
|
|
208
424
|
|
|
209
425
|
if (lc['gl:notes']?.length) {
|
|
210
|
-
data.notes = lc['gl:notes'].map(
|
|
426
|
+
data.notes = lc['gl:notes'].map(n => ({ content: n['gl:content'] ?? '' }));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (lc['gl:annotations']?.length) {
|
|
430
|
+
data.annotations = lc['gl:annotations'].map(a => ({ content: a['gl:content'] ?? '' }));
|
|
211
431
|
}
|
|
212
432
|
|
|
213
433
|
if (lc['gl:examples']?.length) {
|
|
214
|
-
data.examples = lc['gl:examples'].map(
|
|
434
|
+
data.examples = lc['gl:examples'].map(e => ({ content: e['gl:content'] ?? '' }));
|
|
215
435
|
}
|
|
216
436
|
|
|
217
437
|
if (lc['gl:source']?.length) {
|
|
@@ -219,7 +439,7 @@ function mapLocalizedFromJsonLd(lc: any): Record<string, unknown> {
|
|
|
219
439
|
}
|
|
220
440
|
|
|
221
441
|
if (lc['gl:dates']?.length) {
|
|
222
|
-
data.dates = lc['gl:dates'].map(
|
|
442
|
+
data.dates = lc['gl:dates'].map(d => ({
|
|
223
443
|
date: d['gl:date'] ?? null,
|
|
224
444
|
type: d['gl:dateType'] ?? null,
|
|
225
445
|
}));
|
|
@@ -229,7 +449,6 @@ function mapLocalizedFromJsonLd(lc: any): Record<string, unknown> {
|
|
|
229
449
|
data.related = lc['gl:references'].map(mapRelatedFromJsonLd);
|
|
230
450
|
}
|
|
231
451
|
|
|
232
|
-
// Review metadata — passed through to LocalizedConcept constructor
|
|
233
452
|
if (lc['gl:reviewDate']) data.review_date = lc['gl:reviewDate'];
|
|
234
453
|
if (lc['gl:reviewDecisionDate']) data.review_decision_date = lc['gl:reviewDecisionDate'];
|
|
235
454
|
if (lc['gl:reviewDecisionEvent']) data.review_decision_event = lc['gl:reviewDecisionEvent'];
|
|
@@ -240,9 +459,9 @@ function mapLocalizedFromJsonLd(lc: any): Record<string, unknown> {
|
|
|
240
459
|
return data;
|
|
241
460
|
}
|
|
242
461
|
|
|
243
|
-
function conceptFromJsonLd(doc:
|
|
462
|
+
function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
244
463
|
const id = String(doc['gl:identifier'] ?? doc['@id']?.split('/').pop() ?? '');
|
|
245
|
-
const localizations: Record<string,
|
|
464
|
+
const localizations: Record<string, unknown> = {};
|
|
246
465
|
|
|
247
466
|
const rawLc = doc['gl:localizedConcept'] ?? {};
|
|
248
467
|
for (const [lang, lc] of Object.entries(rawLc)) {
|
|
@@ -254,7 +473,7 @@ function conceptFromJsonLd(doc: Record<string, any>): Concept {
|
|
|
254
473
|
const related = (doc['gl:related'] ?? []).map(mapRelatedFromJsonLd);
|
|
255
474
|
const tags = Array.isArray(doc['gl:tags']) ? [...doc['gl:tags']] : [];
|
|
256
475
|
|
|
257
|
-
|
|
476
|
+
const concept = Concept.fromJSON({
|
|
258
477
|
id,
|
|
259
478
|
term: doc['gl:term'] ?? null,
|
|
260
479
|
uri: doc['@id'] ?? null,
|
|
@@ -263,16 +482,21 @@ function conceptFromJsonLd(doc: Record<string, any>): Concept {
|
|
|
263
482
|
tags,
|
|
264
483
|
status: null,
|
|
265
484
|
});
|
|
485
|
+
|
|
486
|
+
attachAnnotations(concept, localizations);
|
|
487
|
+
return concept;
|
|
266
488
|
}
|
|
267
489
|
|
|
268
490
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
269
491
|
|
|
270
|
-
export function conceptFromJson(doc: Record<string,
|
|
492
|
+
export function conceptFromJson(doc: Record<string, unknown>): Concept {
|
|
271
493
|
if (isJsonLd(doc)) {
|
|
272
|
-
return conceptFromJsonLd(doc);
|
|
494
|
+
return conceptFromJsonLd(doc as JsonLdConcept);
|
|
273
495
|
}
|
|
274
|
-
|
|
275
|
-
|
|
496
|
+
const concept = Concept.fromJSON(doc);
|
|
497
|
+
const locs = (doc as Record<string, unknown>).localizations as Record<string, unknown> | undefined;
|
|
498
|
+
if (locs) attachAnnotations(concept, locs);
|
|
499
|
+
return concept;
|
|
276
500
|
}
|
|
277
501
|
|
|
278
502
|
export function conceptToSummary(concept: Concept): ConceptSummary {
|
|
@@ -14,12 +14,20 @@ export interface TaxonomyConcept {
|
|
|
14
14
|
altLabel?: string;
|
|
15
15
|
definition?: string;
|
|
16
16
|
broader?: string;
|
|
17
|
+
category?: string;
|
|
18
|
+
inverseOf?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TaxonomyCategory {
|
|
22
|
+
label: string;
|
|
23
|
+
color: string;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
export interface Taxonomy {
|
|
20
27
|
scheme: string;
|
|
21
28
|
schemeLabel: string | null;
|
|
22
29
|
schemeDefinition: string | null;
|
|
30
|
+
categories?: Record<string, TaxonomyCategory>;
|
|
23
31
|
concepts: Record<string, TaxonomyConcept>;
|
|
24
32
|
}
|
|
25
33
|
|
|
@@ -61,15 +69,35 @@ export class OntologyRegistry {
|
|
|
61
69
|
return id in (this.data[taxonomy]?.concepts ?? {});
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
/** Get broader concept ID, if any (for hierarchical taxonomies like designation-type). */
|
|
65
72
|
getBroader(taxonomy: TaxonomyKey, id: string): string | null {
|
|
66
73
|
return this.getConcept(taxonomy, id)?.broader ?? null;
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
/** Get all child concept IDs of a given concept. */
|
|
70
76
|
getNarrower(taxonomy: TaxonomyKey, id: string): TaxonomyConcept[] {
|
|
71
77
|
return this.getAll(taxonomy).filter(c => c.broader === id);
|
|
72
78
|
}
|
|
79
|
+
|
|
80
|
+
getCategory(taxonomy: TaxonomyKey, id: string): string | null {
|
|
81
|
+
return this.getConcept(taxonomy, id)?.category ?? null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getInverse(taxonomy: TaxonomyKey, id: string): string | null {
|
|
85
|
+
return this.getConcept(taxonomy, id)?.inverseOf ?? null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getCategoryConfig(taxonomy: TaxonomyKey, categoryId: string): TaxonomyCategory | null {
|
|
89
|
+
return this.data[taxonomy]?.categories?.[categoryId] ?? null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getCategories(taxonomy: TaxonomyKey): Record<string, TaxonomyCategory> {
|
|
93
|
+
return this.data[taxonomy]?.categories ?? {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getColor(taxonomy: TaxonomyKey, id: string): string | null {
|
|
97
|
+
const entry = this.data[taxonomy] as unknown as Record<string, unknown>;
|
|
98
|
+
const colors = entry?.colors as Record<string, string> | undefined;
|
|
99
|
+
return colors?.[id] ?? null;
|
|
100
|
+
}
|
|
73
101
|
}
|
|
74
102
|
|
|
75
103
|
export const ontology = new OntologyRegistry();
|
|
@@ -252,7 +252,7 @@ const activeSectionId = computed(() => {
|
|
|
252
252
|
<button
|
|
253
253
|
v-if="group.label"
|
|
254
254
|
@click="toggleGroup(group.id)"
|
|
255
|
-
class="w-full flex items-start gap-1.5 px-2 py-1.5 rounded-lg text-xs font-semibold transition-colors hover:bg-ink-50"
|
|
255
|
+
class="sidebar-group-label w-full flex items-start gap-1.5 px-2 py-1.5 rounded-lg text-xs font-semibold transition-colors hover:bg-ink-50"
|
|
256
256
|
:style="group.color ? { color: group.color } : {}"
|
|
257
257
|
>
|
|
258
258
|
<span class="w-3 text-[10px] mt-0.5 flex-shrink-0">{{ isGroupExpanded(group.id) ? '▾' : '▸' }}</span>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { Concept, LocalizedConcept, Designation,
|
|
2
|
+
import type { Concept, LocalizedConcept, Designation, ConceptSource } from 'glossarist';
|
|
3
3
|
import type { Manifest, GraphEdge } from '../adapters/types';
|
|
4
4
|
import { computed, ref, nextTick, watch } from 'vue';
|
|
5
5
|
import { langName, langLabel, sortLanguages } from '../utils/lang';
|
|
@@ -7,8 +7,8 @@ import { renderMath, cleanContent } from '../utils/math';
|
|
|
7
7
|
import type { RenderOptions } from '../utils/math';
|
|
8
8
|
import { escapeAttr } from '../utils/escape';
|
|
9
9
|
import { entryStatusColor, conceptStatusColor, conceptStatusLabel, conceptStatusDefinition, entryStatusLabel, entryStatusDefinition, getPreferredTerm } from '../utils/concept-helpers';
|
|
10
|
-
import {
|
|
11
|
-
import { conceptUri } from '../adapters/model-bridge';
|
|
10
|
+
import { sourceTypeInfo, sourceStatusInfo } from '../utils/designation-registry';
|
|
11
|
+
import { conceptUri, getAnnotations } from '../adapters/model-bridge';
|
|
12
12
|
import { useRouter } from 'vue-router';
|
|
13
13
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
14
14
|
import { useDsStyle } from '../utils/dataset-style';
|
|
@@ -21,6 +21,7 @@ import ConceptRdfView from './ConceptRdfView.vue';
|
|
|
21
21
|
import FormatDownloads from './FormatDownloads.vue';
|
|
22
22
|
import NonVerbalRepDisplay from './NonVerbalRepDisplay.vue';
|
|
23
23
|
import CitationDisplay from './CitationDisplay.vue';
|
|
24
|
+
import DesignationList from './DesignationList.vue';
|
|
24
25
|
import { useI18n } from '../i18n';
|
|
25
26
|
|
|
26
27
|
const { t, locale } = useI18n();
|
|
@@ -143,7 +144,7 @@ async function navigateRelated(ref: { source: string | null; id: string | null }
|
|
|
143
144
|
router.push({ name: 'concept', params: { registerId: target.registerId, conceptId: target.conceptId } });
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
function relatedLabel(dr: { content?: string; ref?: { source: string | null; id: string | null } | null }): string {
|
|
147
|
+
function relatedLabel(dr: { content?: string | null; ref?: { source: string | null; id: string | null } | null }): string {
|
|
147
148
|
if (dr.content) return dr.content;
|
|
148
149
|
const resolved = dr.ref ? getResolvedRef(dr.ref).target : null;
|
|
149
150
|
if (resolved) {
|
|
@@ -200,6 +201,8 @@ interface LangContent {
|
|
|
200
201
|
renderedTerm: string;
|
|
201
202
|
definition: string;
|
|
202
203
|
renderedDefinition: string;
|
|
204
|
+
annotations: string[];
|
|
205
|
+
renderedAnnotations: string[];
|
|
203
206
|
notes: string[];
|
|
204
207
|
renderedNotes: string[];
|
|
205
208
|
examples: string[];
|
|
@@ -224,6 +227,7 @@ const allLangContent = computed(() => {
|
|
|
224
227
|
|
|
225
228
|
const definition = lc.definitions
|
|
226
229
|
.map(d => d.content).filter(Boolean).join('\n\n');
|
|
230
|
+
const annotations = getAnnotations(lc).map(a => a.content).filter(Boolean);
|
|
227
231
|
const notes = lc.notes.map(n => n.content).filter(Boolean);
|
|
228
232
|
const examples = lc.examples.map(e => e.content).filter(Boolean);
|
|
229
233
|
|
|
@@ -233,6 +237,8 @@ const allLangContent = computed(() => {
|
|
|
233
237
|
renderedTerm: renderMath(getPreferredTerm(lc, '')),
|
|
234
238
|
definition,
|
|
235
239
|
renderedDefinition: renderMath(definition, renderOpts),
|
|
240
|
+
annotations,
|
|
241
|
+
renderedAnnotations: annotations.map((a: string) => renderMath(a, renderOpts)),
|
|
236
242
|
notes,
|
|
237
243
|
renderedNotes: notes.map(n => renderMath(n, renderOpts)),
|
|
238
244
|
examples,
|
|
@@ -259,7 +265,7 @@ const langContentMap = computed(() => {
|
|
|
259
265
|
});
|
|
260
266
|
|
|
261
267
|
function hasContent(lc: LangContent): boolean {
|
|
262
|
-
return !!(lc.definition || lc.notes.length || lc.examples.length || lc.sources.length);
|
|
268
|
+
return !!(lc.definition || lc.annotations.length || lc.notes.length || lc.examples.length || lc.sources.length);
|
|
263
269
|
}
|
|
264
270
|
|
|
265
271
|
function initCollapsed() {
|
|
@@ -364,7 +370,7 @@ const edgeDisplayCache = computed(() => {
|
|
|
364
370
|
});
|
|
365
371
|
|
|
366
372
|
function getEdgeDisplay(uri: string): EdgeDisplay {
|
|
367
|
-
return edgeDisplayCache.value.get(uri) ?? { uri, conceptId: uri, tooltip: uri, isLocal: false, badge: null };
|
|
373
|
+
return edgeDisplayCache.value.get(uri) ?? { uri, conceptId: uri, designation: "", tooltip: uri, isLocal: false, badge: null };
|
|
368
374
|
}
|
|
369
375
|
|
|
370
376
|
interface ResolvedRef {
|
|
@@ -402,7 +408,7 @@ async function navigateEdge(edge: GraphEdge) {
|
|
|
402
408
|
|
|
403
409
|
function navigateDomain(domain: { slug: string; conceptId?: string }) {
|
|
404
410
|
const sectionId = domain.conceptId || domain.slug;
|
|
405
|
-
router.push({ name: 'dataset', params: { registerId: manifest.id }, query: { section: sectionId } });
|
|
411
|
+
router.push({ name: 'dataset', params: { registerId: props.manifest.id }, query: { section: sectionId } });
|
|
406
412
|
}
|
|
407
413
|
|
|
408
414
|
function getTermForLang(lang: string): string {
|
|
@@ -623,76 +629,38 @@ const nonVerbalReps = computed(() => {
|
|
|
623
629
|
<div v-if="hasContent(lc) && collapsedLangs.has(lc.lang)" class="px-3 sm:px-4 pb-3 -mt-0.5">
|
|
624
630
|
<p v-if="lc.definition" class="text-xs text-ink-300 leading-relaxed pl-[22px]">{{ plainTruncate(lc.definition) }}</p>
|
|
625
631
|
<p v-else class="text-xs text-ink-200 leading-relaxed pl-[22px]">
|
|
632
|
+
<template v-if="lc.annotations.length">{{ lc.annotations.length }} annotation{{ lc.annotations.length > 1 ? 's' : '' }}</template>
|
|
633
|
+
<template v-if="lc.annotations.length && lc.notes.length"> · </template>
|
|
626
634
|
<template v-if="lc.notes.length">{{ lc.notes.length }} note{{ lc.notes.length > 1 ? 's' : '' }}</template>
|
|
627
|
-
<template v-if="lc.notes.length && lc.examples.length"> · </template>
|
|
635
|
+
<template v-if="(lc.annotations.length || lc.notes.length) && lc.examples.length"> · </template>
|
|
628
636
|
<template v-if="lc.examples.length">{{ lc.examples.length }} example{{ lc.examples.length > 1 ? 's' : '' }}</template>
|
|
629
637
|
</p>
|
|
630
638
|
</div>
|
|
631
639
|
|
|
632
640
|
<!-- Expandable content -->
|
|
633
641
|
<div v-if="hasContent(lc)" v-show="!collapsedLangs.has(lc.lang)" class="lang-content px-3 sm:px-4 pb-4 space-y-3">
|
|
634
|
-
<!-- Designations
|
|
635
|
-
<
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
<template v-if="abbreviationDetails(d).length">
|
|
643
|
-
<span v-for="abbr in abbreviationDetails(d)" :key="abbr" class="badge text-[10px] bg-amber-50 text-amber-600">{{ abbr }}</span>
|
|
644
|
-
</template>
|
|
645
|
-
<!-- Term type (ISO 12620) -->
|
|
646
|
-
<span v-if="d.termType" class="badge text-[10px] bg-gray-50 text-gray-600" :title="termTypeInfo(d.termType).definition ?? ''">{{ termTypeInfo(d.termType).label }}</span>
|
|
647
|
-
<!-- Grammar info -->
|
|
648
|
-
<template v-if="d.type === 'expression' && (d as Expression).grammarInfo?.length">
|
|
649
|
-
<template v-for="(gi, giIdx) in (d as Expression).grammarInfo" :key="giIdx">
|
|
650
|
-
<span v-for="badge in grammarBadges(gi)" :key="giIdx + '-' + badge.label"
|
|
651
|
-
class="badge text-[10px] bg-gray-50 text-gray-600" :title="badge.definition ?? ''">{{ badge.label }}</span>
|
|
652
|
-
</template>
|
|
653
|
-
</template>
|
|
654
|
-
<!-- Pronunciation -->
|
|
655
|
-
<template v-if="d.pronunciations?.length">
|
|
656
|
-
<span v-for="(p, pi) in d.pronunciations" :key="'p'+pi"
|
|
657
|
-
class="text-xs text-ink-400 font-mono" :title="pronunciationTooltip(p)">{{ pronunciationLabel(p) }}</span>
|
|
658
|
-
</template>
|
|
659
|
-
<!-- Flags -->
|
|
660
|
-
<span v-if="d.international" class="badge text-[10px] bg-sky-50 text-sky-600">international</span>
|
|
661
|
-
<span v-if="d.absent" class="badge text-[10px] bg-red-50 text-red-600">absent</span>
|
|
662
|
-
<span v-if="d.geographicalArea" class="badge text-[10px] bg-gray-50 text-gray-600">{{ d.geographicalArea }}</span>
|
|
663
|
-
<span v-if="d.usageInfo" class="text-xs text-ink-300">{{ d.usageInfo }}</span>
|
|
664
|
-
<span v-if="d.fieldOfApplication" class="text-xs text-ink-300">field: {{ d.fieldOfApplication }}</span>
|
|
665
|
-
<!-- Per-designation language/script/system overrides -->
|
|
666
|
-
<template v-if="d.language && d.language !== lc.lang">
|
|
667
|
-
<span class="badge text-[10px] bg-teal-50 text-teal-600">lang: {{ langName(d.language) }}</span>
|
|
668
|
-
</template>
|
|
669
|
-
<span v-if="d.script" class="badge text-[10px] bg-gray-50 text-gray-600">script: {{ d.script }}</span>
|
|
670
|
-
<span v-if="d.system" class="badge text-[10px] bg-gray-50 text-gray-600">system: {{ d.system }}</span>
|
|
671
|
-
</div>
|
|
672
|
-
<!-- Designation sources -->
|
|
673
|
-
<div v-if="d.sources?.length" class="mt-1 space-y-0.5">
|
|
674
|
-
<div v-for="(ds, dsi) in d.sources" :key="'ds'+dsi" class="text-xs text-ink-400 flex items-center gap-1.5">
|
|
675
|
-
<span v-if="ds.type" class="badge text-[9px]" :class="sourceTypeInfo(ds.type).color">{{ sourceTypeInfo(ds.type).label }}</span>
|
|
676
|
-
<CitationDisplay v-if="ds.origin" :citation="ds.origin" :register-id="registerId" />
|
|
677
|
-
<span v-else-if="ds.modification" class="text-ink-300">{{ ds.modification }}</span>
|
|
678
|
-
</div>
|
|
679
|
-
</div>
|
|
680
|
-
<!-- Designation relationships -->
|
|
681
|
-
<div v-if="d.related?.length" class="mt-0.5 space-y-0.5">
|
|
682
|
-
<div v-for="(dr, dri) in d.related" :key="'dr'+dri" class="text-xs text-ink-400 flex items-center gap-1.5">
|
|
683
|
-
<span class="badge text-[9px] bg-gray-50 text-gray-600">{{ relationshipLabel(dr.type) }}</span>
|
|
684
|
-
<button v-if="getResolvedRef(dr.ref).target" @click="navigateRelated(dr.ref!)" class="concept-link">{{ relatedLabel(dr) }}</button>
|
|
685
|
-
<span v-else>{{ relatedLabel(dr) }}</span>
|
|
686
|
-
</div>
|
|
687
|
-
</div>
|
|
688
|
-
</div>
|
|
689
|
-
</div>
|
|
642
|
+
<!-- Designations -->
|
|
643
|
+
<DesignationList
|
|
644
|
+
:designations="orderedDesignations(lc.lang)"
|
|
645
|
+
:rendered-designations="lc.renderedDesignations"
|
|
646
|
+
:lang="lc.lang"
|
|
647
|
+
:register-id="registerId"
|
|
648
|
+
@navigate-related="(ref) => navigateRelated(ref)"
|
|
649
|
+
/>
|
|
690
650
|
|
|
691
651
|
<!-- Definition -->
|
|
692
652
|
<div v-if="lc.definition" class="p-4 rounded-lg bg-surface border-l-2" :style="{ borderLeftColor: getColor(manifest.id) }">
|
|
693
653
|
<div class="text-ink-800 leading-relaxed" v-html="lc.renderedDefinition"></div>
|
|
694
654
|
</div>
|
|
695
655
|
|
|
656
|
+
<!-- Annotations -->
|
|
657
|
+
<div v-if="lc.annotations.length" class="space-y-2">
|
|
658
|
+
<div v-for="(_, i) in lc.annotations" :key="'ann-'+i" class="text-ink-500 text-sm leading-relaxed italic pl-3 border-l-2 border-ink-200">
|
|
659
|
+
<span class="font-medium text-ink-400 text-xs uppercase tracking-wide not-italic">{{ t('concept.annotation') }}</span>
|
|
660
|
+
<div class="mt-1 not-italic" v-html="lc.renderedAnnotations[i]"></div>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
|
|
696
664
|
<!-- Notes -->
|
|
697
665
|
<div v-if="lc.notes.length" class="space-y-2">
|
|
698
666
|
<div v-for="(_, i) in lc.notes" :key="i" class="text-ink-600 text-sm leading-relaxed">
|