@glossarist/concept-browser 0.7.34 → 0.7.37
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 +2 -2
- package/scripts/build-edges.js +16 -8
- package/scripts/generate-data.mjs +284 -86
- package/src/__tests__/citation-display.test.ts +165 -3
- package/src/__tests__/cite-ref.test.ts +112 -0
- package/src/__tests__/concept-detail-interaction.test.ts +1 -5
- package/src/__tests__/{math.test.ts → content-renderer.test.ts} +113 -29
- package/src/__tests__/escape.test.ts +76 -0
- package/src/__tests__/graph-data-source.test.ts +155 -0
- package/src/__tests__/model-bridge-bridges.test.ts +150 -0
- package/src/__tests__/model-bridge-citation.test.ts +163 -0
- package/src/__tests__/reference-resolver-cite.test.ts +122 -0
- package/src/__tests__/reference-resolver.test.ts +12 -7
- package/src/__tests__/resolve-view.test.ts +1 -1
- package/src/__tests__/sidebar-nav-highlighting.test.ts +178 -0
- package/src/__tests__/source-refs.test.ts +9 -6
- package/src/__tests__/test-helpers.ts +20 -0
- package/src/__tests__/uri-router.test.ts +39 -12
- package/src/adapters/DatasetAdapter.ts +35 -143
- package/src/adapters/GraphDataSource.ts +178 -0
- package/src/adapters/ReferenceResolver.ts +101 -47
- package/src/adapters/UriRouter.ts +82 -10
- package/src/adapters/factory.ts +35 -28
- package/src/adapters/model-bridge.ts +121 -71
- package/src/adapters/types.ts +3 -0
- package/src/components/AppSidebar.vue +7 -4
- package/src/components/CitationDisplay.vue +86 -30
- package/src/components/ConceptDetail.vue +24 -126
- package/src/components/LanguageDetail.vue +6 -6
- package/src/composables/use-concept-content.ts +8 -8
- package/src/composables/use-ontology-nav.ts +129 -130
- package/src/composables/use-render-options.ts +1 -1
- package/src/graph/GraphEngine.ts +65 -0
- package/src/stores/vocabulary.ts +12 -73
- package/src/utils/content-renderer.ts +312 -0
- package/src/utils/markdown-lite.ts +2 -2
- package/src/utils/math.ts +0 -189
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { Concept, LocalizedConcept, Designation
|
|
2
|
+
import type { Concept, LocalizedConcept, Designation } from 'glossarist';
|
|
3
3
|
import type { Manifest, GraphEdge } from '../adapters/types';
|
|
4
4
|
import { computed, ref, nextTick, watch } from 'vue';
|
|
5
|
-
import { langName
|
|
6
|
-
import {
|
|
7
|
-
import type { RenderOptions } from '../utils/
|
|
5
|
+
import { langName } from '../utils/lang';
|
|
6
|
+
import { renderContent } from '../utils/content-renderer';
|
|
7
|
+
import type { RenderOptions } from '../utils/content-renderer';
|
|
8
8
|
import { escapeAttr } from '../utils/escape';
|
|
9
9
|
import { entryStatusColor, conceptStatusColor, conceptStatusLabel, conceptStatusDefinition, entryStatusLabel, entryStatusDefinition, getPreferredTerm } from '../utils/concept-helpers';
|
|
10
10
|
import { sourceTypeInfo, sourceStatusInfo } from '../utils/designation-registry';
|
|
11
|
-
import { conceptUri
|
|
11
|
+
import { conceptUri } 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';
|
|
15
15
|
import { getFactory } from '../adapters/factory';
|
|
16
16
|
import { useRenderOptions } from '../composables/use-render-options';
|
|
17
17
|
import { useConceptEdges } from '../composables/use-concept-edges';
|
|
18
|
-
import { useConceptContent
|
|
18
|
+
import { useConceptContent } from '../composables/use-concept-content';
|
|
19
19
|
import { relationshipLabel, INVERSE_RELATIONSHIPS } from '../utils/relationship-categories';
|
|
20
20
|
import { slugify } from '../utils/slugify';
|
|
21
21
|
import { useSiteConfig } from '../config/use-site-config';
|
|
@@ -83,27 +83,12 @@ function copyUri() {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const languages = computed(() => {
|
|
87
|
-
const sorted = sortLanguages(props.concept.languages, props.manifest.languageOrder);
|
|
88
|
-
// Put current UI locale first
|
|
89
|
-
const current = locale.value;
|
|
90
|
-
const idx = sorted.indexOf(current);
|
|
91
|
-
if (idx > 0) {
|
|
92
|
-
sorted.splice(idx, 1);
|
|
93
|
-
sorted.unshift(current);
|
|
94
|
-
}
|
|
95
|
-
return sorted;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Collapsible language sections — expand all with content, collapse those without
|
|
99
|
-
const collapsedLangs = ref(new Set<string>());
|
|
100
|
-
|
|
101
86
|
const engConcept = computed((): LocalizedConcept | null => {
|
|
102
87
|
return props.concept.localization('eng') ?? null;
|
|
103
88
|
});
|
|
104
89
|
|
|
105
90
|
const primaryTerm = computed(() => getPreferredTerm(engConcept.value, conceptId.value));
|
|
106
|
-
const renderedPrimaryTerm = computed(() =>
|
|
91
|
+
const renderedPrimaryTerm = computed(() => renderContent(primaryTerm.value));
|
|
107
92
|
|
|
108
93
|
const managedStatus = computed(() => props.concept.status);
|
|
109
94
|
|
|
@@ -115,12 +100,10 @@ const conceptSources = computed(() => props.concept.sources);
|
|
|
115
100
|
|
|
116
101
|
const conceptTags = computed(() => props.concept.tags ?? []);
|
|
117
102
|
|
|
118
|
-
// Cross-reference resolver: generates clickable links for inline refs
|
|
119
|
-
|
|
120
103
|
const factory = getFactory();
|
|
121
104
|
const { ensureBibLoaded, bibResolver, figResolver } = useRenderOptions(() => props.registerId);
|
|
122
105
|
|
|
123
|
-
const renderOpts
|
|
106
|
+
const renderOpts = computed<RenderOptions>(() => ({
|
|
124
107
|
xrefResolver: (uri, term) => {
|
|
125
108
|
const resolution = factory.resolve(uri, props.registerId);
|
|
126
109
|
if (resolution.type === 'internal') {
|
|
@@ -135,15 +118,16 @@ const renderOpts: RenderOptions = {
|
|
|
135
118
|
return escapeAttr(term);
|
|
136
119
|
},
|
|
137
120
|
conceptRefResolver: (conceptId, term) => {
|
|
138
|
-
|
|
121
|
+
const adapter = factory.getAdapter(props.registerId);
|
|
122
|
+
const resolvedId = adapter?.lookupByDesignation(conceptId) ?? conceptId;
|
|
123
|
+
return `<a href="#" class="xref-link" data-register="${escapeAttr(props.registerId)}" data-concept="${escapeAttr(resolvedId)}">${escapeAttr(term)}</a>`;
|
|
139
124
|
},
|
|
140
125
|
bibResolver,
|
|
141
126
|
figResolver,
|
|
142
|
-
};
|
|
127
|
+
}));
|
|
143
128
|
|
|
144
129
|
watch(() => props.registerId, () => { ensureBibLoaded(); }, { immediate: true });
|
|
145
130
|
|
|
146
|
-
// Handle clicks on cross-reference links via event delegation
|
|
147
131
|
function handleContentClick(e: MouseEvent) {
|
|
148
132
|
const target = (e.target as HTMLElement).closest('.xref-link') as HTMLElement | null;
|
|
149
133
|
if (!target) return;
|
|
@@ -155,86 +139,18 @@ function handleContentClick(e: MouseEvent) {
|
|
|
155
139
|
}
|
|
156
140
|
}
|
|
157
141
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const examples = lc.examples.map(e => e.content).filter(Boolean);
|
|
171
|
-
|
|
172
|
-
result.push({
|
|
173
|
-
lang,
|
|
174
|
-
lc,
|
|
175
|
-
renderedTerm: renderMath(getPreferredTerm(lc, '')),
|
|
176
|
-
definition,
|
|
177
|
-
renderedDefinition: renderMath(definition, renderOpts),
|
|
178
|
-
annotations,
|
|
179
|
-
renderedAnnotations: annotations.map((a: string) => renderMath(a, renderOpts)),
|
|
180
|
-
notes,
|
|
181
|
-
renderedNotes: notes.map(n => renderMath(n, renderOpts)),
|
|
182
|
-
examples,
|
|
183
|
-
renderedExamples: examples.map(e => renderMath(e, renderOpts)),
|
|
184
|
-
sources: lc.sources,
|
|
185
|
-
designations: lc.terms,
|
|
186
|
-
renderedDesignations: new Map(lc.terms.map(d => [d.designation, renderMath(d.designation)])),
|
|
187
|
-
entryStatus: lc.entryStatus ?? '',
|
|
188
|
-
classification: lc.classification,
|
|
189
|
-
reviewType: lc.reviewType,
|
|
190
|
-
release: lc.release,
|
|
191
|
-
lineageSourceSimilarity: lc.lineageSourceSimilarity,
|
|
192
|
-
lcScript: lc.script,
|
|
193
|
-
lcSystem: lc.system,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
return result;
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const langContentMap = computed(() => {
|
|
200
|
-
const map = new Map<string, LangContent>();
|
|
201
|
-
for (const lc of allLangContent.value) map.set(lc.lang, lc);
|
|
202
|
-
return map;
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
function hasContent(lc: LangContent): boolean {
|
|
206
|
-
return !!(lc.definition || lc.annotations.length || lc.notes.length || lc.examples.length || lc.sources.length);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function initCollapsed() {
|
|
210
|
-
const mainLangs = siteConfig.value?.defaults?.mainLanguages || [];
|
|
211
|
-
const mainSet = new Set(mainLangs.length ? mainLangs : ['eng']);
|
|
212
|
-
const collapsed = new Set<string>();
|
|
213
|
-
for (const lc of allLangContent.value) {
|
|
214
|
-
if (!hasContent(lc) && !mainSet.has(lc.lang)) {
|
|
215
|
-
collapsed.add(lc.lang);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
collapsedLangs.value = collapsed;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
watch(languages, () => { initCollapsed(); }, { immediate: true });
|
|
222
|
-
|
|
223
|
-
const allCollapsed = computed(() => collapsedLangs.value.size === allLangContent.value.length);
|
|
224
|
-
|
|
225
|
-
function toggleLang(lang: string) {
|
|
226
|
-
const s = new Set(collapsedLangs.value);
|
|
227
|
-
if (s.has(lang)) s.delete(lang); else s.add(lang);
|
|
228
|
-
collapsedLangs.value = s;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function toggleAll() {
|
|
232
|
-
if (allCollapsed.value) {
|
|
233
|
-
collapsedLangs.value = new Set();
|
|
234
|
-
} else {
|
|
235
|
-
collapsedLangs.value = new Set(allLangContent.value.map(lc => lc.lang));
|
|
236
|
-
}
|
|
237
|
-
}
|
|
142
|
+
const {
|
|
143
|
+
languages,
|
|
144
|
+
allLangContent,
|
|
145
|
+
langContentMap,
|
|
146
|
+
hasContent,
|
|
147
|
+
collapsedLangs,
|
|
148
|
+
allCollapsed,
|
|
149
|
+
toggleLang,
|
|
150
|
+
toggleAll,
|
|
151
|
+
plainTruncate,
|
|
152
|
+
orderedDesignations,
|
|
153
|
+
} = useConceptContent(conceptComputed, manifestComputed, renderOpts);
|
|
238
154
|
|
|
239
155
|
function scrollToLang(lang: string) {
|
|
240
156
|
if (collapsedLangs.value.has(lang)) {
|
|
@@ -263,14 +179,6 @@ function getDesignationsForLang(lang: string): Designation[] {
|
|
|
263
179
|
return lc?.terms ?? [];
|
|
264
180
|
}
|
|
265
181
|
|
|
266
|
-
function orderedDesignations(lang: string): Designation[] {
|
|
267
|
-
const desigs = getDesignationsForLang(lang);
|
|
268
|
-
const preferred = desigs.filter(d => d.normativeStatus === 'preferred');
|
|
269
|
-
const admitted = desigs.filter(d => d.normativeStatus === 'admitted' || d.normativeStatus === 'deprecated');
|
|
270
|
-
const rest = desigs.filter(d => d.normativeStatus !== 'preferred' && d.normativeStatus !== 'admitted' && d.normativeStatus !== 'deprecated');
|
|
271
|
-
return [...preferred, ...admitted, ...rest];
|
|
272
|
-
}
|
|
273
|
-
|
|
274
182
|
function hasDefinition(lang: string): boolean {
|
|
275
183
|
const lc = props.concept.localization(lang);
|
|
276
184
|
if (!lc) return false;
|
|
@@ -282,16 +190,9 @@ function goAdjacent(id: string) {
|
|
|
282
190
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
283
191
|
}
|
|
284
192
|
|
|
285
|
-
function plainTruncate(html: string, max: number = 120): string {
|
|
286
|
-
const text = cleanContent(html).replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
|
287
|
-
return text.length <= max ? text : text.slice(0, max).trimEnd() + '…';
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Domain rendering: merge ConceptReference domains and per-localization domain strings
|
|
291
193
|
const conceptDomains = computed(() => {
|
|
292
194
|
const domainMap = new Map<string, { slug: string; label: string; langs: string[]; conceptId?: string }>();
|
|
293
195
|
|
|
294
|
-
// Managed concept level ConceptReference domains (authoritative)
|
|
295
196
|
for (const ref of conceptRefDomains.value) {
|
|
296
197
|
const id = ref.conceptId ?? '';
|
|
297
198
|
const label = id || ref.urn || '';
|
|
@@ -301,7 +202,6 @@ const conceptDomains = computed(() => {
|
|
|
301
202
|
}
|
|
302
203
|
}
|
|
303
204
|
|
|
304
|
-
// Per-localization domain strings
|
|
305
205
|
for (const lang of props.concept.languages) {
|
|
306
206
|
const lc = props.concept.localization(lang);
|
|
307
207
|
const domain = lc?.domain;
|
|
@@ -318,7 +218,6 @@ const conceptDomains = computed(() => {
|
|
|
318
218
|
return [...domainMap.values()].sort((a, b) => b.langs.length - a.langs.length);
|
|
319
219
|
});
|
|
320
220
|
|
|
321
|
-
// Non-verbal reps: aggregate across all localizations
|
|
322
221
|
const nonVerbalReps = computed(() => {
|
|
323
222
|
const reps: typeof import('glossarist').NonVerbRep.prototype[] = [];
|
|
324
223
|
for (const lang of props.concept.languages) {
|
|
@@ -329,7 +228,6 @@ const nonVerbalReps = computed(() => {
|
|
|
329
228
|
}
|
|
330
229
|
return reps;
|
|
331
230
|
});
|
|
332
|
-
|
|
333
231
|
</script>
|
|
334
232
|
|
|
335
233
|
<template>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import type { Concept, LocalizedConcept, Designation, Expression, Abbreviation as AbbreviationType } from 'glossarist';
|
|
3
3
|
import { computed } from 'vue';
|
|
4
4
|
import { langName, langLabel } from '../utils/lang';
|
|
5
|
-
import {
|
|
6
|
-
import type { RenderOptions } from '../utils/
|
|
5
|
+
import { renderContent } from '../utils/content-renderer';
|
|
6
|
+
import type { RenderOptions } from '../utils/content-renderer';
|
|
7
7
|
import { escapeAttr } from '../utils/escape';
|
|
8
8
|
import { entryStatusColor } from '../utils/concept-helpers';
|
|
9
9
|
import { designationTypeInfo, normativeStatusInfo, grammarBadges, pronunciationLabel, pronunciationTooltip } from '../utils/designation-registry';
|
|
@@ -115,7 +115,7 @@ function handleContentClick(e: MouseEvent) {
|
|
|
115
115
|
<div class="section-label">{{ t('concept.designations') }}</div>
|
|
116
116
|
<div class="space-y-2 mt-3">
|
|
117
117
|
<div v-for="(d, i) in designations" :key="i" class="flex items-center gap-2 flex-wrap">
|
|
118
|
-
<span class="font-medium text-ink-800 text-lg" v-html="
|
|
118
|
+
<span class="font-medium text-ink-800 text-lg" v-html="renderContent(d.designation)"></span>
|
|
119
119
|
<span class="badge text-[10px]" :class="designationTypeInfo(d).color">{{ designationTypeInfo(d).label }}</span>
|
|
120
120
|
<span class="badge text-[10px]" :class="normativeStatusInfo(d.normativeStatus).color">{{ normativeStatusInfo(d.normativeStatus).label }}</span>
|
|
121
121
|
<template v-if="d.type === 'expression' && (d as Expression).grammarInfo?.length">
|
|
@@ -142,7 +142,7 @@ function handleContentClick(e: MouseEvent) {
|
|
|
142
142
|
<!-- Definition -->
|
|
143
143
|
<div v-if="definition" class="card p-5">
|
|
144
144
|
<div class="section-label">{{ t('concept.definition') }}</div>
|
|
145
|
-
<div class="text-ink-800 leading-relaxed mt-3" v-html="
|
|
145
|
+
<div class="text-ink-800 leading-relaxed mt-3" v-html="renderContent(definition, renderOpts)"></div>
|
|
146
146
|
</div>
|
|
147
147
|
|
|
148
148
|
<!-- Notes -->
|
|
@@ -151,7 +151,7 @@ function handleContentClick(e: MouseEvent) {
|
|
|
151
151
|
<div class="space-y-3 mt-3">
|
|
152
152
|
<div v-for="(note, i) in notes" :key="i" class="text-ink-600 text-sm leading-relaxed">
|
|
153
153
|
<span class="font-medium text-ink-400 text-xs uppercase tracking-wide">{{ t('concept.note') }} {{ i + 1 }}</span>
|
|
154
|
-
<div class="mt-1" v-html="
|
|
154
|
+
<div class="mt-1" v-html="renderContent(note, renderOpts)"></div>
|
|
155
155
|
</div>
|
|
156
156
|
</div>
|
|
157
157
|
</div>
|
|
@@ -162,7 +162,7 @@ function handleContentClick(e: MouseEvent) {
|
|
|
162
162
|
<div class="space-y-3 mt-3">
|
|
163
163
|
<div v-for="(ex, i) in examples" :key="i" class="text-ink-600 text-sm leading-relaxed">
|
|
164
164
|
<span class="font-medium text-ink-400 text-xs uppercase tracking-wide">{{ t('concept.example') }} {{ i + 1 }}</span>
|
|
165
|
-
<div class="mt-1" v-html="
|
|
165
|
+
<div class="mt-1" v-html="renderContent(ex, renderOpts)"></div>
|
|
166
166
|
</div>
|
|
167
167
|
</div>
|
|
168
168
|
</div>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { computed, ref, watch, type ComputedRef } from 'vue';
|
|
2
2
|
import type { Concept, LocalizedConcept, ConceptSource, Designation } from 'glossarist';
|
|
3
3
|
import type { Manifest } from '../adapters/types';
|
|
4
|
-
import type { RenderOptions } from '../utils/
|
|
5
|
-
import {
|
|
4
|
+
import type { RenderOptions } from '../utils/content-renderer';
|
|
5
|
+
import { renderContent, cleanContent } from '../utils/content-renderer';
|
|
6
6
|
import { getAnnotations } from '../adapters/model-bridge';
|
|
7
7
|
import { getPreferredTerm, entryStatusColor, entryStatusLabel, entryStatusDefinition } from '../utils/concept-helpers';
|
|
8
8
|
import { sortLanguages } from '../utils/lang';
|
|
@@ -68,18 +68,18 @@ export function useConceptContent(
|
|
|
68
68
|
result.push({
|
|
69
69
|
lang,
|
|
70
70
|
lc,
|
|
71
|
-
renderedTerm:
|
|
71
|
+
renderedTerm: renderContent(getPreferredTerm(lc, '')),
|
|
72
72
|
definition,
|
|
73
|
-
renderedDefinition:
|
|
73
|
+
renderedDefinition: renderContent(definition, opts),
|
|
74
74
|
annotations,
|
|
75
|
-
renderedAnnotations: annotations.map((a: string) =>
|
|
75
|
+
renderedAnnotations: annotations.map((a: string) => renderContent(a, opts)),
|
|
76
76
|
notes,
|
|
77
|
-
renderedNotes: notes.map(n =>
|
|
77
|
+
renderedNotes: notes.map(n => renderContent(n, opts)),
|
|
78
78
|
examples,
|
|
79
|
-
renderedExamples: examples.map(e =>
|
|
79
|
+
renderedExamples: examples.map(e => renderContent(e, opts)),
|
|
80
80
|
sources: lc.sources,
|
|
81
81
|
designations: lc.terms,
|
|
82
|
-
renderedDesignations: new Map(lc.terms.map(d => [d.designation,
|
|
82
|
+
renderedDesignations: new Map(lc.terms.map(d => [d.designation, renderContent(d.designation)])),
|
|
83
83
|
entryStatus: lc.entryStatus ?? '',
|
|
84
84
|
classification: lc.classification,
|
|
85
85
|
reviewType: lc.reviewType,
|
|
@@ -26,51 +26,6 @@ export function compactToSlug(compact: string): string {
|
|
|
26
26
|
return compact.replace(/:/g, '-');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const expandedClasses = ref(new Set<string>(['gloss:Designation']));
|
|
30
|
-
|
|
31
|
-
const collapsedSections = ref(new Set<string>([
|
|
32
|
-
'objectProperty',
|
|
33
|
-
'datatypeProperty',
|
|
34
|
-
'shape',
|
|
35
|
-
'taxonomy',
|
|
36
|
-
'namedIndividual',
|
|
37
|
-
'annotationProperty',
|
|
38
|
-
]));
|
|
39
|
-
|
|
40
|
-
const searchQuery = ref('');
|
|
41
|
-
|
|
42
|
-
function toggleExpand(cls: OwlClass) {
|
|
43
|
-
const s = new Set(expandedClasses.value);
|
|
44
|
-
if (s.has(cls.compact)) s.delete(cls.compact);
|
|
45
|
-
else s.add(cls.compact);
|
|
46
|
-
expandedClasses.value = s;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function toggleSection(key: string) {
|
|
50
|
-
const s = new Set(collapsedSections.value);
|
|
51
|
-
if (s.has(key)) s.delete(key);
|
|
52
|
-
else s.add(key);
|
|
53
|
-
collapsedSections.value = s;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function expandAllSections() {
|
|
57
|
-
collapsedSections.value = new Set();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function collapseAllSections() {
|
|
61
|
-
collapsedSections.value = new Set(['objectProperty', 'datatypeProperty', 'shape', 'taxonomy', 'class', 'namedIndividual', 'annotationProperty']);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function childClasses(parentId: string): OwlClass[] {
|
|
65
|
-
const cls = getClass(parentId);
|
|
66
|
-
if (!cls) return [];
|
|
67
|
-
return cls.children.map(id => getClass(id)).filter((c): c is OwlClass => !!c);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function hasChildren(cls: OwlClass): boolean {
|
|
71
|
-
return cls.children.length > 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
29
|
const treeRoots = getClassTree();
|
|
75
30
|
const allShapes = getAllShapes();
|
|
76
31
|
const objectProperties = getObjectProperties();
|
|
@@ -97,28 +52,6 @@ const taxonomyLabels: Record<string, string> = {
|
|
|
97
52
|
grammarNumber: 'Grammar Number',
|
|
98
53
|
};
|
|
99
54
|
|
|
100
|
-
interface IndividualGroup {
|
|
101
|
-
key: string;
|
|
102
|
-
label: string;
|
|
103
|
-
concepts: { id: string; prefLabel: string }[];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const groupedIndividuals = computed<IndividualGroup[]>(() => {
|
|
107
|
-
return taxonomyKeys.map(key => {
|
|
108
|
-
const tax = (taxonomyData as Record<string, any>)[key];
|
|
109
|
-
if (!tax) return { key, label: taxonomyLabels[key] || key, concepts: [] };
|
|
110
|
-
const concepts = Object.values(tax.concepts as Record<string, any>).map((c: any) => ({
|
|
111
|
-
id: c.id,
|
|
112
|
-
prefLabel: c.prefLabel,
|
|
113
|
-
}));
|
|
114
|
-
return { key, label: tax.schemeLabel || taxonomyLabels[key] || key, concepts };
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const totalIndividuals = computed(() =>
|
|
119
|
-
groupedIndividuals.value.reduce((sum, g) => sum + g.concepts.length, 0),
|
|
120
|
-
);
|
|
121
|
-
|
|
122
55
|
const valuesToTaxonomy: Record<string, string> = {
|
|
123
56
|
'gloss:status': 'conceptStatus',
|
|
124
57
|
'gloss:entstatus': 'entryStatus',
|
|
@@ -132,6 +65,20 @@ const valuesToTaxonomy: Record<string, string> = {
|
|
|
132
65
|
'gloss:number': 'grammarNumber',
|
|
133
66
|
};
|
|
134
67
|
|
|
68
|
+
function childClasses(parentId: string): OwlClass[] {
|
|
69
|
+
const cls = getClass(parentId);
|
|
70
|
+
if (!cls) return [];
|
|
71
|
+
return cls.children.map(id => getClass(id)).filter((c): c is OwlClass => !!c);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function hasChildren(cls: OwlClass): boolean {
|
|
75
|
+
return cls.children.length > 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function matchesSearch(text: string, query: string): boolean {
|
|
79
|
+
return text.toLowerCase().includes(query.toLowerCase());
|
|
80
|
+
}
|
|
81
|
+
|
|
135
82
|
function taxonomyKeyForValuesFrom(valuesFrom: string | null): string | null {
|
|
136
83
|
if (!valuesFrom) return null;
|
|
137
84
|
return valuesToTaxonomy[valuesFrom] ?? null;
|
|
@@ -145,80 +92,126 @@ function getShapesForTaxonomy(taxonomyKey: string): OwlShape[] {
|
|
|
145
92
|
);
|
|
146
93
|
}
|
|
147
94
|
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
95
|
+
export function useOntologyNav() {
|
|
96
|
+
const expandedClasses = ref(new Set<string>(['gloss:Designation']));
|
|
97
|
+
|
|
98
|
+
const collapsedSections = ref(new Set<string>([
|
|
99
|
+
'objectProperty',
|
|
100
|
+
'datatypeProperty',
|
|
101
|
+
'shape',
|
|
102
|
+
'taxonomy',
|
|
103
|
+
'namedIndividual',
|
|
104
|
+
'annotationProperty',
|
|
105
|
+
]));
|
|
106
|
+
|
|
107
|
+
const searchQuery = ref('');
|
|
108
|
+
|
|
109
|
+
function toggleExpand(cls: OwlClass) {
|
|
110
|
+
const s = new Set(expandedClasses.value);
|
|
111
|
+
if (s.has(cls.compact)) s.delete(cls.compact);
|
|
112
|
+
else s.add(cls.compact);
|
|
113
|
+
expandedClasses.value = s;
|
|
157
114
|
}
|
|
158
|
-
walk(treeRoots, 0);
|
|
159
|
-
return items;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
function matchesSearch(text: string, query: string): boolean {
|
|
163
|
-
return text.toLowerCase().includes(query.toLowerCase());
|
|
164
|
-
}
|
|
165
115
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const matchedObjectProps: OwlProperty[] = [];
|
|
172
|
-
const matchedDatatypeProps: OwlProperty[] = [];
|
|
173
|
-
const matchedShapes: OwlShape[] = [];
|
|
174
|
-
const matchedIndividuals: { group: string; id: string; prefLabel: string }[] = [];
|
|
175
|
-
const matchedAnnotationProps: AnnotationProperty[] = [];
|
|
176
|
-
|
|
177
|
-
// Walk all classes (tree + leaves)
|
|
178
|
-
function walkAll(classes: OwlClass[]) {
|
|
179
|
-
for (const cls of classes) {
|
|
180
|
-
if (matchesSearch(cls.label, q) || matchesSearch(cls.compact, q)) {
|
|
181
|
-
matchedClasses.push(cls);
|
|
182
|
-
}
|
|
183
|
-
walkAll(childClasses(cls.compact));
|
|
184
|
-
}
|
|
116
|
+
function toggleSection(key: string) {
|
|
117
|
+
const s = new Set(collapsedSections.value);
|
|
118
|
+
if (s.has(key)) s.delete(key);
|
|
119
|
+
else s.add(key);
|
|
120
|
+
collapsedSections.value = s;
|
|
185
121
|
}
|
|
186
|
-
walkAll(treeRoots);
|
|
187
122
|
|
|
188
|
-
|
|
189
|
-
|
|
123
|
+
function expandAllSections() {
|
|
124
|
+
collapsedSections.value = new Set();
|
|
190
125
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
for (const s of allShapes) {
|
|
195
|
-
if (matchesSearch(s.label, q) || matchesSearch(s.compact, q)) matchedShapes.push(s);
|
|
126
|
+
|
|
127
|
+
function collapseAllSections() {
|
|
128
|
+
collapsedSections.value = new Set(['objectProperty', 'datatypeProperty', 'shape', 'taxonomy', 'class', 'namedIndividual', 'annotationProperty']);
|
|
196
129
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
130
|
+
|
|
131
|
+
const allNavItems = computed(() => {
|
|
132
|
+
const items: { id: string; label: string; depth: number }[] = [];
|
|
133
|
+
function walk(classes: OwlClass[], depth: number) {
|
|
134
|
+
for (const cls of classes) {
|
|
135
|
+
items.push({ id: cls.compact, label: cls.label, depth });
|
|
136
|
+
if (expandedClasses.value.has(cls.compact)) {
|
|
137
|
+
walk(childClasses(cls.compact), depth + 1);
|
|
138
|
+
}
|
|
201
139
|
}
|
|
202
140
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
141
|
+
walk(treeRoots, 0);
|
|
142
|
+
return items;
|
|
143
|
+
});
|
|
207
144
|
|
|
208
|
-
const
|
|
145
|
+
const searchResults = computed(() => {
|
|
146
|
+
const q = searchQuery.value.trim();
|
|
147
|
+
if (!q) return null;
|
|
148
|
+
|
|
149
|
+
const matchedClasses: OwlClass[] = [];
|
|
150
|
+
const matchedObjectProps: OwlProperty[] = [];
|
|
151
|
+
const matchedDatatypeProps: OwlProperty[] = [];
|
|
152
|
+
const matchedShapes: OwlShape[] = [];
|
|
153
|
+
const matchedIndividuals: { group: string; id: string; prefLabel: string }[] = [];
|
|
154
|
+
const matchedAnnotationProps: AnnotationProperty[] = [];
|
|
155
|
+
|
|
156
|
+
function walkAll(classes: OwlClass[]) {
|
|
157
|
+
for (const cls of classes) {
|
|
158
|
+
if (matchesSearch(cls.label, q) || matchesSearch(cls.compact, q)) {
|
|
159
|
+
matchedClasses.push(cls);
|
|
160
|
+
}
|
|
161
|
+
walkAll(childClasses(cls.compact));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
walkAll(treeRoots);
|
|
209
165
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
166
|
+
for (const p of objectProperties) {
|
|
167
|
+
if (matchesSearch(p.label, q) || matchesSearch(p.compact, q)) matchedObjectProps.push(p);
|
|
168
|
+
}
|
|
169
|
+
for (const p of datatypeProperties) {
|
|
170
|
+
if (matchesSearch(p.label, q) || matchesSearch(p.compact, q)) matchedDatatypeProps.push(p);
|
|
171
|
+
}
|
|
172
|
+
for (const s of allShapes) {
|
|
173
|
+
if (matchesSearch(s.label, q) || matchesSearch(s.compact, q)) matchedShapes.push(s);
|
|
174
|
+
}
|
|
175
|
+
for (const g of groupedIndividuals.value) {
|
|
176
|
+
for (const c of g.concepts) {
|
|
177
|
+
if (matchesSearch(c.prefLabel, q) || matchesSearch(c.id, q)) {
|
|
178
|
+
matchedIndividuals.push({ group: g.key, id: c.id, prefLabel: c.prefLabel });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const ap of annotationProperties) {
|
|
183
|
+
if (matchesSearch(ap.label, q) || matchesSearch(ap.compact, q)) matchedAnnotationProps.push(ap);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const total = matchedClasses.length + matchedObjectProps.length + matchedDatatypeProps.length + matchedShapes.length + matchedIndividuals.length + matchedAnnotationProps.length;
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
total,
|
|
190
|
+
classes: matchedClasses,
|
|
191
|
+
objectProperties: matchedObjectProps,
|
|
192
|
+
datatypeProperties: matchedDatatypeProps,
|
|
193
|
+
shapes: matchedShapes,
|
|
194
|
+
individuals: matchedIndividuals,
|
|
195
|
+
annotationProperties: matchedAnnotationProps,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const groupedIndividuals = computed<IndividualGroup[]>(() => {
|
|
200
|
+
return taxonomyKeys.map(key => {
|
|
201
|
+
const tax = (taxonomyData as Record<string, any>)[key];
|
|
202
|
+
if (!tax) return { key, label: taxonomyLabels[key] || key, concepts: [] };
|
|
203
|
+
const concepts = Object.values(tax.concepts as Record<string, any>).map((c: any) => ({
|
|
204
|
+
id: c.id,
|
|
205
|
+
prefLabel: c.prefLabel,
|
|
206
|
+
}));
|
|
207
|
+
return { key, label: tax.schemeLabel || taxonomyLabels[key] || key, concepts };
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const totalIndividuals = computed(() =>
|
|
212
|
+
groupedIndividuals.value.reduce((sum, g) => sum + g.concepts.length, 0),
|
|
213
|
+
);
|
|
220
214
|
|
|
221
|
-
export function useOntologyNav() {
|
|
222
215
|
return {
|
|
223
216
|
expandedClasses,
|
|
224
217
|
collapsedSections,
|
|
@@ -248,3 +241,9 @@ export function useOntologyNav() {
|
|
|
248
241
|
ENTITY_TYPE_META,
|
|
249
242
|
};
|
|
250
243
|
}
|
|
244
|
+
|
|
245
|
+
interface IndividualGroup {
|
|
246
|
+
key: string;
|
|
247
|
+
label: string;
|
|
248
|
+
concepts: { id: string; prefLabel: string }[];
|
|
249
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, watch } from 'vue';
|
|
2
|
-
import type { RenderOptions, BibResolver, FigResolver } from '../utils/
|
|
2
|
+
import type { RenderOptions, BibResolver, FigResolver } from '../utils/content-renderer';
|
|
3
3
|
import { getFactory } from '../adapters/factory';
|
|
4
4
|
import { escapeAttr } from '../utils/escape';
|
|
5
5
|
|