@glossarist/concept-browser 0.7.69 → 0.7.71
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/cli/index.mjs +28 -9
- package/package.json +14 -8
- package/public/favicon.svg +1 -4
- package/scripts/bridge-to-astro.mjs +155 -0
- package/scripts/generate-data.mjs +12 -0
- package/src/adapters/model-bridge.ts +50 -12
- package/src/adapters/non-verbal/glossarist-augment.d.ts +5 -1
- package/src/assets/glossarist-logo-full.svg +1 -0
- package/src/astro-islands/app.ts +3 -0
- package/src/components/AppFooter.vue +3 -2
- package/src/components/AppHeader.vue +4 -7
- package/src/components/AppSidebar.vue +8 -5
- package/src/components/ConceptDetail.vue +12 -0
- package/src/components/DesignationList.vue +15 -4
- package/src/components/LanguageDetail.vue +6 -0
- package/src/components/RelationSphere.vue +36 -32
- package/src/components/site/SeoMeta.astro +23 -0
- package/src/components/site/SiteFooter.astro +14 -0
- package/src/components/site/SiteHeader.astro +25 -0
- package/src/components/site/SiteSidebar.astro +56 -0
- package/src/content.config.ts +85 -0
- package/src/i18n/locales/eng.yml +1 -0
- package/src/i18n/locales/fra.yml +1 -0
- package/src/islands/AppIsland.vue +7 -0
- package/src/islands/BibliographyIsland.vue +26 -0
- package/src/islands/ConceptDetailIsland.vue +27 -0
- package/src/islands/ConceptDiffIsland.vue +214 -0
- package/src/islands/ConceptIsland.vue +102 -0
- package/src/islands/GraphIsland.vue +15 -0
- package/src/islands/GroupRendererIsland.vue +15 -0
- package/src/islands/HeaderIsland.vue +10 -0
- package/src/islands/NonVerbalIsland.vue +75 -0
- package/src/islands/RelationSphereIsland.vue +20 -0
- package/src/islands/SearchIsland.vue +23 -0
- package/src/islands/SeriesCardIsland.vue +12 -0
- package/src/islands/ShortcutsIsland.vue +13 -0
- package/src/islands/SidebarIsland.vue +15 -0
- package/src/islands/SupersessionDiffIsland.vue +267 -0
- package/src/islands/app-entry.ts +8 -0
- package/src/islands/app.ts +4 -0
- package/src/layouts/Default.astro +70 -0
- package/src/lib/site-config.ts +26 -0
- package/src/pages/404.astro +9 -0
- package/src/pages/[...path].astro +29 -0
- package/src/pages/dataset/[id]/concept/[cid].astro +23 -0
- package/src/pages/dataset/[id]/index.astro +16 -0
- package/src/pages/graph.astro +5 -0
- package/src/pages/group/[id]/index.astro +16 -0
- package/src/pages/index.astro +10 -0
- package/src/pages/search.astro +5 -0
- package/src/styles/tailwind.css +503 -0
- package/src/views/ConceptView.vue +18 -23
- package/src/views/StatsView.vue +1 -1
- package/tsconfig.json +2 -1
- package/vite.config.ts +3 -1
- package/postcss.config.js +0 -13
|
@@ -0,0 +1,267 @@
|
|
|
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>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
import '@/styles/tailwind.css';
|
|
3
|
+
import AppIsland from '../islands/AppIsland.vue';
|
|
4
|
+
import { getSiteConfig } from '../lib/site-config';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
conceptTerm?: string;
|
|
10
|
+
conceptDefinition?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { title, description = '', conceptTerm, conceptDefinition } = Astro.props;
|
|
14
|
+
const siteConfig = await getSiteConfig();
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<!DOCTYPE html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head>
|
|
20
|
+
<meta charset="utf-8" />
|
|
21
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
22
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
23
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
|
24
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
|
25
|
+
<link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png" />
|
|
26
|
+
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png" />
|
|
27
|
+
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png" />
|
|
28
|
+
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png" />
|
|
29
|
+
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png" />
|
|
30
|
+
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png" />
|
|
31
|
+
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png" />
|
|
32
|
+
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png" />
|
|
33
|
+
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png" />
|
|
34
|
+
<link rel="apple-touch-icon" sizes="167x167" href="/apple-touch-icon-167x167.png" />
|
|
35
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png" />
|
|
36
|
+
<link rel="apple-touch-icon" sizes="1024x1024" href="/apple-touch-icon-1024x1024.png" />
|
|
37
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
38
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
39
|
+
<meta name="apple-mobile-web-app-title" content={siteConfig.title} />
|
|
40
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
41
|
+
<meta name="description" content={description} />
|
|
42
|
+
<title>{title}</title>
|
|
43
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
44
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
45
|
+
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=DM+Serif+Display:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" onload="this.rel='stylesheet'">
|
|
46
|
+
<noscript><link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=DM+Serif+Display:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"></noscript>
|
|
47
|
+
<script is:inline>
|
|
48
|
+
(function() {
|
|
49
|
+
const c = document.cookie.split(';').map(s => s.trim()).find(s => s.startsWith('theme='));
|
|
50
|
+
const t = c ? c.split('=')[1] : null;
|
|
51
|
+
if (t === 'dark' || (!t && matchMedia('(prefers-color-scheme: dark)').matches))
|
|
52
|
+
document.documentElement.classList.add('dark');
|
|
53
|
+
})();
|
|
54
|
+
</script>
|
|
55
|
+
</head>
|
|
56
|
+
<body class="bg-surface dark:bg-ink-900">
|
|
57
|
+
<div id="app">
|
|
58
|
+
<AppIsland client:only="vue" />
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{conceptTerm && (
|
|
62
|
+
<noscript>
|
|
63
|
+
<div style="padding: 2rem; max-width: 800px; margin: 0 auto;">
|
|
64
|
+
<h1>{conceptTerm}</h1>
|
|
65
|
+
{conceptDefinition && <p>{conceptDefinition}</p>}
|
|
66
|
+
</div>
|
|
67
|
+
</noscript>
|
|
68
|
+
)}
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Minimal site config loader for Astro SSG.
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import yaml from 'js-yaml';
|
|
5
|
+
|
|
6
|
+
let cached: any = null;
|
|
7
|
+
|
|
8
|
+
export async function getSiteConfig() {
|
|
9
|
+
if (cached) return cached;
|
|
10
|
+
const path = resolve(process.cwd(), 'site-config.yml');
|
|
11
|
+
if (existsSync(path)) {
|
|
12
|
+
try {
|
|
13
|
+
cached = yaml.load(readFileSync(path, 'utf-8'));
|
|
14
|
+
} catch {
|
|
15
|
+
cached = { title: 'Glossarist', description: '' };
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
const jsonPath = resolve(process.cwd(), 'public', 'site-config.json');
|
|
19
|
+
if (existsSync(jsonPath)) {
|
|
20
|
+
cached = JSON.parse(readFileSync(jsonPath, 'utf-8'));
|
|
21
|
+
} else {
|
|
22
|
+
cached = { title: 'Glossarist', description: '' };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AppLayout from '../layouts/Default.astro';
|
|
3
|
+
import { getCollection } from 'astro:content';
|
|
4
|
+
|
|
5
|
+
const datasets = await getCollection('datasets');
|
|
6
|
+
const title = datasets.length > 0 ? datasets[0].data.title : 'Concept Browser';
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<AppLayout title={title} />
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AppLayout from '../layouts/Default.astro';
|
|
3
|
+
import { getCollection } from 'astro:content';
|
|
4
|
+
|
|
5
|
+
export const prerender = true;
|
|
6
|
+
|
|
7
|
+
export async function getStaticPaths() {
|
|
8
|
+
const datasets = await getCollection('datasets');
|
|
9
|
+
const paths = [
|
|
10
|
+
{ params: { path: 'ontology' } },
|
|
11
|
+
{ params: { path: 'about' } },
|
|
12
|
+
{ params: { path: 'contributors' } },
|
|
13
|
+
{ params: { path: 'news' } },
|
|
14
|
+
];
|
|
15
|
+
for (const ds of datasets) {
|
|
16
|
+
paths.push({ params: { path: `dataset/${ds.data.id}/about` } });
|
|
17
|
+
paths.push({ params: { path: `dataset/${ds.data.id}/stats` } });
|
|
18
|
+
for (const g of await getCollection('groups')) {
|
|
19
|
+
paths.push({ params: { path: `group/${g.data.id}/about` } });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return paths;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const datasets = await getCollection('datasets');
|
|
26
|
+
const title = datasets.length > 0 ? datasets[0].data.title : 'Concept Browser';
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<AppLayout title={title} />
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AppLayout from '../../../../layouts/Default.astro';
|
|
3
|
+
import { getCollection } from 'astro:content';
|
|
4
|
+
|
|
5
|
+
export async function getStaticPaths() {
|
|
6
|
+
const concepts = await getCollection('concepts');
|
|
7
|
+
return concepts.map(c => ({
|
|
8
|
+
params: { id: c.data.registerId, cid: c.data.conceptId },
|
|
9
|
+
props: { conceptData: c.data },
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { conceptData } = Astro.props;
|
|
14
|
+
const term = conceptData?.localizations?.eng?.terms?.[0]?.designation ?? conceptData?.eng ?? conceptData?.conceptId;
|
|
15
|
+
const definition = conceptData?.localizations?.eng?.definitions?.[0]?.content ?? '';
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<AppLayout
|
|
19
|
+
title={`${term} — Concept`}
|
|
20
|
+
description={definition}
|
|
21
|
+
conceptTerm={term}
|
|
22
|
+
conceptDefinition={definition}
|
|
23
|
+
/>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AppLayout from '../../../layouts/Default.astro';
|
|
3
|
+
import { getCollection } from 'astro:content';
|
|
4
|
+
|
|
5
|
+
export async function getStaticPaths() {
|
|
6
|
+
const datasets = await getCollection('datasets');
|
|
7
|
+
return datasets.map(d => ({
|
|
8
|
+
params: { id: d.data.id },
|
|
9
|
+
props: { dataset: d.data },
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { dataset } = Astro.props;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<AppLayout title={`${dataset.title}`} description={dataset.description} />
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AppLayout from '../../../layouts/Default.astro';
|
|
3
|
+
import { getCollection } from 'astro:content';
|
|
4
|
+
|
|
5
|
+
export async function getStaticPaths() {
|
|
6
|
+
const groups = await getCollection('groups');
|
|
7
|
+
return groups.map(g => ({
|
|
8
|
+
params: { id: g.data.id },
|
|
9
|
+
props: { group: g.data },
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { group } = Astro.props;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<AppLayout title={group.label} description={group.description} />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AppLayout from '../layouts/Default.astro';
|
|
3
|
+
import { getCollection } from 'astro:content';
|
|
4
|
+
|
|
5
|
+
const datasets = await getCollection('datasets');
|
|
6
|
+
const totalConcepts = datasets.reduce((sum, d) => sum + (d.data.conceptCount || 0), 0);
|
|
7
|
+
const title = datasets.length > 0 ? datasets[0].data.title : 'Concept Browser';
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<AppLayout title={title} description={`${totalConcepts} concepts across ${datasets.length} datasets`} />
|