@glossarist/concept-browser 0.3.4 → 0.4.0

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.
Files changed (81) hide show
  1. package/README.md +3 -2
  2. package/cli/index.mjs +2 -1
  3. package/env.d.ts +5 -0
  4. package/package.json +4 -3
  5. package/scripts/build-edges.js +78 -10
  6. package/scripts/generate-data.mjs +152 -20
  7. package/scripts/generate-ontology-data.mjs +184 -0
  8. package/scripts/generate-ontology-schema.mjs +315 -0
  9. package/src/__tests__/about-view.test.ts +98 -0
  10. package/src/__tests__/app-footer.test.ts +38 -0
  11. package/src/__tests__/app-header.test.ts +130 -0
  12. package/src/__tests__/app-sidebar.test.ts +159 -0
  13. package/src/__tests__/asciidoc-lite.test.ts +1 -1
  14. package/src/__tests__/concept-card.test.ts +115 -0
  15. package/src/__tests__/concept-detail-interaction.test.ts +273 -0
  16. package/src/__tests__/concept-formats.test.ts +32 -30
  17. package/src/__tests__/concept-timeline.test.ts +200 -0
  18. package/src/__tests__/concept-view.test.ts +88 -0
  19. package/src/__tests__/contributors-view.test.ts +103 -0
  20. package/src/__tests__/dataset-adapter.test.ts +172 -23
  21. package/src/__tests__/dataset-view.test.ts +232 -0
  22. package/src/__tests__/designation-registry.test.ts +161 -0
  23. package/src/__tests__/format-downloads.test.ts +98 -0
  24. package/src/__tests__/graph-view.test.ts +69 -0
  25. package/src/__tests__/graph.test.ts +62 -0
  26. package/src/__tests__/home-interaction.test.ts +157 -0
  27. package/src/__tests__/language-detail.test.ts +203 -0
  28. package/src/__tests__/nav-icon.test.ts +48 -0
  29. package/src/__tests__/news-view.test.ts +87 -0
  30. package/src/__tests__/ontology-registry.test.ts +109 -0
  31. package/src/__tests__/page-view.test.ts +83 -0
  32. package/src/__tests__/relationship-categories.test.ts +62 -0
  33. package/src/__tests__/resolve-view.test.ts +77 -0
  34. package/src/__tests__/router.test.ts +65 -0
  35. package/src/__tests__/search-bar.test.ts +219 -0
  36. package/src/__tests__/search-view.test.ts +41 -0
  37. package/src/__tests__/stats-view.test.ts +77 -0
  38. package/src/__tests__/test-helpers.ts +171 -0
  39. package/src/__tests__/ui-store.test.ts +100 -0
  40. package/src/__tests__/v-math.test.ts +8 -7
  41. package/src/adapters/DatasetAdapter.ts +188 -63
  42. package/src/adapters/model-bridge.ts +277 -0
  43. package/src/adapters/ontology-registry.ts +75 -0
  44. package/src/adapters/ontology-schema.ts +100 -0
  45. package/src/adapters/types.ts +53 -78
  46. package/src/components/AppSidebar.vue +1 -1
  47. package/src/components/CitationDisplay.vue +35 -0
  48. package/src/components/ConceptDetail.vue +349 -146
  49. package/src/components/ConceptRdfView.vue +397 -0
  50. package/src/components/ConceptTimeline.vue +57 -60
  51. package/src/components/GraphPanel.vue +96 -31
  52. package/src/components/LanguageDetail.vue +46 -61
  53. package/src/components/NavIcon.vue +1 -0
  54. package/src/components/NonVerbalRepDisplay.vue +38 -0
  55. package/src/components/RelationshipList.vue +99 -0
  56. package/src/composables/use-render-options.ts +1 -4
  57. package/src/config/use-site-config.ts +3 -0
  58. package/src/data/ontology-schema.json +1551 -0
  59. package/src/data/taxonomies.json +543 -0
  60. package/src/graph/GraphEngine.ts +7 -4
  61. package/src/router/index.ts +6 -1
  62. package/src/shims/empty.ts +1 -0
  63. package/src/shims/node-crypto.ts +6 -0
  64. package/src/shims/node-path.ts +10 -0
  65. package/src/stores/vocabulary.ts +82 -32
  66. package/src/style.css +74 -20
  67. package/src/utils/asciidoc-lite.ts +17 -19
  68. package/src/utils/concept-formats.ts +22 -20
  69. package/src/utils/concept-helpers.ts +54 -0
  70. package/src/utils/designation-registry.ts +124 -0
  71. package/src/utils/escape.ts +7 -0
  72. package/src/utils/markdown-lite.ts +1 -3
  73. package/src/utils/math.ts +2 -11
  74. package/src/utils/plurimath.ts +2 -7
  75. package/src/utils/relationship-categories.ts +84 -0
  76. package/src/views/ConceptView.vue +22 -1
  77. package/src/views/DatasetView.vue +7 -2
  78. package/src/views/OntologySchemaView.vue +302 -0
  79. package/src/views/PageView.vue +28 -17
  80. package/src/views/StatsView.vue +34 -12
  81. package/vite.config.ts +8 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Model bridge: converts between wire-format JSON and glossarist-js model instances.
