@glossarist/concept-browser 0.7.37 → 0.7.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.42",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -74,6 +74,8 @@ const {
|
|
|
74
74
|
navigateRelated,
|
|
75
75
|
} = useConceptEdges(conceptComputed, registerIdComputed, manifestComputed, edgesComputed, router);
|
|
76
76
|
|
|
77
|
+
const hoveredEdgeDisplay = ref<{ designation: string; conceptId: string; tooltip: string } | null>(null);
|
|
78
|
+
|
|
77
79
|
const uriCopied = ref(false);
|
|
78
80
|
function copyUri() {
|
|
79
81
|
const uri = conceptUri(props.concept, props.registerId, props.manifest.uriBase);
|
|
@@ -473,42 +475,63 @@ const nonVerbalReps = computed(() => {
|
|
|
473
475
|
<!-- Relations -->
|
|
474
476
|
<div v-if="outgoingEdges.length || incomingEdges.length" class="card p-5">
|
|
475
477
|
<div class="section-label">{{ t('concept.relations') }}</div>
|
|
478
|
+
|
|
479
|
+
<!-- Outgoing -->
|
|
476
480
|
<div v-if="outgoingEdges.length" class="mt-3">
|
|
477
|
-
<div class="text-xs text-ink-300 mb-
|
|
478
|
-
<div class="space-y-
|
|
481
|
+
<div class="text-xs text-ink-300 mb-1.5">{{ t('concept.outgoing') }} ({{ outgoingEdges.length }})</div>
|
|
482
|
+
<div class="space-y-0.5 max-h-56 overflow-y-auto pr-1 -mr-1">
|
|
479
483
|
<button
|
|
480
484
|
v-for="edge in outgoingEdges"
|
|
481
485
|
:key="edge.target + edge.type"
|
|
482
486
|
@click="navigateEdge(edge)"
|
|
483
|
-
|
|
484
|
-
|
|
487
|
+
@mouseenter="hoveredEdgeDisplay = getEdgeDisplay(edge.target)"
|
|
488
|
+
@mouseleave="hoveredEdgeDisplay = null"
|
|
489
|
+
@focus="hoveredEdgeDisplay = getEdgeDisplay(edge.target)"
|
|
490
|
+
class="concept-link block w-full text-left rounded-md px-1.5 py-1 hover:bg-ink-50 transition-colors"
|
|
485
491
|
:class="getEdgeDisplay(edge.target).isLocal ? '' : 'xref-external'"
|
|
486
492
|
>
|
|
487
|
-
<
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
493
|
+
<div class="flex items-center gap-1 mb-0.5">
|
|
494
|
+
<span class="badge text-[9px] flex-shrink-0" :class="edgeBadgeColor(edge.type, 'out')">{{ relationshipLabel(edge.type) }} →</span>
|
|
495
|
+
<span v-if="getEdgeDisplay(edge.target).badge" class="badge badge-gray text-[9px] flex-shrink-0 truncate">{{ getEdgeDisplay(edge.target).badge!.title }}</span>
|
|
496
|
+
</div>
|
|
497
|
+
<div class="text-sm text-ink-700 leading-snug line-clamp-2">{{ getEdgeDisplay(edge.target).designation || edge.label || getEdgeDisplay(edge.target).conceptId }}</div>
|
|
491
498
|
</button>
|
|
492
499
|
</div>
|
|
493
500
|
</div>
|
|
501
|
+
|
|
502
|
+
<!-- Incoming -->
|
|
494
503
|
<div v-if="incomingEdges.length" class="mt-3 pt-3 border-t border-ink-100/60">
|
|
495
|
-
<div class="text-xs text-ink-300 mb-
|
|
496
|
-
<div class="space-y-
|
|
504
|
+
<div class="text-xs text-ink-300 mb-1.5">{{ t('concept.incoming') }} ({{ incomingEdges.length }})</div>
|
|
505
|
+
<div class="space-y-0.5 max-h-40 overflow-y-auto pr-1 -mr-1">
|
|
497
506
|
<button
|
|
498
507
|
v-for="edge in incomingEdges"
|
|
499
508
|
:key="edge.source + edge.type"
|
|
500
509
|
@click="navigateEdge(edge)"
|
|
501
|
-
|
|
502
|
-
|
|
510
|
+
@mouseenter="hoveredEdgeDisplay = getEdgeDisplay(edge.source)"
|
|
511
|
+
@mouseleave="hoveredEdgeDisplay = null"
|
|
512
|
+
@focus="hoveredEdgeDisplay = getEdgeDisplay(edge.source)"
|
|
513
|
+
class="concept-link block w-full text-left rounded-md px-1.5 py-1 hover:bg-ink-50 transition-colors"
|
|
503
514
|
:class="getEdgeDisplay(edge.source).isLocal ? '' : 'xref-external'"
|
|
504
515
|
>
|
|
505
|
-
<
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
516
|
+
<div class="flex items-center gap-1 mb-0.5">
|
|
517
|
+
<span class="badge text-[9px] flex-shrink-0" :class="edgeBadgeColor(edge.type, 'in')">← {{ relationshipLabel(inverseEdgeType(edge.type)) }}</span>
|
|
518
|
+
<span v-if="getEdgeDisplay(edge.source).badge" class="badge badge-gray text-[9px] flex-shrink-0 truncate">{{ getEdgeDisplay(edge.source).badge!.title }}</span>
|
|
519
|
+
</div>
|
|
520
|
+
<div class="text-sm text-ink-700 leading-snug line-clamp-2">{{ getEdgeDisplay(edge.source).designation || edge.label || getEdgeDisplay(edge.source).conceptId }}</div>
|
|
509
521
|
</button>
|
|
510
522
|
</div>
|
|
511
523
|
</div>
|
|
524
|
+
|
|
525
|
+
<!-- Hover detail strip (fixed area at bottom of card) -->
|
|
526
|
+
<Transition name="fade">
|
|
527
|
+
<div
|
|
528
|
+
v-if="hoveredEdgeDisplay"
|
|
529
|
+
class="mt-3 pt-3 border-t border-ink-100/60"
|
|
530
|
+
>
|
|
531
|
+
<div class="text-xs text-ink-300 mb-0.5">{{ t('concept.identifier') || 'Identifier' }}</div>
|
|
532
|
+
<div class="font-mono text-[10px] text-ink-500 break-all leading-tight">{{ hoveredEdgeDisplay.conceptId }}</div>
|
|
533
|
+
</div>
|
|
534
|
+
</Transition>
|
|
512
535
|
</div>
|
|
513
536
|
|
|
514
537
|
<!-- Domains -->
|
|
@@ -659,3 +682,14 @@ const nonVerbalReps = computed(() => {
|
|
|
659
682
|
</div>
|
|
660
683
|
</div>
|
|
661
684
|
</template>
|
|
685
|
+
|
|
686
|
+
<style scoped>
|
|
687
|
+
.fade-enter-active,
|
|
688
|
+
.fade-leave-active {
|
|
689
|
+
transition: opacity 0.15s ease;
|
|
690
|
+
}
|
|
691
|
+
.fade-enter-from,
|
|
692
|
+
.fade-leave-to {
|
|
693
|
+
opacity: 0;
|
|
694
|
+
}
|
|
695
|
+
</style>
|
|
@@ -58,12 +58,13 @@ export function useConceptEdges(
|
|
|
58
58
|
const designation = node
|
|
59
59
|
? (node.designations[locale.value] || node.designations.eng || Object.values(node.designations)[0] || '')
|
|
60
60
|
: '';
|
|
61
|
-
const tooltipLines: string[] = [
|
|
61
|
+
const tooltipLines: string[] = [conceptId];
|
|
62
62
|
if (node) {
|
|
63
63
|
for (const [lang, des] of Object.entries(node.designations)) {
|
|
64
64
|
tooltipLines.push(`${langLabel(lang)}: ${des}`);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
tooltipLines.push(uri);
|
|
67
68
|
let badge: { id: string; title: string } | null = null;
|
|
68
69
|
if (resolution.type === 'internal' && resolution.registerId !== registerId.value) {
|
|
69
70
|
const m = store.manifests.get(resolution.registerId);
|
package/src/style.css
CHANGED
|
@@ -334,6 +334,35 @@
|
|
|
334
334
|
color: #dddde6;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
/* ── Brand colors: lighten for dark backgrounds ── */
|
|
338
|
+
/* ISO red (#e3000f) is too harsh on dark — use a softer coral */
|
|
339
|
+
.dark {
|
|
340
|
+
--brand-primary: #ff5d5d;
|
|
341
|
+
--brand-primary-rgb: 255, 93, 93;
|
|
342
|
+
--brand-dark: #1a1b2e;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Concept/xref links: lighten in dark mode (override pure red) */
|
|
346
|
+
.dark .concept-link,
|
|
347
|
+
.dark .xref-link {
|
|
348
|
+
color: #ff6b6b;
|
|
349
|
+
}
|
|
350
|
+
.dark .concept-link:hover,
|
|
351
|
+
.dark .xref-link:hover {
|
|
352
|
+
filter: brightness(1.15);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Brand badges: readable background in dark mode */
|
|
356
|
+
.dark .badge-brand {
|
|
357
|
+
background-color: rgba(255, 93, 93, 0.15);
|
|
358
|
+
color: #ff8a8a;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Primary buttons: avoid harsh red hover in dark mode */
|
|
362
|
+
.dark .btn-primary:hover {
|
|
363
|
+
background-color: #c41818;
|
|
364
|
+
}
|
|
365
|
+
|
|
337
366
|
/* ── Surfaces ── */
|
|
338
367
|
.dark .bg-surface { background-color: #0f1020 !important; }
|
|
339
368
|
.dark .bg-surface-alt { background-color: #161728 !important; }
|
|
@@ -30,10 +30,17 @@ const chunkLoading = ref(false);
|
|
|
30
30
|
// Background chunk preloading via requestIdleCallback
|
|
31
31
|
let idlePreloadHandle: ReturnType<typeof requestIdleCallback> | ReturnType<typeof setTimeout> | null = null;
|
|
32
32
|
|
|
33
|
-
watch(adapter, (a) => {
|
|
34
|
-
if (idlePreloadHandle !== null) return;
|
|
33
|
+
watch(adapter, async (a) => {
|
|
35
34
|
if (!a || !a.index) return;
|
|
36
35
|
|
|
36
|
+
// If a section filter is active, load all chunks immediately (not idle)
|
|
37
|
+
if (sectionQuery.value && !allChunksLoaded.value) {
|
|
38
|
+
await ensureAllChunksForFilter(true);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (idlePreloadHandle !== null) return;
|
|
43
|
+
|
|
37
44
|
const schedule: (cb: () => void) => number = typeof requestIdleCallback !== 'undefined'
|
|
38
45
|
? (cb) => requestIdleCallback(cb, { timeout: 2000 })
|
|
39
46
|
: (cb) => window.setTimeout(cb, 0);
|
|
@@ -87,6 +94,7 @@ const allChunksLoaded = ref(false);
|
|
|
87
94
|
const selectedLang = ref<string | null>(null);
|
|
88
95
|
const viewMode = ref<'systematic' | 'alphabetical'>('systematic');
|
|
89
96
|
const sectionQuery = computed(() => (route.query.section as string) || null);
|
|
97
|
+
const page = ref(1);
|
|
90
98
|
|
|
91
99
|
interface LangOption {
|
|
92
100
|
code: string;
|
|
@@ -124,10 +132,14 @@ onUnmounted(() => window.removeEventListener('keydown', onGlobalKeydown));
|
|
|
124
132
|
|
|
125
133
|
async function ensureAllChunksForFilter(needsLoad: boolean) {
|
|
126
134
|
page.value = 1;
|
|
127
|
-
if (needsLoad
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
if (!needsLoad || allChunksLoaded.value) return;
|
|
136
|
+
const a = adapter.value;
|
|
137
|
+
if (!a?.index) return;
|
|
138
|
+
chunkLoading.value = true;
|
|
139
|
+
try {
|
|
140
|
+
await a.ensureAllChunksLoaded();
|
|
130
141
|
allChunksLoaded.value = true;
|
|
142
|
+
} finally {
|
|
131
143
|
chunkLoading.value = false;
|
|
132
144
|
}
|
|
133
145
|
}
|
|
@@ -138,7 +150,7 @@ watch(filter, async (q) => {
|
|
|
138
150
|
|
|
139
151
|
watch(sectionQuery, async () => {
|
|
140
152
|
await ensureAllChunksForFilter(!!sectionQuery.value);
|
|
141
|
-
});
|
|
153
|
+
}, { immediate: true });
|
|
142
154
|
|
|
143
155
|
watch(selectedLang, async (lang) => {
|
|
144
156
|
await ensureAllChunksForFilter(!!lang);
|
|
@@ -202,7 +214,6 @@ const alphabetGroups = computed(() => {
|
|
|
202
214
|
return [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
203
215
|
});
|
|
204
216
|
|
|
205
|
-
const page = ref(1);
|
|
206
217
|
const perPage = 50;
|
|
207
218
|
|
|
208
219
|
// Check if the current page range is loaded in the index
|