@glossarist/concept-browser 0.7.31 → 0.7.33
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/build-edges.js +46 -4
- package/scripts/extract-source-refs.js +32 -0
- package/scripts/generate-data.mjs +2 -0
- package/src/__tests__/citation-display.test.ts +143 -0
- package/src/__tests__/concept-helpers.test.ts +129 -0
- package/src/__tests__/designation-relationship.test.ts +35 -4
- package/src/__tests__/extract-source-refs.test.ts +136 -0
- package/src/__tests__/factory-lazy.test.ts +2 -2
- package/src/__tests__/load-source-refs.test.ts +128 -0
- package/src/__tests__/slugify.test.ts +28 -0
- package/src/__tests__/source-refs.test.ts +191 -0
- package/src/adapters/DatasetAdapter.ts +51 -19
- package/src/adapters/ReferenceResolver.ts +5 -0
- package/src/adapters/factory.ts +36 -40
- package/src/adapters/model-bridge.ts +29 -19
- package/src/adapters/ontology-registry.ts +21 -4
- package/src/adapters/types.ts +2 -0
- package/src/components/CitationDisplay.vue +123 -14
- package/src/components/ConceptDetail.vue +25 -197
- package/src/components/DesignationList.vue +3 -3
- package/src/composables/use-concept-content.ts +160 -0
- package/src/composables/use-concept-edges.ts +181 -0
- package/src/data/taxonomies.json +13 -1
- package/src/graph/GraphEngine.ts +15 -0
- package/src/utils/concept-helpers.ts +4 -7
- package/src/utils/designation-registry.ts +15 -37
- package/src/utils/index.ts +1 -0
- package/src/utils/slugify.ts +3 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { computed, ref, watch, type ComputedRef } from 'vue';
|
|
2
|
+
import type { Concept, LocalizedConcept, ConceptSource, Designation } from 'glossarist';
|
|
3
|
+
import type { Manifest } from '../adapters/types';
|
|
4
|
+
import type { RenderOptions } from '../utils/math';
|
|
5
|
+
import { renderMath, cleanContent } from '../utils/math';
|
|
6
|
+
import { getAnnotations } from '../adapters/model-bridge';
|
|
7
|
+
import { getPreferredTerm, entryStatusColor, entryStatusLabel, entryStatusDefinition } from '../utils/concept-helpers';
|
|
8
|
+
import { sortLanguages } from '../utils/lang';
|
|
9
|
+
import { useSiteConfig } from '../config/use-site-config';
|
|
10
|
+
import { useI18n } from '../i18n';
|
|
11
|
+
|
|
12
|
+
export interface LangContent {
|
|
13
|
+
lang: string;
|
|
14
|
+
lc: LocalizedConcept;
|
|
15
|
+
renderedTerm: string;
|
|
16
|
+
definition: string;
|
|
17
|
+
renderedDefinition: string;
|
|
18
|
+
annotations: string[];
|
|
19
|
+
renderedAnnotations: string[];
|
|
20
|
+
notes: string[];
|
|
21
|
+
renderedNotes: string[];
|
|
22
|
+
examples: string[];
|
|
23
|
+
renderedExamples: string[];
|
|
24
|
+
sources: ConceptSource[];
|
|
25
|
+
designations: Designation[];
|
|
26
|
+
renderedDesignations: Map<string, string>;
|
|
27
|
+
entryStatus: string;
|
|
28
|
+
classification: string | null;
|
|
29
|
+
reviewType: string | null;
|
|
30
|
+
release: string | null;
|
|
31
|
+
lineageSourceSimilarity: number | null;
|
|
32
|
+
lcScript: string | null;
|
|
33
|
+
lcSystem: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useConceptContent(
|
|
37
|
+
concept: ComputedRef<Concept>,
|
|
38
|
+
manifest: ComputedRef<Manifest>,
|
|
39
|
+
renderOpts: ComputedRef<RenderOptions>,
|
|
40
|
+
) {
|
|
41
|
+
const { locale } = useI18n();
|
|
42
|
+
const { config: siteConfig } = useSiteConfig();
|
|
43
|
+
|
|
44
|
+
const languages = computed(() => {
|
|
45
|
+
const sorted = sortLanguages(concept.value.languages, manifest.value.languageOrder);
|
|
46
|
+
const current = locale.value;
|
|
47
|
+
const idx = sorted.indexOf(current);
|
|
48
|
+
if (idx > 0) {
|
|
49
|
+
sorted.splice(idx, 1);
|
|
50
|
+
sorted.unshift(current);
|
|
51
|
+
}
|
|
52
|
+
return sorted;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const allLangContent = computed(() => {
|
|
56
|
+
const result: LangContent[] = [];
|
|
57
|
+
for (const lang of languages.value) {
|
|
58
|
+
const lc = concept.value.localization(lang);
|
|
59
|
+
if (!lc) continue;
|
|
60
|
+
|
|
61
|
+
const definition = lc.definitions
|
|
62
|
+
.map(d => d.content).filter(Boolean).join('\n\n');
|
|
63
|
+
const annotations = getAnnotations(lc).map(a => a.content).filter(Boolean);
|
|
64
|
+
const notes = lc.notes.map(n => n.content).filter(Boolean);
|
|
65
|
+
const examples = lc.examples.map(e => e.content).filter(Boolean);
|
|
66
|
+
const opts = renderOpts.value;
|
|
67
|
+
|
|
68
|
+
result.push({
|
|
69
|
+
lang,
|
|
70
|
+
lc,
|
|
71
|
+
renderedTerm: renderMath(getPreferredTerm(lc, '')),
|
|
72
|
+
definition,
|
|
73
|
+
renderedDefinition: renderMath(definition, opts),
|
|
74
|
+
annotations,
|
|
75
|
+
renderedAnnotations: annotations.map((a: string) => renderMath(a, opts)),
|
|
76
|
+
notes,
|
|
77
|
+
renderedNotes: notes.map(n => renderMath(n, opts)),
|
|
78
|
+
examples,
|
|
79
|
+
renderedExamples: examples.map(e => renderMath(e, opts)),
|
|
80
|
+
sources: lc.sources,
|
|
81
|
+
designations: lc.terms,
|
|
82
|
+
renderedDesignations: new Map(lc.terms.map(d => [d.designation, renderMath(d.designation)])),
|
|
83
|
+
entryStatus: lc.entryStatus ?? '',
|
|
84
|
+
classification: lc.classification,
|
|
85
|
+
reviewType: lc.reviewType,
|
|
86
|
+
release: lc.release,
|
|
87
|
+
lineageSourceSimilarity: lc.lineageSourceSimilarity,
|
|
88
|
+
lcScript: lc.script,
|
|
89
|
+
lcSystem: lc.system,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const langContentMap = computed(() => {
|
|
96
|
+
const map = new Map<string, LangContent>();
|
|
97
|
+
for (const lc of allLangContent.value) map.set(lc.lang, lc);
|
|
98
|
+
return map;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
function hasContent(lc: LangContent): boolean {
|
|
102
|
+
return !!(lc.definition || lc.annotations.length || lc.notes.length || lc.examples.length || lc.sources.length);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const collapsedLangs = ref(new Set<string>());
|
|
106
|
+
|
|
107
|
+
function initCollapsed() {
|
|
108
|
+
const mainLangs = siteConfig.value?.defaults?.mainLanguages || [];
|
|
109
|
+
const mainSet = new Set(mainLangs.length ? mainLangs : ['eng']);
|
|
110
|
+
const collapsed = new Set<string>();
|
|
111
|
+
for (const lc of allLangContent.value) {
|
|
112
|
+
if (!hasContent(lc) && !mainSet.has(lc.lang)) {
|
|
113
|
+
collapsed.add(lc.lang);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
collapsedLangs.value = collapsed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
watch(languages, () => { initCollapsed(); }, { immediate: true });
|
|
120
|
+
|
|
121
|
+
const allCollapsed = computed(() => collapsedLangs.value.size === allLangContent.value.length);
|
|
122
|
+
|
|
123
|
+
function toggleLang(lang: string) {
|
|
124
|
+
const s = new Set(collapsedLangs.value);
|
|
125
|
+
if (s.has(lang)) s.delete(lang); else s.add(lang);
|
|
126
|
+
collapsedLangs.value = s;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function toggleAll() {
|
|
130
|
+
collapsedLangs.value = allCollapsed.value
|
|
131
|
+
? new Set()
|
|
132
|
+
: new Set(allLangContent.value.map(lc => lc.lang));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function plainTruncate(html: string, max: number = 120): string {
|
|
136
|
+
const text = cleanContent(html).replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
|
137
|
+
return text.length <= max ? text : text.slice(0, max).trimEnd() + '…';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function orderedDesignations(lang: string): Designation[] {
|
|
141
|
+
const desigs = langContentMap.value.get(lang)?.designations ?? [];
|
|
142
|
+
const preferred = desigs.filter(d => d.normativeStatus === 'preferred');
|
|
143
|
+
const admitted = desigs.filter(d => d.normativeStatus === 'admitted' || d.normativeStatus === 'deprecated');
|
|
144
|
+
const rest = desigs.filter(d => d.normativeStatus !== 'preferred' && d.normativeStatus !== 'admitted' && d.normativeStatus !== 'deprecated');
|
|
145
|
+
return [...preferred, ...admitted, ...rest];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
languages,
|
|
150
|
+
allLangContent,
|
|
151
|
+
langContentMap,
|
|
152
|
+
hasContent,
|
|
153
|
+
collapsedLangs,
|
|
154
|
+
allCollapsed,
|
|
155
|
+
toggleLang,
|
|
156
|
+
toggleAll,
|
|
157
|
+
plainTruncate,
|
|
158
|
+
orderedDesignations,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { computed, type ComputedRef } from 'vue';
|
|
2
|
+
import type { Concept, RelatedConcept } from 'glossarist';
|
|
3
|
+
import type { Manifest, GraphEdge } from '../adapters/types';
|
|
4
|
+
import { getFactory } from '../adapters/factory';
|
|
5
|
+
import { conceptUri } from '../adapters/model-bridge';
|
|
6
|
+
import { useVocabularyStore } from '../stores/vocabulary';
|
|
7
|
+
import { useDsStyle } from '../utils/dataset-style';
|
|
8
|
+
import { categorizeRelationship, relationshipLabel, INVERSE_RELATIONSHIPS } from '../utils/relationship-categories';
|
|
9
|
+
import { langLabel } from '../utils/lang';
|
|
10
|
+
import { escapeAttr } from '../utils/escape';
|
|
11
|
+
import { useI18n } from '../i18n';
|
|
12
|
+
|
|
13
|
+
export interface EdgeDisplay {
|
|
14
|
+
uri: string;
|
|
15
|
+
conceptId: string;
|
|
16
|
+
designation: string;
|
|
17
|
+
tooltip: string;
|
|
18
|
+
isLocal: boolean;
|
|
19
|
+
badge: { id: string; title: string } | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useConceptEdges(
|
|
23
|
+
concept: ComputedRef<Concept>,
|
|
24
|
+
registerId: ComputedRef<string>,
|
|
25
|
+
manifest: ComputedRef<Manifest>,
|
|
26
|
+
edges: ComputedRef<GraphEdge[]>,
|
|
27
|
+
) {
|
|
28
|
+
const factory = getFactory();
|
|
29
|
+
const store = useVocabularyStore();
|
|
30
|
+
const { getColor } = useDsStyle();
|
|
31
|
+
const { locale } = useI18n();
|
|
32
|
+
|
|
33
|
+
const conceptUriValue = computed(() =>
|
|
34
|
+
conceptUri(concept.value, registerId.value, manifest.value.uriBase)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const outgoingEdges = computed(() =>
|
|
38
|
+
store.graph.getUniqueEdges(conceptUriValue.value, 'outgoing', 'target')
|
|
39
|
+
.filter(e => e.type !== 'domain' && e.type !== 'section')
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const incomingEdges = computed(() =>
|
|
43
|
+
store.graph.getUniqueEdges(conceptUriValue.value, 'incoming', 'source')
|
|
44
|
+
.filter(e => e.type !== 'domain' && e.type !== 'section')
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const edgeDisplayCache = computed(() => {
|
|
48
|
+
const cache = new Map<string, EdgeDisplay>();
|
|
49
|
+
for (const e of edges.value) {
|
|
50
|
+
const uri = e.source === conceptUriValue.value ? e.target : e.source;
|
|
51
|
+
if (cache.has(uri)) continue;
|
|
52
|
+
const resolution = factory.resolve(uri, registerId.value);
|
|
53
|
+
const isLocal = resolution.type === 'internal' && resolution.registerId === registerId.value;
|
|
54
|
+
const conceptId = uri.match(/\/concept\/([^/]+)$/)?.[1] ?? uri.split('/').pop() ?? uri;
|
|
55
|
+
const node = store.graph.getNode(uri);
|
|
56
|
+
const designation = node
|
|
57
|
+
? (node.designations[locale.value] || node.designations.eng || Object.values(node.designations)[0] || '')
|
|
58
|
+
: '';
|
|
59
|
+
const tooltipLines: string[] = [uri];
|
|
60
|
+
if (node) {
|
|
61
|
+
for (const [lang, des] of Object.entries(node.designations)) {
|
|
62
|
+
tooltipLines.push(`${langLabel(lang)}: ${des}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let badge: { id: string; title: string } | null = null;
|
|
66
|
+
if (resolution.type === 'internal' && resolution.registerId !== registerId.value) {
|
|
67
|
+
const m = store.manifests.get(resolution.registerId);
|
|
68
|
+
badge = { id: resolution.registerId, title: m?.shortname || m?.title || resolution.registerId };
|
|
69
|
+
} else if (resolution.type === 'site') {
|
|
70
|
+
badge = { id: '', title: resolution.label };
|
|
71
|
+
} else if (resolution.type === 'url') {
|
|
72
|
+
badge = { id: '', title: resolution.label };
|
|
73
|
+
}
|
|
74
|
+
cache.set(uri, { uri, conceptId, designation, tooltip: tooltipLines.join('\n'), isLocal, badge });
|
|
75
|
+
}
|
|
76
|
+
return cache;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function getEdgeDisplay(uri: string): EdgeDisplay {
|
|
80
|
+
return edgeDisplayCache.value.get(uri) ?? { uri, conceptId: uri, designation: '', tooltip: uri, isLocal: false, badge: null };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function edgeBadgeColor(type: string, direction: 'out' | 'in'): string {
|
|
84
|
+
if (type === 'supersedes' || type === 'superseded_by') {
|
|
85
|
+
return direction === 'out' ? 'text-orange-700 bg-orange-50' : 'text-red-700 bg-red-50';
|
|
86
|
+
}
|
|
87
|
+
return categorizeRelationship(type).color;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function inverseEdgeType(type: string): string {
|
|
91
|
+
return INVERSE_RELATIONSHIPS[type] || type;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Concept-level related concepts (managed concept cross-references)
|
|
95
|
+
const conceptRelated = computed(() => {
|
|
96
|
+
const direct = concept.value.relatedConcepts?.filter(rc => !INVERSE_RELATIONSHIPS[rc.type]) ?? [];
|
|
97
|
+
const derived = incomingEdges.value
|
|
98
|
+
.filter(e => INVERSE_RELATIONSHIPS[e.type])
|
|
99
|
+
.map(e => {
|
|
100
|
+
const parsed = factory.resolve(e.source, registerId.value);
|
|
101
|
+
const sourceUrn = parsed.type === 'internal'
|
|
102
|
+
? store.manifests.get(parsed.registerId)?.datasetUri
|
|
103
|
+
: null;
|
|
104
|
+
const conceptId = e.source.match(/\/concept\/([^/]+)$/)?.[1];
|
|
105
|
+
return {
|
|
106
|
+
type: INVERSE_RELATIONSHIPS[e.type],
|
|
107
|
+
ref: sourceUrn && conceptId ? { source: sourceUrn, id: conceptId } : null,
|
|
108
|
+
content: '',
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
return [...direct, ...derived];
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
function resolveRelatedRef(ref: { source: string | null; id: string | null } | null) {
|
|
115
|
+
return factory.resolveRelatedRef(ref, registerId.value);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const resolvedRefs = computed(() => {
|
|
119
|
+
const map = new Map<string, { target: { registerId: string; conceptId: string } | null }>();
|
|
120
|
+
for (const cr of conceptRelated.value) {
|
|
121
|
+
const key = `${cr.ref?.source ?? ''}:${cr.ref?.id ?? ''}`;
|
|
122
|
+
if (map.has(key)) continue;
|
|
123
|
+
map.set(key, { target: resolveRelatedRef(cr.ref) });
|
|
124
|
+
}
|
|
125
|
+
return map;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
function getResolvedRef(ref: { source: string | null; id: string | null } | null) {
|
|
129
|
+
if (!ref) return { target: null };
|
|
130
|
+
const key = `${ref.source ?? ''}:${ref.id ?? ''}`;
|
|
131
|
+
return resolvedRefs.value.get(key) ?? { target: resolveRelatedRef(ref) };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function relatedLabel(dr: { content?: string | null; ref?: { source: string | null; id: string | null } | null }): string {
|
|
135
|
+
if (dr.content) return dr.content;
|
|
136
|
+
const resolved = dr.ref ? getResolvedRef(dr.ref).target : null;
|
|
137
|
+
if (resolved) {
|
|
138
|
+
const m = store.manifests.get(resolved.registerId);
|
|
139
|
+
const dsLabel = m?.shortname || m?.title || resolved.registerId;
|
|
140
|
+
return `${resolved.conceptId} (${dsLabel})`;
|
|
141
|
+
}
|
|
142
|
+
return dr.ref ? `${dr.ref.id || ''} (${dr.ref.source || ''})`.trim() : '';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function navigateEdge(edge: GraphEdge) {
|
|
146
|
+
const uri = edge.source === conceptUriValue.value ? edge.target : edge.source;
|
|
147
|
+
const resolution = factory.resolve(uri);
|
|
148
|
+
const router = (await import('vue-router')).useRouter();
|
|
149
|
+
|
|
150
|
+
if (resolution.type === 'internal') {
|
|
151
|
+
router.push({ name: 'concept', params: { registerId: resolution.registerId, conceptId: resolution.conceptId } });
|
|
152
|
+
} else if (resolution.type === 'site') {
|
|
153
|
+
window.open(`${resolution.baseUrl}/resolve/${encodeURIComponent(uri)}`, '_blank', 'noopener');
|
|
154
|
+
} else if (resolution.type === 'url') {
|
|
155
|
+
window.open(resolution.url, '_blank', 'noopener');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function navigateRelated(ref: { source: string | null; id: string | null }) {
|
|
160
|
+
const target = resolveRelatedRef(ref);
|
|
161
|
+
if (!target) return;
|
|
162
|
+
const router = (await import('vue-router')).useRouter();
|
|
163
|
+
router.push({ name: 'concept', params: { registerId: target.registerId, conceptId: target.conceptId } });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
conceptUriValue,
|
|
168
|
+
outgoingEdges,
|
|
169
|
+
incomingEdges,
|
|
170
|
+
edgeDisplayCache,
|
|
171
|
+
getEdgeDisplay,
|
|
172
|
+
edgeBadgeColor,
|
|
173
|
+
inverseEdgeType,
|
|
174
|
+
conceptRelated,
|
|
175
|
+
resolveRelatedRef,
|
|
176
|
+
getResolvedRef,
|
|
177
|
+
relatedLabel,
|
|
178
|
+
navigateEdge,
|
|
179
|
+
navigateRelated,
|
|
180
|
+
};
|
|
181
|
+
}
|
package/src/data/taxonomies.json
CHANGED
|
@@ -221,6 +221,18 @@
|
|
|
221
221
|
"prefLabel": "not equal",
|
|
222
222
|
"definition": "The concept is not equal to the source."
|
|
223
223
|
}
|
|
224
|
+
},
|
|
225
|
+
"colors": {
|
|
226
|
+
"identical": "badge-green",
|
|
227
|
+
"similar": "badge-blue",
|
|
228
|
+
"modified": "badge-yellow",
|
|
229
|
+
"restyle": "badge-yellow",
|
|
230
|
+
"context_added": "badge-blue",
|
|
231
|
+
"generalisation": "badge-purple",
|
|
232
|
+
"specialisation": "badge-purple",
|
|
233
|
+
"unspecified": "badge-gray",
|
|
234
|
+
"related": "badge-gray",
|
|
235
|
+
"not_equal": "bg-red-50 text-red-700"
|
|
224
236
|
}
|
|
225
237
|
},
|
|
226
238
|
"relationshipType": {
|
|
@@ -940,4 +952,4 @@
|
|
|
940
952
|
}
|
|
941
953
|
}
|
|
942
954
|
}
|
|
943
|
-
}
|
|
955
|
+
}
|
package/src/graph/GraphEngine.ts
CHANGED
|
@@ -98,6 +98,21 @@ export class GraphEngine {
|
|
|
98
98
|
return result;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
getUniqueEdges(uri: string, direction: 'outgoing' | 'incoming' | 'both', dedupeBy: 'source' | 'target' = 'target'): GraphEdge[] {
|
|
102
|
+
const raw = direction === 'outgoing'
|
|
103
|
+
? this.getEdges(uri)
|
|
104
|
+
: direction === 'incoming'
|
|
105
|
+
? this.getIncomingEdges(uri)
|
|
106
|
+
: [...this.getEdges(uri), ...this.getIncomingEdges(uri)];
|
|
107
|
+
const seen = new Set<string>();
|
|
108
|
+
return raw.filter(e => {
|
|
109
|
+
const key = `${e[dedupeBy]}\0${e.type}`;
|
|
110
|
+
if (seen.has(key)) return false;
|
|
111
|
+
seen.add(key);
|
|
112
|
+
return true;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
101
116
|
getNeighbors(uri: string): { outgoing: string[]; incoming: string[] } {
|
|
102
117
|
const outgoing: string[] = [];
|
|
103
118
|
const adj = this.adjacency.get(uri);
|
|
@@ -2,17 +2,15 @@ import type { LocalizedConcept } from 'glossarist';
|
|
|
2
2
|
import { ontology } from '../adapters/ontology-registry';
|
|
3
3
|
|
|
4
4
|
export function entryStatusColor(status: string): string {
|
|
5
|
-
return ontology.
|
|
5
|
+
return ontology.getDisplay('entryStatus', status).color;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function conceptStatusColor(status: string | null): string {
|
|
9
|
-
|
|
10
|
-
return ontology.getColor('conceptStatus', status) ?? 'badge-gray';
|
|
9
|
+
return ontology.getDisplay('conceptStatus', status).color;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
export function conceptStatusLabel(status: string | null): string {
|
|
14
|
-
|
|
15
|
-
return ontology.getLabel('conceptStatus', status) || status;
|
|
13
|
+
return ontology.getDisplay('conceptStatus', status).label;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
export function conceptStatusDefinition(status: string | null): string | null {
|
|
@@ -21,8 +19,7 @@ export function conceptStatusDefinition(status: string | null): string | null {
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
export function entryStatusLabel(status: string | null): string {
|
|
24
|
-
|
|
25
|
-
return ontology.getLabel('entryStatus', status) || status;
|
|
22
|
+
return ontology.getDisplay('entryStatus', status).label;
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
export function entryStatusDefinition(status: string | null): string | null {
|
|
@@ -9,52 +9,28 @@ export interface DesignationTypeInfo {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function designationTypeInfo(designation: Designation): DesignationTypeInfo {
|
|
12
|
-
|
|
13
|
-
const concept = ontology.getConcept('designationType', type);
|
|
14
|
-
return {
|
|
15
|
-
label: concept?.prefLabel ?? type,
|
|
16
|
-
color: ontology.getColor('designationType', type) ?? 'bg-gray-50 text-gray-700',
|
|
17
|
-
definition: concept?.definition ?? undefined,
|
|
18
|
-
};
|
|
12
|
+
return ontology.getDisplay('designationType', designation.type, 'bg-gray-50 text-gray-700');
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
export function normativeStatusInfo(status: string | null): { label: string; color: string; definition?: string } {
|
|
22
|
-
|
|
23
|
-
const concept = ontology.getConcept('normativeStatus', status);
|
|
24
|
-
return {
|
|
25
|
-
label: concept?.prefLabel ?? status,
|
|
26
|
-
color: ontology.getColor('normativeStatus', status) ?? 'bg-gray-50 text-gray-700',
|
|
27
|
-
definition: concept?.definition ?? undefined,
|
|
28
|
-
};
|
|
16
|
+
return ontology.getDisplay('normativeStatus', status, 'bg-gray-50 text-gray-700');
|
|
29
17
|
}
|
|
30
18
|
|
|
31
19
|
export function sourceStatusInfo(status: string | null): { label: string; color: string; definition?: string } {
|
|
32
|
-
|
|
33
|
-
const concept = ontology.getConcept('sourceStatus', status);
|
|
34
|
-
return {
|
|
35
|
-
label: concept?.prefLabel ?? status,
|
|
36
|
-
color: 'badge-gray',
|
|
37
|
-
definition: concept?.definition ?? undefined,
|
|
38
|
-
};
|
|
20
|
+
return ontology.getDisplay('sourceStatus', status, 'badge-gray');
|
|
39
21
|
}
|
|
40
22
|
|
|
41
23
|
export function sourceTypeInfo(type: string | null): { label: string; color: string; definition?: string } {
|
|
42
|
-
|
|
43
|
-
const concept = ontology.getConcept('sourceType', type);
|
|
44
|
-
return {
|
|
45
|
-
label: concept?.prefLabel ?? type,
|
|
46
|
-
color: ontology.getColor('sourceType', type) ?? 'badge-gray',
|
|
47
|
-
definition: concept?.definition ?? undefined,
|
|
48
|
-
};
|
|
24
|
+
return ontology.getDisplay('sourceType', type, 'badge-gray');
|
|
49
25
|
}
|
|
50
26
|
|
|
51
27
|
export function termTypeInfo(termType: string | null): { label: string; category: string; definition?: string } {
|
|
52
28
|
if (!termType) return { label: '', category: '' };
|
|
53
|
-
const
|
|
29
|
+
const display = ontology.getDisplay('termType', termType);
|
|
54
30
|
return {
|
|
55
|
-
label:
|
|
56
|
-
category:
|
|
57
|
-
definition:
|
|
31
|
+
label: display.label,
|
|
32
|
+
category: ontology.getBroader('termType', termType) ?? '',
|
|
33
|
+
definition: display.definition,
|
|
58
34
|
};
|
|
59
35
|
}
|
|
60
36
|
|
|
@@ -68,18 +44,20 @@ export function abbreviationDetails(designation: Designation): string[] {
|
|
|
68
44
|
return parts;
|
|
69
45
|
}
|
|
70
46
|
|
|
47
|
+
const GRAMMAR_BOOLEAN_POS = ['noun', 'verb', 'adj', 'adverb', 'preposition', 'participle'] as const;
|
|
48
|
+
|
|
71
49
|
export function grammarBadges(gi: GrammarInfo): { label: string; definition?: string }[] {
|
|
72
50
|
const badges: { label: string; definition?: string }[] = [];
|
|
73
51
|
if (gi.gender) {
|
|
74
|
-
const
|
|
75
|
-
badges.push({ label:
|
|
52
|
+
const display = ontology.getDisplay('grammarGender', gi.gender);
|
|
53
|
+
badges.push({ label: display.label, definition: display.definition });
|
|
76
54
|
}
|
|
77
55
|
if (gi.number) {
|
|
78
|
-
const
|
|
79
|
-
badges.push({ label:
|
|
56
|
+
const display = ontology.getDisplay('grammarNumber', gi.number);
|
|
57
|
+
badges.push({ label: display.label, definition: display.definition });
|
|
80
58
|
}
|
|
81
59
|
if (gi.partOfSpeech) badges.push({ label: gi.partOfSpeech });
|
|
82
|
-
for (const pos of
|
|
60
|
+
for (const pos of GRAMMAR_BOOLEAN_POS) {
|
|
83
61
|
if (gi[pos]) badges.push({ label: pos });
|
|
84
62
|
}
|
|
85
63
|
return badges;
|
package/src/utils/index.ts
CHANGED