3
+ *
4
+ * Supports two input formats:
5
+ * 1. JSON-LD (gl:-prefixed) — legacy format from current generate-data.mjs
6
+ * 2. Glossarist native — snake_case format from glossarist-js Concept.toJSON()
7
+ *
8
+ * All downstream code works exclusively with Concept instances.
9
+ */
10
+ import {
11
+ Concept,
12
+ LocalizedConcept,
13
+ Designation,
14
+ Expression,
15
+ Abbreviation,
16
+ Symbol as SymbolDesignation,
17
+ GraphicalSymbol,
18
+ Citation,
19
+ ConceptSource,
20
+ RelatedConcept,
21
+ ConceptDate,
22
+ DetailedDefinition,
23
+ NonVerbRep,
24
+ RELATIONSHIP_TYPES,
25
+ DATE_TYPES,
26
+ } from 'glossarist';
27
+ import {
28
+ LetterSymbol,
29
+ GrammarInfo,
30
+ Pronunciation,
31
+ ConceptReference,
32
+ Locality,
33
+ GRAMMAR_GENDERS,
34
+ GRAMMAR_NUMBERS,
35
+ GRAMMAR_PARTS_OF_SPEECH,
36
+ } from 'glossarist/models';
37
+ import type { ConceptSummary } from './types';
38
+
39
+ // ── Detection ─────────────────────────────────────────────────────────────
40
+
41
+ function isJsonLd(doc: Record<string, unknown>): boolean {
42
+ return '@type' in doc && 'gl:localizedConcept' in doc;
43
+ }
44
+
45
+ // ── JSON-LD → Glossarist native mapping ───────────────────────────────────
46
+
47
+ function mapDesignationFromJsonLd(d: any): Record<string, unknown> {
48
+ const result: Record<string, unknown> = {};
49
+ const rawType = (d['@type'] as string) || '';
50
+
51
+ // Map type
52
+ if (rawType.includes('Abbreviation')) result.type = 'abbreviation';
53
+ else if (rawType.includes('LetterSymbol')) result.type = 'letter_symbol';
54
+ else if (rawType.includes('GraphicalSymbol')) result.type = 'graphical_symbol';
55
+ else if (rawType.includes('Symbol')) result.type = 'symbol';
56
+ else result.type = 'expression';
57
+
58
+ result.designation = d['gl:term'] ?? '';
59
+ result.normative_status = d['gl:normativeStatus'] ?? null;
60
+
61
+ if (d['gl:absent'] != null) result.absent = d['gl:absent'];
62
+ if (d['gl:fieldOfApplication']) result.field_of_application = d['gl:fieldOfApplication'];
63
+ if (d['gl:usageInfo']) result.usage_info = d['gl:usageInfo'];
64
+ if (d['gl:geographicalArea']) result.geographical_area = d['gl:geographicalArea'];
65
+ if (d['gl:language']) result.language = d['gl:language'];
66
+ if (d['gl:script']) result.script = d['gl:script'];
67
+ if (d['gl:system']) result.system = d['gl:system'];
68
+ if (d['gl:international'] != null) result.international = d['gl:international'];
69
+ if (d['gl:termType']) result.term_type = d['gl:termType'];
70
+
71
+ if (d['gl:pronunciation']?.length) {
72
+ result.pronunciation = d['gl:pronunciation'].map((p: any) => ({
73
+ content: p['gl:content'] ?? null,
74
+ language: p['gl:language'] ?? null,
75
+ script: p['gl:script'] ?? null,
76
+ system: p['gl:system'] ?? null,
77
+ country: p['gl:country'] ?? null,
78
+ }));
79
+ }
80
+
81
+ if (d['gl:source']?.length) {
82
+ result.sources = d['gl:source'].map(mapSourceFromJsonLd);
83
+ }
84
+
85
+ if (d['gl:related']?.length) {
86
+ result.related = d['gl:related'].map(mapRelatedFromJsonLd);
87
+ }
88
+
89
+ // Expression-specific
90
+ if (d['gl:prefix'] != null) result.prefix = d['gl:prefix'];
91
+ if (d['gl:gender']) {
92
+ result.grammar_info = [{ gender: d['gl:gender'] }];
93
+ }
94
+ if (d['gl:grammarInfo']?.length) {
95
+ result.grammar_info = d['gl:grammarInfo'].map((gi: any) => ({
96
+ gender: gi['gl:gender'] ?? null,
97
+ number: gi['gl:number'] ?? null,
98
+ part_of_speech: gi['gl:partOfSpeech'] ?? null,
99
+ noun: gi['gl:noun'] ?? false,
100
+ verb: gi['gl:verb'] ?? false,
101
+ adj: gi['gl:adj'] ?? false,
102
+ adverb: gi['gl:adverb'] ?? false,
103
+ preposition: gi['gl:preposition'] ?? false,
104
+ participle: gi['gl:participle'] ?? false,
105
+ }));
106
+ }
107
+
108
+ return result;
109
+ }
110
+
111
+ function mapSourceFromJsonLd(s: any): Record<string, unknown> {
112
+ const result: Record<string, unknown> = {};
113
+ if (s['gl:sourceType']) result.type = s['gl:sourceType'];
114
+ if (s['gl:sourceStatus']) result.status = s['gl:sourceStatus'];
115
+ if (s['gl:modification']) result.modification = s['gl:modification'];
116
+
117
+ if (s['gl:origin']) {
118
+ const origin: Record<string, unknown> = {};
119
+ const o = s['gl:origin'];
120
+ if (o['gl:ref']) {
121
+ const rawRef = o['gl:ref'];
122
+ const refObj: Record<string, unknown> = {};
123
+ // v3: ref is always an object { gl:source, gl:id, gl:version }
124
+ if (rawRef['gl:source']) refObj.source = rawRef['gl:source'];
125
+ if (rawRef['gl:id']) refObj.id = rawRef['gl:id'];
126
+ if (rawRef['gl:version']) refObj.version = rawRef['gl:version'];
127
+ if (rawRef['source']) refObj.source = rawRef['source'];
128
+ if (rawRef['id']) refObj.id = rawRef['id'];
129
+ if (rawRef['version']) refObj.version = rawRef['version'];
130
+ if (Object.keys(refObj).length > 0) origin.ref = refObj;
131
+ }
132
+ if (o['gl:locality']) {
133
+ const loc: Record<string, unknown> = {};
134
+ const rawLoc = o['gl:locality'];
135
+ if (rawLoc['gl:localityType']) loc.type = rawLoc['gl:localityType'];
136
+ if (rawLoc['gl:referenceFrom']) loc.reference_from = rawLoc['gl:referenceFrom'];
137
+ if (rawLoc['gl:referenceTo']) loc.reference_to = rawLoc['gl:referenceTo'];
138
+ if (rawLoc['type']) loc.type = rawLoc['type'];
139
+ if (rawLoc['reference_from']) loc.reference_from = rawLoc['reference_from'];
140
+ if (rawLoc['reference_to']) loc.reference_to = rawLoc['reference_to'];
141
+ origin.locality = loc;
142
+ }
143
+ if (o['gl:link']) origin.link = o['gl:link'];
144
+ if (o['gl:id']) origin.id = o['gl:id'];
145
+ if (o['gl:version']) origin.version = o['gl:version'];
146
+ if (o['gl:source']) origin.source = o['gl:source'];
147
+ result.origin = origin;
148
+ }
149
+
150
+ return result;
151
+ }
152
+
153
+ function mapRelatedFromJsonLd(r: any): Record<string, unknown> {
154
+ const result: Record<string, unknown> = { type: 'references' };
155
+ if (r['@id']) {
156
+ // Extract concept ID from URI: ".../concept/3.1.1.6" → source=register, id=3.1.1.6
157
+ const uri = r['@id'] as string;
158
+ const idMatch = uri.match(/\/concept\/([^/]+)$/);
159
+ result.ref = idMatch
160
+ ? { source: uri.split('/').slice(-3, -2)[0] || '', id: idMatch[1] }
161
+ : { source: uri, id: null };
162
+ }
163
+ if (r['gl:term']) result.content = r['gl:term'];
164
+ return result;
165
+ }
166
+
167
+ function mapLocalizedFromJsonLd(lc: any): Record<string, unknown> {
168
+ const data: Record<string, unknown> = {};
169
+
170
+ if (lc['gl:languageCode']) data.language_code = lc['gl:languageCode'];
171
+ if (lc['gl:entryStatus']) data.entry_status = lc['gl:entryStatus'];
172
+ if (lc['gl:classification']) data.classification = lc['gl:classification'];
173
+ if (lc['gl:reviewType']) data.review_type = lc['gl:reviewType'];
174
+ if (lc['gl:domain']) data.domain = lc['gl:domain'];
175
+ if (lc['gl:release']) data.release = lc['gl:release'];
176
+ if (lc['gl:lineageSourceSimilarity'] != null) data.lineage_source_similarity = lc['gl:lineageSourceSimilarity'];
177
+ if (lc['gl:script']) data.script = lc['gl:script'];
178
+ if (lc['gl:system']) data.system = lc['gl:system'];
179
+
180
+ if (lc['gl:designation']?.length) {
181
+ data.terms = lc['gl:designation'].map(mapDesignationFromJsonLd);
182
+ }
183
+
184
+ if (lc['gl:definition']?.length) {
185
+ data.definition = lc['gl:definition'].map((d: any) => {
186
+ const def: Record<string, unknown> = { content: d['gl:content'] ?? '' };
187
+ return def;
188
+ });
189
+ }
190
+
191
+ if (lc['gl:notes']?.length) {
192
+ data.notes = lc['gl:notes'].map((n: any) => ({ content: n['gl:content'] ?? '' }));
193
+ }
194
+
195
+ if (lc['gl:examples']?.length) {
196
+ data.examples = lc['gl:examples'].map((e: any) => ({ content: e['gl:content'] ?? '' }));
197
+ }
198
+
199
+ if (lc['gl:source']?.length) {
200
+ data.sources = lc['gl:source'].map(mapSourceFromJsonLd);
201
+ }
202
+
203
+ if (lc['gl:dates']?.length) {
204
+ data.dates = lc['gl:dates'].map((d: any) => ({
205
+ date: d['gl:date'] ?? null,
206
+ type: d['gl:dateType'] ?? null,
207
+ }));
208
+ }
209
+
210
+ if (lc['gl:references']?.length) {
211
+ data.related = lc['gl:references'].map(mapRelatedFromJsonLd);
212
+ }
213
+
214
+ // Review metadata — passed through to LocalizedConcept constructor
215
+ if (lc['gl:reviewDate']) data.review_date = lc['gl:reviewDate'];
216
+ if (lc['gl:reviewDecisionDate']) data.review_decision_date = lc['gl:reviewDecisionDate'];
217
+ if (lc['gl:reviewDecisionEvent']) data.review_decision_event = lc['gl:reviewDecisionEvent'];
218
+ if (lc['gl:reviewStatus']) data.review_status = lc['gl:reviewStatus'];
219
+ if (lc['gl:reviewDecision']) data.review_decision = lc['gl:reviewDecision'];
220
+ if (lc['gl:reviewDecisionNotes']) data.review_decision_notes = lc['gl:reviewDecisionNotes'];
221
+
222
+ return data;
223
+ }
224
+
225
+ function conceptFromJsonLd(doc: Record<string, any>): Concept {
226
+ const id = String(doc['gl:identifier'] ?? doc['@id']?.split('/').pop() ?? '');
227
+ const localizations: Record<string, any> = {};
228
+
229
+ const rawLc = doc['gl:localizedConcept'] ?? {};
230
+ for (const [lang, lc] of Object.entries(rawLc)) {
231
+ if (lc && typeof lc === 'object') {
232
+ localizations[lang] = mapLocalizedFromJsonLd(lc);
233
+ }
234
+ }
235
+
236
+ const related = (doc['gl:references'] ?? []).map(mapRelatedFromJsonLd);
237
+
238
+ return Concept.fromJSON({
239
+ id,
240
+ term: doc['gl:term'] ?? null,
241
+ uri: doc['@id'] ?? null,
242
+ localizations,
243
+ related,
244
+ status: null,
245
+ });
246
+ }
247
+
248
+ // ── Public API ────────────────────────────────────────────────────────────
249
+
250
+ export function conceptFromJson(doc: Record<string, any>): Concept {
251
+ if (isJsonLd(doc)) {
252
+ return conceptFromJsonLd(doc);
253
+ }
254
+ // glossarist native format — use fromJSON directly
255
+ return Concept.fromJSON(doc as Record<string, unknown>);
256
+ }
257
+
258
+ export function conceptToSummary(concept: Concept): ConceptSummary {
259
+ const designations: Record<string, string> = {};
260
+ for (const lang of concept.languages) {
261
+ const lc = concept.localization(lang);
262
+ if (lc?.primaryDesignation) {
263
+ designations[lang] = lc.primaryDesignation;
264
+ }
265
+ }
266
+ return {
267
+ id: concept.id,
268
+ designations,
269
+ eng: designations.eng || Object.values(designations)[0] || '',
270
+ status: concept.status ?? 'valid',
271
+ };
272
+ }
273
+
274
+ export function conceptUri(concept: Concept, registerId: string, uriBase: string): string {
275
+ if (concept.uri) return concept.uri;
276
+ return `${uriBase}/${registerId}/concept/${concept.id}`;
277
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Ontology Registry — taxonomy-driven labels and definitions for the browser.
3
+ *
4
+ * All enumeration labels, definitions, and colors come from the SKOS taxonomy
5
+ * data extracted at build time from concept-model/ontologies/taxonomies/*.ttl.
6
+ * The browser never hardcodes taxonomy values — it looks them up here.
7
+ */
8
+ import taxonomyData from '../data/taxonomies.json';
9
+
10
+ export interface TaxonomyConcept {
11
+ id: string;
12
+ iri: string;
13
+ prefLabel: string;
14
+ altLabel?: string;
15
+ definition?: string;
16
+ broader?: string;
17
+ }
18
+
19
+ export interface Taxonomy {
20
+ scheme: string;
21
+ schemeLabel: string | null;
22
+ schemeDefinition: string | null;
23
+ concepts: Record<string, TaxonomyConcept>;
24
+ }
25
+
26
+ type TaxonomyKey = keyof typeof taxonomyData;
27
+
28
+ class OntologyRegistry {
29
+ private data: Record<string, Taxonomy>;
30
+
31
+ constructor() {
32
+ this.data = taxonomyData as unknown as Record<string, Taxonomy>;
33
+ }
34
+
35
+ getConcept(taxonomy: TaxonomyKey, id: string): TaxonomyConcept | null {
36
+ return this.data[taxonomy]?.concepts[id] ?? null;
37
+ }
38
+
39
+ getLabel(taxonomy: TaxonomyKey, id: string | null | undefined): string {
40
+ if (!id) return '';
41
+ return this.getConcept(taxonomy, id)?.prefLabel ?? id;
42
+ }
43
+
44
+ getAltLabel(taxonomy: TaxonomyKey, id: string): string | null {
45
+ return this.getConcept(taxonomy, id)?.altLabel ?? null;
46
+ }
47
+
48
+ getDefinition(taxonomy: TaxonomyKey, id: string): string | null {
49
+ return this.getConcept(taxonomy, id)?.definition ?? null;
50
+ }
51
+
52
+ getAll(taxonomy: TaxonomyKey): TaxonomyConcept[] {
53
+ return Object.values(this.data[taxonomy]?.concepts ?? {});
54
+ }
55
+
56
+ getScheme(taxonomy: TaxonomyKey): string {
57
+ return this.data[taxonomy]?.scheme ?? '';
58
+ }
59
+
60
+ has(taxonomy: TaxonomyKey, id: string): boolean {
61
+ return id in (this.data[taxonomy]?.concepts ?? {});
62
+ }
63
+
64
+ /** Get broader concept ID, if any (for hierarchical taxonomies like designation-type). */
65
+ getBroader(taxonomy: TaxonomyKey, id: string): string | null {
66
+ return this.getConcept(taxonomy, id)?.broader ?? null;
67
+ }
68
+
69
+ /** Get all child concept IDs of a given concept. */
70
+ getNarrower(taxonomy: TaxonomyKey, id: string): TaxonomyConcept[] {
71
+ return this.getAll(taxonomy).filter(c => c.broader === id);
72
+ }
73
+ }
74
+
75
+ export const ontology = new OntologyRegistry();
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Ontology schema loader — provides class/property definitions parsed from
3
+ * the Glossarist OWL ontology for the Ontospy-style concept view.
4
+ */
5
+ import schemaData from '../data/ontology-schema.json';
6
+
7
+ export interface OwlClass {
8
+ iri: string;
9
+ compact: string;
10
+ label: string;
11
+ comment: string | null;
12
+ subClassOf: string | null;
13
+ disjointWith: string | null;
14
+ children: string[];
15
+ ancestors: string[];
16
+ }
17
+
18
+ export interface OwlProperty {
19
+ iri: string;
20
+ compact: string;
21
+ label: string;
22
+ comment: string | null;
23
+ type: 'object' | 'datatype';
24
+ domain: string | null;
25
+ domainUnion: string[] | null;
26
+ range: string | null;
27
+ rangeUnion: string[] | null;
28
+ inverseOf: string | null;
29
+ }
30
+
31
+ interface OntologySchema {
32
+ ontologyIri: string;
33
+ ontologyLabel: string;
34
+ classes: Record<string, OwlClass>;
35
+ classHierarchyRoots: string[];
36
+ properties: Record<string, OwlProperty>;
37
+ propertiesByDomain: Record<string, { object: string[]; datatype: string[] }>;
38
+ stats: { classCount: number; objectPropertyCount: number; datatypePropertyCount: number };
39
+ }
40
+
41
+ const data = schemaData as unknown as OntologySchema;
42
+
43
+ export function getClass(id: string): OwlClass | null {
44
+ return data.classes[id] ?? null;
45
+ }
46
+
47
+ export function getProperty(id: string): OwlProperty | null {
48
+ return data.properties[id] ?? null;
49
+ }
50
+
51
+ export function getPropertiesForDomain(domain: string): { object: OwlProperty[]; datatype: OwlProperty[] } {
52
+ const group = data.propertiesByDomain[domain];
53
+ if (!group) return { object: [], datatype: [] };
54
+ return {
55
+ object: group.object.map(id => data.properties[id]).filter(Boolean),
56
+ datatype: group.datatype.map(id => data.properties[id]).filter(Boolean),
57
+ };
58
+ }
59
+
60
+ /** Get all properties applicable to a class, including inherited ones. */
61
+ export function getAllPropertiesForClass(classId: string): { object: OwlProperty[]; datatype: OwlProperty[] } {
62
+ const cls = data.classes[classId];
63
+ if (!cls) return { object: [], datatype: [] };
64
+
65
+ const classChain = [classId, ...cls.ancestors];
66
+ const objectProps: OwlProperty[] = [];
67
+ const datatypeProps: OwlProperty[] = [];
68
+ const seen = new Set<string>();
69
+
70
+ for (const c of classChain) {
71
+ const props = getPropertiesForDomain(c);
72
+ for (const p of props.object) {
73
+ if (!seen.has(p.compact)) { seen.add(p.compact); objectProps.push(p); }
74
+ }
75
+ for (const p of props.datatype) {
76
+ if (!seen.has(p.compact)) { seen.add(p.compact); datatypeProps.push(p); }
77
+ }
78
+ }
79
+
80
+ return { object: objectProps, datatype: datatypeProps };
81
+ }
82
+
83
+ /** Get the full class hierarchy tree starting from roots. */
84
+ export function getClassTree(): OwlClass[] {
85
+ return data.classHierarchyRoots
86
+ .map(id => data.classes[id])
87
+ .filter(Boolean);
88
+ }
89
+
90
+ export function getAllClasses(): OwlClass[] {
91
+ return Object.values(data.classes);
92
+ }
93
+
94
+ export function getAllProperties(): OwlProperty[] {
95
+ return Object.values(data.properties);
96
+ }
97
+
98
+ export function getStats() {
99
+ return data.stats;
100
+ }
@@ -1,4 +1,38 @@
1
- /** Core types for the vocabulary browser data model. */
1
+ /**
2
+ * Infrastructure types for the vocabulary browser.
3
+ * Concept model types come from glossarist-js via model-bridge.ts.
4
+ */
5
+ import type { RELATIONSHIP_TYPES } from 'glossarist';
6
+
7
+ // Re-export glossarist model types for convenience
8
+ export type {
9
+ Concept,
10
+ LocalizedConcept,
11
+ Designation,
12
+ Expression,
13
+ Abbreviation,
14
+ Symbol as SymbolDesignation,
15
+ GraphicalSymbol,
16
+ Citation,
17
+ ConceptSource,
18
+ RelatedConcept,
19
+ ConceptDate,
20
+ DetailedDefinition,
21
+ NonVerbRep,
22
+ } from 'glossarist';
23
+
24
+ export type {
25
+ LetterSymbol,
26
+ GrammarInfo,
27
+ Pronunciation,
28
+ ConceptReference,
29
+ Locality,
30
+ } from 'glossarist/models';
31
+
32
+ export { RELATIONSHIP_TYPES, DATE_TYPES } from 'glossarist';
33
+ export { GRAMMAR_GENDERS, GRAMMAR_NUMBERS, GRAMMAR_PARTS_OF_SPEECH } from 'glossarist/models';
34
+
35
+ // ── Dataset metadata ──────────────────────────────────────────────────────
2
36
 
3
37
  export interface Manifest {
4
38
  id: string;
@@ -34,11 +68,12 @@ export interface ConceptIndex {
34
68
  conceptCount: number;
35
69
  chunkSize: number;
36
70
  chunks: { file: string; count: number }[];
37
- concepts: ConceptSummary[];
71
+ concepts: (ConceptSummary | undefined)[];
38
72
  }
39
73
 
40
74
  export interface ConceptSummary {
41
75
  id: string;
76
+ designations: Record<string, string>;
42
77
  eng: string;
43
78
  status: string;
44
79
  }
@@ -50,83 +85,25 @@ export interface ConceptEntry {
50
85
  status: string;
51
86
  }
52
87
 
53
- export interface ConceptDocument {
54
- '@context': string;
55
- '@id': string;
56
- '@type': string;
57
- 'gl:identifier': string;
58
- 'gl:localizedConcept': Record<string, LocalizedConcept>;
59
- }
60
-
61
- export interface LocalizedConcept {
62
- '@id': string;
63
- '@type': string;
64
- 'gl:languageCode': string;
65
- 'gl:entryStatus'?: string;
66
- 'gl:designation'?: Designation[];
67
- 'gl:definition'?: DetailedDefinition[];
68
- 'gl:notes'?: DetailedDefinition[];
69
- 'gl:examples'?: DetailedDefinition[];
70
- 'gl:source'?: ConceptSource[];
71
- 'gl:release'?: number;
72
- 'gl:reviewDate'?: string;
73
- 'gl:reviewDecisionDate'?: string;
74
- 'gl:reviewDecisionEvent'?: string;
75
- 'gl:reviewStatus'?: string;
76
- 'gl:reviewDecision'?: string;
77
- 'gl:reviewDecisionNotes'?: string;
78
- 'gl:dates'?: ConceptDate[];
79
- 'gl:references'?: CrossReference[];
80
- }
81
-
82
- export interface Designation {
83
- '@type': string;
84
- 'gl:normativeStatus': string;
85
- 'gl:term': string;
86
- 'gl:gender'?: string;
87
- 'gl:plurality'?: string;
88
- 'gl:international'?: boolean;
89
- }
90
-
91
- export interface DetailedDefinition {
92
- '@type': string;
93
- 'gl:content': string;
94
- }
95
-
96
- export interface ConceptSource {
97
- '@type': string;
98
- 'gl:sourceType'?: string;
99
- 'gl:sourceStatus'?: string;
100
- 'gl:modification'?: string;
101
- 'gl:origin'?: {
102
- '@type': string;
103
- 'gl:ref'?: string;
104
- 'gl:clause'?: string;
105
- 'gl:link'?: string;
106
- };
107
- }
108
-
109
- export interface ConceptDate {
110
- 'gl:dateType': string;
111
- 'gl:date': string;
112
- }
113
-
114
- export interface CrossReference {
115
- '@id': string;
116
- 'gl:term': string;
117
- }
118
-
119
88
  export interface DatasetRegistry {
120
89
  id: string;
121
90
  manifestUrl: string;
122
91
  }
123
92
 
93
+ // ── Graph types ────────────────────────────────────────────────────────────
94
+
95
+ export const EDGE_TYPE = {
96
+ REFERENCES: 'references',
97
+ DOMAIN: 'domain',
98
+ } as const;
99
+
124
100
  export interface GraphEdge {
125
- source: string; // concept URI
126
- target: string; // concept URI
101
+ source: string;
102
+ target: string;
127
103
  type: string;
128
104
  label?: string;
129
105
  register: string;
106
+ lang?: string;
130
107
  }
131
108
 
132
109
  export interface GraphNode {
@@ -136,8 +113,11 @@ export interface GraphNode {
136
113
  designations: Record<string, string>;
137
114
  status: string;
138
115
  loaded: boolean;
116
+ nodeType?: 'concept' | 'domain';
139
117
  }
140
118
 
119
+ // ── Search ─────────────────────────────────────────────────────────────────
120
+
141
121
  export interface SearchHit {
142
122
  conceptId: string;
143
123
  registerId: string;
@@ -147,14 +127,9 @@ export interface SearchHit {
147
127
  snippet?: string;
148
128
  }
149
129
 
150
- export type RelationType =
151
- | 'related'
152
- | 'narrower'
153
- | 'broader'
154
- | 'see'
155
- | 'references'
156
- | 'replaces'
157
- | 'superseded';
130
+ // ── Resolution ─────────────────────────────────────────────────────────────
131
+
132
+ export type RelationType = typeof RELATIONSHIP_TYPES[number];
158
133
 
159
134
  export type Resolution =
160
135
  | { type: 'internal'; registerId: string; conceptId: string; crossDataset: boolean }
@@ -14,7 +14,7 @@ const route = useRoute();
14
14
  const { getColor } = useDsStyle();
15
15
  const { globalPages, datasetPages, config: siteConfig } = useSiteConfig();
16
16
 
17
- const currentDataset = computed(() => (route.params as any).registerId ?? '');
17
+ const currentDataset = computed(() => route.params.registerId as string ?? '');
18
18
 
19
19
  const datasetEntries = computed(() => {
20
20
  const entries: { id: string; title: string; loaded: boolean; conceptCount: number }[] = [];
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ import type { Citation } from 'glossarist';
3
+
4
+ const props = defineProps<{
5
+ citation: Citation;
6
+ }>();
7
+
8
+ function formatRef(c: Citation): string {
9
+ const ref = c.ref;
10
+ if (!ref) return '';
11
+ const parts: string[] = [];
12
+ if (ref.source) parts.push(ref.source);
13
+ if (ref.id) parts.push(ref.id);
14
+ if (ref.version) parts.push(`(${ref.version})`);
15
+ return parts.join(' ');
16
+ }
17
+ </script>
18
+
19
+ <template>
20
+ <span class="inline">
21
+ <template v-if="citation.ref">
22
+ <span v-if="citation.ref.source" class="font-medium">{{ citation.ref.source }}</span>
23
+ <span v-if="citation.ref.id"> {{ citation.ref.id }}</span>
24
+ <span v-if="citation.ref.version" class="text-ink-400"> ({{ citation.ref.version }})</span>
25
+ </template>
26
+ <template v-if="citation.locality">
27
+ <span v-if="citation.locality.type" class="text-ink-400">, {{ citation.locality.type }}</span>
28
+ <span v-if="citation.locality.referenceFrom" class="text-ink-400">
29
+ {{ citation.locality.referenceTo ? ` ${citation.locality.referenceFrom}–${citation.locality.referenceTo}` : ` ${citation.locality.referenceFrom}` }}
30
+ </span>
31
+ </template>
32
+ <a v-if="citation.link" :href="citation.link" target="_blank" rel="noopener" class="concept-link ml-1">[link]</a>
33
+ <span v-if="citation.original" class="text-xs text-ink-300 ml-1">(orig: {{ citation.original }})</span>
34
+ </span>
35
+ </template>