@glossarist/concept-browser 0.7.78 → 0.7.80
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 +109 -39
- package/scripts/generate-data.mjs +63 -27
- package/src/__tests__/build-edges-redundancy.test.ts +101 -0
- package/src/__tests__/composables/use-concept-diff.test.ts +240 -0
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +132 -0
- package/src/__tests__/composables/use-dataset-stats.test.ts +96 -0
- package/src/__tests__/config/page-types.test.ts +82 -0
- package/src/__tests__/model-bridge-bridges.test.ts +3 -2
- package/src/__tests__/model-bridge-citation.test.ts +1 -1
- package/src/__tests__/ontology-registry.test.ts +2 -2
- package/src/__tests__/partitive-relation-styling.test.ts +82 -0
- package/src/__tests__/partitive-relation.test.ts +285 -0
- package/src/adapters/GraphDataSource.ts +16 -6
- package/src/adapters/dataset-stats.ts +13 -0
- package/src/adapters/non-verbal/glossarist-augment.d.ts +155 -4
- package/src/adapters/types.ts +27 -17
- package/src/components/AppSidebar.vue +14 -57
- package/src/components/ConceptDetail.vue +29 -0
- package/src/components/ConceptDiffModal.vue +120 -0
- package/src/components/ConceptDiffView.vue +192 -0
- package/src/components/ConceptEditionRail.vue +61 -36
- package/src/components/PartitiveRelationList.vue +139 -0
- package/src/components/groups/DatasetGroupRenderer.vue +14 -4
- package/src/components/groups/DefaultGroupSidebar.vue +28 -11
- package/src/components/groups/LineageGroupSidebar.vue +17 -29
- package/src/composables/use-concept-diff.ts +231 -0
- package/src/composables/use-concept-edges.ts +66 -45
- package/src/composables/use-dataset-stats.ts +52 -0
- package/src/config/page-types.ts +67 -0
- package/src/config/types.ts +6 -1
- package/src/config/use-site-config.ts +13 -29
- package/src/data/taxonomies.json +24 -1
- package/src/i18n/locales/eng.yml +6 -0
- package/src/utils/partitive-relation-styling.ts +62 -0
- package/src/views/DatasetView.vue +5 -3
- package/src/views/StatsView.vue +9 -22
- package/src/__tests__/hyperedge-styling.test.ts +0 -57
- package/src/__tests__/partitive-hyperedge.test.ts +0 -199
- package/src/components/PartitiveHyperedgeList.vue +0 -112
- package/src/islands/BibliographyIsland.vue +0 -26
- package/src/islands/ConceptDetailIsland.vue +0 -27
- package/src/islands/ConceptDiffIsland.vue +0 -214
- package/src/islands/ConceptIsland.vue +0 -102
- package/src/islands/GraphIsland.vue +0 -15
- package/src/islands/GroupRendererIsland.vue +0 -15
- package/src/islands/HeaderIsland.vue +0 -10
- package/src/islands/NonVerbalIsland.vue +0 -75
- package/src/islands/RelationSphereIsland.vue +0 -20
- package/src/islands/SearchIsland.vue +0 -23
- package/src/islands/SeriesCardIsland.vue +0 -12
- package/src/islands/ShortcutsIsland.vue +0 -13
- package/src/islands/SidebarIsland.vue +0 -15
- package/src/islands/SupersessionDiffIsland.vue +0 -267
- package/src/utils/hyperedge-styling.ts +0 -48
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ErrorBoundary title="Graph view failed" retry-key="graph">
|
|
3
|
-
<GraphPanel :nodes="nodes" :edges="edges" />
|
|
4
|
-
</ErrorBoundary>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script setup lang="ts">
|
|
8
|
-
import GraphPanel from '../components/GraphPanel.vue';
|
|
9
|
-
import ErrorBoundary from '../components/ErrorBoundary.vue';
|
|
10
|
-
|
|
11
|
-
defineProps<{
|
|
12
|
-
nodes: Array<{ id: string; label: string; group?: string }>;
|
|
13
|
-
edges: Array<{ source: string; target: string; type?: string }>;
|
|
14
|
-
}>();
|
|
15
|
-
</script>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ErrorBoundary title="Group renderer failed">
|
|
3
|
-
<DatasetGroupRenderer :group="group" :datasets="datasets" />
|
|
4
|
-
</ErrorBoundary>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script setup lang="ts">
|
|
8
|
-
import DatasetGroupRenderer from '../components/DatasetGroupRenderer.vue';
|
|
9
|
-
import ErrorBoundary from '../components/ErrorBoundary.vue';
|
|
10
|
-
|
|
11
|
-
defineProps<{
|
|
12
|
-
group: any;
|
|
13
|
-
datasets: any[];
|
|
14
|
-
}>();
|
|
15
|
-
</script>
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="nvr-island">
|
|
3
|
-
<section v-if="figures.length" class="mb-6">
|
|
4
|
-
<h2 class="text-sm font-semibold text-ink-500 uppercase tracking-wider mb-3">Figures</h2>
|
|
5
|
-
<div class="space-y-4">
|
|
6
|
-
<figure v-for="(fig, i) in figures" :key="i" class="card p-4">
|
|
7
|
-
<figcaption class="text-sm text-ink-600 dark:text-ink-300 mb-2" dir="auto">{{ fig.caption || `Figure ${i + 1}` }}</figcaption>
|
|
8
|
-
<div v-if="fig.images?.length" class="grid grid-cols-2 gap-2">
|
|
9
|
-
<img
|
|
10
|
-
v-for="(img, j) in fig.images"
|
|
11
|
-
:key="j"
|
|
12
|
-
:src="`/data/${registerId}/images/${img.src}`"
|
|
13
|
-
:alt="fig.altText || fig.caption || `Figure ${i + 1}`"
|
|
14
|
-
class="rounded border border-ink-200 dark:border-ink-700 max-w-full"
|
|
15
|
-
loading="lazy"
|
|
16
|
-
/>
|
|
17
|
-
</div>
|
|
18
|
-
<p v-if="fig.description" class="text-xs text-ink-400 mt-2" dir="auto">{{ fig.description }}</p>
|
|
19
|
-
</figure>
|
|
20
|
-
</div>
|
|
21
|
-
</section>
|
|
22
|
-
|
|
23
|
-
<section v-if="tables.length" class="mb-6">
|
|
24
|
-
<h2 class="text-sm font-semibold text-ink-500 uppercase tracking-wider mb-3">Tables</h2>
|
|
25
|
-
<div class="space-y-4">
|
|
26
|
-
<div v-for="(tbl, i) in tables" :key="i" class="card p-4">
|
|
27
|
-
<p class="text-sm text-ink-600 dark:text-ink-300 mb-2" dir="auto">{{ tbl.caption || `Table ${i + 1}` }}</p>
|
|
28
|
-
<div v-if="tbl.rows?.length" class="overflow-x-auto">
|
|
29
|
-
<table class="w-full text-sm">
|
|
30
|
-
<tbody>
|
|
31
|
-
<tr v-for="(row, r) in tbl.rows" :key="r" class="border-b border-ink-100 dark:border-ink-700">
|
|
32
|
-
<td v-for="(cell, c) in row" :key="c" class="py-1 px-2 text-ink-600 dark:text-ink-300" dir="auto">{{ cell }}</td>
|
|
33
|
-
</tr>
|
|
34
|
-
</tbody>
|
|
35
|
-
</table>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
</section>
|
|
40
|
-
|
|
41
|
-
<section v-if="formulas.length" class="mb-6">
|
|
42
|
-
<h2 class="text-sm font-semibold text-ink-500 uppercase tracking-wider mb-3">Formulas</h2>
|
|
43
|
-
<div class="space-y-3">
|
|
44
|
-
<div v-for="(fml, i) in formulas" :key="i" class="card p-4">
|
|
45
|
-
<p v-if="fml.caption" class="text-sm text-ink-600 dark:text-ink-300 mb-2" dir="auto">{{ fml.caption }}</p>
|
|
46
|
-
<code class="text-sm font-mono text-ink-700 dark:text-ink-200 bg-ink-50 dark:bg-ink-800 px-2 py-1 rounded" dir="auto">{{ fml.content || fml.latex || fml.text || '' }}</code>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</section>
|
|
50
|
-
</div>
|
|
51
|
-
</template>
|
|
52
|
-
|
|
53
|
-
<script setup lang="ts">
|
|
54
|
-
import { computed } from 'vue';
|
|
55
|
-
|
|
56
|
-
const props = defineProps<{
|
|
57
|
-
nonVerbalReps?: Array<{
|
|
58
|
-
type?: string;
|
|
59
|
-
caption?: string;
|
|
60
|
-
description?: string;
|
|
61
|
-
images?: Array<{ src: string }>;
|
|
62
|
-
altText?: string;
|
|
63
|
-
rows?: string[][];
|
|
64
|
-
content?: string;
|
|
65
|
-
latex?: string;
|
|
66
|
-
text?: string;
|
|
67
|
-
}>;
|
|
68
|
-
registerId: string;
|
|
69
|
-
}>();
|
|
70
|
-
|
|
71
|
-
const all = computed(() => props.nonVerbalReps || []);
|
|
72
|
-
const figures = computed(() => all.value.filter(r => r.type === 'figure'));
|
|
73
|
-
const tables = computed(() => all.value.filter(r => r.type === 'table'));
|
|
74
|
-
const formulas = computed(() => all.value.filter(r => r.type === 'formula'));
|
|
75
|
-
</script>
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ErrorBoundary title="Sphere view failed" :retry-key="conceptUri">
|
|
3
|
-
<RelationSphere
|
|
4
|
-
:concept="concept"
|
|
5
|
-
:edges="edges"
|
|
6
|
-
:concept-uri="conceptUri"
|
|
7
|
-
/>
|
|
8
|
-
</ErrorBoundary>
|
|
9
|
-
</template>
|
|
10
|
-
|
|
11
|
-
<script setup lang="ts">
|
|
12
|
-
import RelationSphere from '../components/RelationSphere.vue';
|
|
13
|
-
import ErrorBoundary from '../components/ErrorBoundary.vue';
|
|
14
|
-
|
|
15
|
-
defineProps<{
|
|
16
|
-
concept: any;
|
|
17
|
-
conceptUri: string;
|
|
18
|
-
edges?: any[];
|
|
19
|
-
}>();
|
|
20
|
-
</script>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ErrorBoundary title="Search failed" retry-key="search">
|
|
3
|
-
<div class="search-island">
|
|
4
|
-
<SearchBar />
|
|
5
|
-
<SearchResults />
|
|
6
|
-
</div>
|
|
7
|
-
</ErrorBoundary>
|
|
8
|
-
</template>
|
|
9
|
-
|
|
10
|
-
<script setup lang="ts">
|
|
11
|
-
import SearchBar from '../components/SearchBar.vue';
|
|
12
|
-
import SearchResults from '../components/SearchResults.vue';
|
|
13
|
-
import ErrorBoundary from '../components/ErrorBoundary.vue';
|
|
14
|
-
|
|
15
|
-
defineProps<{
|
|
16
|
-
concepts?: Array<{
|
|
17
|
-
conceptId: string;
|
|
18
|
-
registerId: string;
|
|
19
|
-
term: string;
|
|
20
|
-
status: string;
|
|
21
|
-
}>;
|
|
22
|
-
}>();
|
|
23
|
-
</script>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ErrorBoundary title="Series card failed">
|
|
3
|
-
<DatasetSeriesCard :register-id="registerId" />
|
|
4
|
-
</ErrorBoundary>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script setup lang="ts">
|
|
8
|
-
import DatasetSeriesCard from '../components/DatasetSeriesCard.vue';
|
|
9
|
-
import ErrorBoundary from '../components/ErrorBoundary.vue';
|
|
10
|
-
|
|
11
|
-
defineProps<{ registerId: string }>();
|
|
12
|
-
</script>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ErrorBoundary title="Shortcuts failed">
|
|
3
|
-
<ShortcutsModal :open="open" @close="$emit('close')" />
|
|
4
|
-
</ErrorBoundary>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script setup lang="ts">
|
|
8
|
-
import ShortcutsModal from '../components/ShortcutsModal.vue';
|
|
9
|
-
import ErrorBoundary from '../components/ErrorBoundary.vue';
|
|
10
|
-
|
|
11
|
-
defineProps<{ open: boolean }>();
|
|
12
|
-
defineEmits<{ close: [] }>();
|
|
13
|
-
</script>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ErrorBoundary title="Sidebar failed">
|
|
3
|
-
<AppSidebar />
|
|
4
|
-
</ErrorBoundary>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script setup lang="ts">
|
|
8
|
-
import AppSidebar from '../components/AppSidebar.vue';
|
|
9
|
-
import ErrorBoundary from '../components/ErrorBoundary.vue';
|
|
10
|
-
|
|
11
|
-
defineProps<{
|
|
12
|
-
activeDataset?: string;
|
|
13
|
-
activeGroup?: string;
|
|
14
|
-
}>();
|
|
15
|
-
</script>
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="supersession-diff">
|
|
3
|
-
<!-- Supersession badge -->
|
|
4
|
-
<div v-if="supersedes || supersededBy" class="mb-4 flex items-center gap-2 flex-wrap">
|
|
5
|
-
<span v-if="supersededBy" class="inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-full bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300 font-medium">
|
|
6
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 11l5-5m0 0l5 5m-5-5v12"/></svg>
|
|
7
|
-
Superseded by
|
|
8
|
-
<a :href="`/dataset/${supersededBy.registerId}/concept/${supersededBy.conceptId}`" class="underline hover:text-amber-800 dark:hover:text-amber-200">
|
|
9
|
-
{{ supersededBy.term || supersededBy.conceptId }}
|
|
10
|
-
</a>
|
|
11
|
-
<button v-if="supersededBy.conceptData" @click="compareWith(supersededBy)" class="ml-1 text-amber-600 dark:text-amber-400 hover:text-amber-800 dark:hover:text-amber-200 underline">
|
|
12
|
-
compare →
|
|
13
|
-
</button>
|
|
14
|
-
</span>
|
|
15
|
-
<span v-if="supersedes" class="inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium">
|
|
16
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 13l-5 5m0 0l-5-5m5 5V6"/></svg>
|
|
17
|
-
Supersedes
|
|
18
|
-
<a :href="`/dataset/${supersedes.registerId}/concept/${supersedes.conceptId}`" class="underline hover:text-blue-800 dark:hover:text-blue-200">
|
|
19
|
-
{{ supersedes.term || supersedes.conceptId }}
|
|
20
|
-
</a>
|
|
21
|
-
<button v-if="supersedes.conceptData" @click="compareWith(supersedes)" class="ml-1 text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-200 underline">
|
|
22
|
-
compare →
|
|
23
|
-
</button>
|
|
24
|
-
</span>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<!-- Comparison panel (slides in when user clicks "compare") -->
|
|
28
|
-
<Transition name="slide-down">
|
|
29
|
-
<div v-if="activeComparison" class="mt-4 border border-ink-200 dark:border-ink-700 rounded-lg overflow-hidden">
|
|
30
|
-
<!-- Header -->
|
|
31
|
-
<div class="flex items-center justify-between px-4 py-2.5 bg-ink-50/50 dark:bg-ink-800/30 border-b border-ink-100/60 dark:border-ink-700/40">
|
|
32
|
-
<div class="flex items-center gap-3">
|
|
33
|
-
<span class="text-sm font-medium text-ink-600 dark:text-ink-300">
|
|
34
|
-
Comparing: {{ concept.designations?.eng ?? concept.conceptId }}
|
|
35
|
-
</span>
|
|
36
|
-
<svg class="w-4 h-4 text-ink-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"/></svg>
|
|
37
|
-
<span class="text-sm font-medium text-ink-600 dark:text-ink-300">
|
|
38
|
-
{{ activeComparison.term || activeComparison.conceptId }}
|
|
39
|
-
</span>
|
|
40
|
-
</div>
|
|
41
|
-
<button @click="activeComparison = null; diffResult = null" class="text-ink-400 hover:text-ink-600 dark:hover:text-ink-200">
|
|
42
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
43
|
-
</button>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<!-- Diff content -->
|
|
47
|
-
<div class="p-4 space-y-4">
|
|
48
|
-
<!-- Similarity score -->
|
|
49
|
-
<div v-if="diffResult?.similarity != null" class="flex items-center gap-3">
|
|
50
|
-
<span class="text-xs text-ink-400 w-20">Similarity</span>
|
|
51
|
-
<div class="flex-1 max-w-xs bg-ink-100 dark:bg-ink-800 rounded-full h-2.5 overflow-hidden">
|
|
52
|
-
<div
|
|
53
|
-
class="h-full rounded-full transition-all duration-500"
|
|
54
|
-
:class="similarityColor(diffResult.similarity)"
|
|
55
|
-
:style="{ width: `${Math.round(diffResult.similarity * 100)}%` }"
|
|
56
|
-
/>
|
|
57
|
-
</div>
|
|
58
|
-
<span class="text-sm font-medium" :class="similarityTextColor(diffResult.similarity)">
|
|
59
|
-
{{ Math.round(diffResult.similarity * 100) }}%
|
|
60
|
-
</span>
|
|
61
|
-
<span v-if="diffResult.stats" class="text-xs text-ink-400">
|
|
62
|
-
(+{{ diffResult.stats.added }} / -{{ diffResult.stats.removed }} / ~{{ diffResult.stats.changed }})
|
|
63
|
-
</span>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<!-- Definition diff -->
|
|
67
|
-
<section v-if="diffSections.definition">
|
|
68
|
-
<h4 class="text-xs font-semibold text-ink-400 uppercase tracking-wider mb-2">Definition</h4>
|
|
69
|
-
<div class="font-mono text-xs leading-relaxed">
|
|
70
|
-
<template v-if="diffSections.definition.hunks">
|
|
71
|
-
<span
|
|
72
|
-
v-for="(hunk, i) in diffSections.definition.hunks"
|
|
73
|
-
:key="i"
|
|
74
|
-
:class="{
|
|
75
|
-
'bg-emerald-100/70 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-300': hunk.type === 'added',
|
|
76
|
-
'bg-red-100/70 dark:bg-red-900/20 text-red-700 dark:text-red-300 line-through': hunk.type === 'removed',
|
|
77
|
-
'text-ink-500': hunk.type === 'equal',
|
|
78
|
-
}"
|
|
79
|
-
dir="auto"
|
|
80
|
-
>{{ hunk.text }} </span>
|
|
81
|
-
</template>
|
|
82
|
-
<span v-else-if="diffSections.definition.type === 'added'" class="text-emerald-600 dark:text-emerald-400" dir="auto">
|
|
83
|
-
{{ diffSections.definition.value }}
|
|
84
|
-
</span>
|
|
85
|
-
<span v-else-if="diffSections.definition.type === 'removed'" class="text-red-500 dark:text-red-400 line-through" dir="auto">
|
|
86
|
-
{{ diffSections.definition.value }}
|
|
87
|
-
</span>
|
|
88
|
-
</div>
|
|
89
|
-
</section>
|
|
90
|
-
|
|
91
|
-
<!-- Designations diff -->
|
|
92
|
-
<section v-if="diffSections.designations">
|
|
93
|
-
<h4 class="text-xs font-semibold text-ink-400 uppercase tracking-wider mb-2">Designations</h4>
|
|
94
|
-
<div class="space-y-1">
|
|
95
|
-
<div v-for="(item, i) in diffSections.designations.items" :key="i" class="flex items-baseline gap-2 text-sm">
|
|
96
|
-
<span
|
|
97
|
-
:class="{
|
|
98
|
-
'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300': item.type === 'added',
|
|
99
|
-
'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300': item.type === 'removed',
|
|
100
|
-
'bg-ink-100 dark:bg-ink-800 text-ink-500': !item.type,
|
|
101
|
-
}"
|
|
102
|
-
class="text-xs px-1.5 py-0.5 rounded font-medium"
|
|
103
|
-
>{{ item.type ?? 'same' }}</span>
|
|
104
|
-
<span :class="{ 'line-through opacity-60': item.type === 'removed' }" dir="auto">{{ item.text }}</span>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
</section>
|
|
108
|
-
|
|
109
|
-
<!-- Notes / Examples changes -->
|
|
110
|
-
<section v-if="diffSections.notes?.length || diffSections.examples?.length">
|
|
111
|
-
<h4 class="text-xs font-semibold text-ink-400 uppercase tracking-wider mb-2">Other changes</h4>
|
|
112
|
-
<div class="space-y-1">
|
|
113
|
-
<div v-for="(note, i) in diffSections.notes" :key="'n'+i" class="text-sm flex items-baseline gap-2">
|
|
114
|
-
<span class="text-xs px-1.5 py-0.5 rounded" :class="note.type === 'added' ? 'bg-emerald-100 text-emerald-600' : 'bg-red-100 text-red-600'">
|
|
115
|
-
note {{ note.type }}
|
|
116
|
-
</span>
|
|
117
|
-
<span dir="auto">{{ note.text }}</span>
|
|
118
|
-
</div>
|
|
119
|
-
<div v-for="(ex, i) in diffSections.examples" :key="'e'+i" class="text-sm flex items-baseline gap-2">
|
|
120
|
-
<span class="text-xs px-1.5 py-0.5 rounded" :class="ex.type === 'added' ? 'bg-emerald-100 text-emerald-600' : 'bg-red-100 text-red-600'">
|
|
121
|
-
example {{ ex.type }}
|
|
122
|
-
</span>
|
|
123
|
-
<span dir="auto">{{ ex.text }}</span>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
</section>
|
|
127
|
-
|
|
128
|
-
<!-- No changes -->
|
|
129
|
-
<div v-if="diffResult && !diffResult.hasChanges" class="text-center py-4">
|
|
130
|
-
<p class="text-sm text-ink-400">Concepts are identical — no textual differences detected.</p>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
</Transition>
|
|
135
|
-
</div>
|
|
136
|
-
</template>
|
|
137
|
-
|
|
138
|
-
<script setup lang="ts">
|
|
139
|
-
import { ref, computed } from 'vue';
|
|
140
|
-
import { diffConcepts } from 'glossarist/diff';
|
|
141
|
-
import type { ConceptDiff } from 'glossarist/diff';
|
|
142
|
-
|
|
143
|
-
interface SupersessionTarget {
|
|
144
|
-
conceptId: string;
|
|
145
|
-
registerId: string;
|
|
146
|
-
term?: string;
|
|
147
|
-
conceptData?: any;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const props = defineProps<{
|
|
151
|
-
concept: any;
|
|
152
|
-
registerId: string;
|
|
153
|
-
supersedes?: SupersessionTarget | null;
|
|
154
|
-
supersededBy?: SupersessionTarget | null;
|
|
155
|
-
}>();
|
|
156
|
-
|
|
157
|
-
const activeComparison = ref<SupersessionTarget | null>(null);
|
|
158
|
-
const diffResult = ref<ConceptDiff | null>(null);
|
|
159
|
-
|
|
160
|
-
async function compareWith(target: SupersessionTarget) {
|
|
161
|
-
if (!target.conceptData) return;
|
|
162
|
-
activeComparison.value = target;
|
|
163
|
-
diffResult.value = null;
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const oldConcept = target === props.supersedes ? target.conceptData : props.concept;
|
|
167
|
-
const newConcept = target === props.supersedes ? props.concept : target.conceptData;
|
|
168
|
-
diffResult.value = diffConcepts(
|
|
169
|
-
toConceptLike(oldConcept),
|
|
170
|
-
toConceptLike(newConcept),
|
|
171
|
-
);
|
|
172
|
-
} catch (e) {
|
|
173
|
-
console.error('Supersession diff failed:', e);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function toConceptLike(data: any) {
|
|
178
|
-
const langs = data.languages ?? (data.localizations ? Object.keys(data.localizations) : []);
|
|
179
|
-
return {
|
|
180
|
-
id: data.conceptId ?? data.id,
|
|
181
|
-
termid: String(data.conceptId ?? data.id),
|
|
182
|
-
status: data.status,
|
|
183
|
-
uri: data.uri,
|
|
184
|
-
languages: langs,
|
|
185
|
-
localization(lang: string) {
|
|
186
|
-
const loc = (data.localizations ?? {})[lang];
|
|
187
|
-
if (!loc) return null;
|
|
188
|
-
return {
|
|
189
|
-
languageCode: loc.languageCode ?? lang,
|
|
190
|
-
terms: loc.terms ?? [],
|
|
191
|
-
definitions: loc.definitions ?? [],
|
|
192
|
-
notes: loc.notes ?? [],
|
|
193
|
-
examples: loc.examples ?? [],
|
|
194
|
-
};
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const diffSections = computed(() => {
|
|
200
|
-
if (!diffResult.value) return {};
|
|
201
|
-
const diff = diffResult.value as any;
|
|
202
|
-
const loc = diff.localization?.('eng') ?? diff.localizations?.eng;
|
|
203
|
-
if (!loc) return {};
|
|
204
|
-
const out: any = {};
|
|
205
|
-
|
|
206
|
-
const defChanged = loc.definitions?.changed?.[0];
|
|
207
|
-
if (defChanged?.textDiff?.hunks?.length) {
|
|
208
|
-
out.definition = { hunks: defChanged.textDiff.hunks };
|
|
209
|
-
} else if (loc.definitions?.added?.length) {
|
|
210
|
-
out.definition = { type: 'added', value: loc.definitions.added[0].value?.content ?? loc.definitions.added[0].value };
|
|
211
|
-
} else if (loc.definitions?.removed?.length) {
|
|
212
|
-
out.definition = { type: 'removed', value: loc.definitions.removed[0].value?.content ?? loc.definitions.removed[0].value };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (loc.designations?.added?.length || loc.designations?.removed?.length || loc.designations?.changed?.length) {
|
|
216
|
-
const items: Array<{ type?: string; text: string }> = [];
|
|
217
|
-
for (const d of loc.designations.added ?? []) {
|
|
218
|
-
items.push({ type: 'added', text: d.value?.designation ?? d.value?.text ?? String(d.value ?? '') });
|
|
219
|
-
}
|
|
220
|
-
for (const d of loc.designations.removed ?? []) {
|
|
221
|
-
items.push({ type: 'removed', text: d.value?.designation ?? d.value?.text ?? String(d.value ?? '') });
|
|
222
|
-
}
|
|
223
|
-
for (const d of loc.designations.changed ?? []) {
|
|
224
|
-
items.push({ type: 'removed', text: d.oldValue?.designation ?? d.oldValue?.text ?? String(d.oldValue ?? '') });
|
|
225
|
-
items.push({ type: 'added', text: d.newValue?.designation ?? d.newValue?.text ?? String(d.newValue ?? '') });
|
|
226
|
-
}
|
|
227
|
-
out.designations = { items };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (loc.notes?.added?.length || loc.notes?.removed?.length) {
|
|
231
|
-
out.notes = [
|
|
232
|
-
...(loc.notes.added ?? []).map((n: any) => ({ type: 'added', text: n.value?.content ?? n.value?.text ?? String(n.value ?? '') })),
|
|
233
|
-
...(loc.notes.removed ?? []).map((n: any) => ({ type: 'removed', text: n.value?.content ?? n.value?.text ?? String(n.value ?? '') })),
|
|
234
|
-
];
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (loc.examples?.added?.length || loc.examples?.removed?.length) {
|
|
238
|
-
out.examples = [
|
|
239
|
-
...(loc.examples.added ?? []).map((e: any) => ({ type: 'added', text: e.value?.content ?? e.value?.text ?? String(e.value ?? '') })),
|
|
240
|
-
...(loc.examples.removed ?? []).map((e: any) => ({ type: 'removed', text: e.value?.content ?? e.value?.text ?? String(e.value ?? '') })),
|
|
241
|
-
];
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return out;
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
function similarityColor(score: number): string {
|
|
248
|
-
if (score >= 0.8) return 'bg-emerald-500';
|
|
249
|
-
if (score >= 0.5) return 'bg-amber-500';
|
|
250
|
-
return 'bg-red-500';
|
|
251
|
-
}
|
|
252
|
-
function similarityTextColor(score: number): string {
|
|
253
|
-
if (score >= 0.8) return 'text-emerald-600 dark:text-emerald-400';
|
|
254
|
-
if (score >= 0.5) return 'text-amber-600 dark:text-amber-400';
|
|
255
|
-
return 'text-red-600 dark:text-red-400';
|
|
256
|
-
}
|
|
257
|
-
</script>
|
|
258
|
-
|
|
259
|
-
<style scoped>
|
|
260
|
-
.slide-down-enter-active, .slide-down-leave-active {
|
|
261
|
-
transition: all 0.3s ease;
|
|
262
|
-
}
|
|
263
|
-
.slide-down-enter-from, .slide-down-leave-to {
|
|
264
|
-
opacity: 0;
|
|
265
|
-
transform: translateY(-10px);
|
|
266
|
-
}
|
|
267
|
-
</style>
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hyperedge styling — colors for partitive decompositions.
|
|
3
|
-
*
|
|
4
|
-
* Independent of relationship-categories.ts (which handles binary edges).
|
|
5
|
-
* Hyperedges have their own visual language: teal base color, enumeration
|
|
6
|
-
* affects opacity, markers add accent badges.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const BASE_LIGHT = '#0d9488';
|
|
10
|
-
const BASE_DARK = '#2dd4bf';
|
|
11
|
-
const ACCENT_DOUBLE_LIGHT = '#3b82f6';
|
|
12
|
-
const ACCENT_DOUBLE_DARK = '#60a5fa';
|
|
13
|
-
const ACCENT_DASHED_LIGHT = '#f59e0b';
|
|
14
|
-
const ACCENT_DASHED_DARK = '#fbbf24';
|
|
15
|
-
|
|
16
|
-
export interface HyperedgeStyle {
|
|
17
|
-
color: string;
|
|
18
|
-
badgeClass: string;
|
|
19
|
-
opacity: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function hyperedgeStyle(
|
|
23
|
-
enumeration: 'closed' | 'open',
|
|
24
|
-
markers: string[],
|
|
25
|
-
isDark: boolean,
|
|
26
|
-
): HyperedgeStyle {
|
|
27
|
-
const base = isDark ? BASE_DARK : BASE_LIGHT;
|
|
28
|
-
const opacity = enumeration === 'open' ? 0.6 : 1.0;
|
|
29
|
-
|
|
30
|
-
let badgeClass = 'badge-teal';
|
|
31
|
-
if (markers.includes('double')) {
|
|
32
|
-
badgeClass = isDark ? 'badge-blue' : 'badge-blue';
|
|
33
|
-
} else if (markers.includes('dashed')) {
|
|
34
|
-
badgeClass = 'badge-yellow';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return { color: base, badgeClass, opacity };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function markerColor(marker: string, isDark: boolean): string {
|
|
41
|
-
if (marker === 'double') return isDark ? ACCENT_DOUBLE_DARK : ACCENT_DOUBLE_LIGHT;
|
|
42
|
-
if (marker === 'dashed') return isDark ? ACCENT_DASHED_DARK : ACCENT_DASHED_LIGHT;
|
|
43
|
-
return isDark ? BASE_DARK : BASE_LIGHT;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function enumerationLabel(enumeration: 'closed' | 'open'): string {
|
|
47
|
-
return enumeration === 'open' ? 'Open (partial)' : 'Closed (complete)';
|
|
48
|
-
}
|