@glossarist/concept-browser 0.5.0 → 0.6.0
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/README.md +32 -0
- package/cli/index.mjs +21 -0
- package/package.json +1 -1
- package/scripts/generate-data.mjs +43 -1
- package/scripts/generate-ontology-schema.mjs +312 -10
- package/src/App.vue +3 -0
- package/src/__tests__/concept-card.test.ts +16 -2
- package/src/__tests__/markdown-lite.test.ts +26 -0
- package/src/adapters/factory.ts +3 -2
- package/src/adapters/model-bridge.ts +2 -0
- package/src/adapters/ontology-schema.ts +89 -4
- package/src/adapters/types.ts +1 -0
- package/src/components/AppFooter.vue +3 -1
- package/src/components/AppHeader.vue +46 -4
- package/src/components/AppSidebar.vue +286 -46
- package/src/components/ConceptCard.vue +16 -4
- package/src/components/ConceptDetail.vue +42 -35
- package/src/components/ConceptRdfView.vue +3 -3
- package/src/components/ConceptTimeline.vue +2 -14
- package/src/components/GraphPanel.vue +19 -0
- package/src/components/LanguageDetail.vue +11 -8
- package/src/composables/use-ontology-nav.ts +183 -13
- package/src/composables/use-render-options.ts +2 -2
- package/src/config/types.ts +1 -0
- package/src/config/use-site-config.ts +3 -2
- package/src/data/ontology-schema.json +1721 -153
- package/src/i18n/index.ts +49 -0
- package/src/i18n/locales/eng.yml +66 -0
- package/src/i18n/locales/fra.yml +66 -0
- package/src/router/index.ts +10 -0
- package/src/shims/glossarist-tags.ts +10 -0
- package/src/stores/vocabulary.ts +1 -1
- package/src/style.css +12 -0
- package/src/utils/lang.ts +13 -0
- package/src/utils/markdown-lite.ts +15 -0
- package/src/views/AboutView.vue +1 -1
- package/src/views/ContributorsView.vue +1 -1
- package/src/views/DatasetView.vue +77 -6
- package/src/views/HomeView.vue +21 -17
- package/src/views/NewsView.vue +2 -2
- package/src/views/OntologySchemaView.vue +331 -14
- package/src/views/PageView.vue +27 -11
- package/src/views/ResolveView.vue +1 -1
- package/src/views/SearchView.vue +4 -2
- package/src/views/StatsView.vue +1 -1
- package/vite.config.ts +34 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ontology schema loader — provides class/property definitions parsed from
|
|
2
|
+
* Ontology schema loader — provides class/property/shape definitions parsed from
|
|
3
3
|
* the Glossarist OWL ontology for the Ontospy-style concept view.
|
|
4
4
|
*/
|
|
5
5
|
import schemaData from '../data/ontology-schema.json';
|
|
@@ -28,14 +28,72 @@ export interface OwlProperty {
|
|
|
28
28
|
inverseOf: string | null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export interface ShaclConstraint {
|
|
32
|
+
path: string | null;
|
|
33
|
+
datatype: string | null;
|
|
34
|
+
class: string | null;
|
|
35
|
+
valuesFrom: string | null;
|
|
36
|
+
nodeKind: string | null;
|
|
37
|
+
minCount: number | null;
|
|
38
|
+
maxCount: number | null;
|
|
39
|
+
in: string[] | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OwlShape {
|
|
43
|
+
iri: string;
|
|
44
|
+
compact: string;
|
|
45
|
+
label: string;
|
|
46
|
+
comment: string | null;
|
|
47
|
+
targetClass: string | null;
|
|
48
|
+
shapeClass: string | null;
|
|
49
|
+
constraints: ShaclConstraint[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface OwlOntology {
|
|
53
|
+
iri: string;
|
|
54
|
+
label: string;
|
|
55
|
+
comment: string | null;
|
|
56
|
+
prefix: string | null;
|
|
57
|
+
namespaceUri: string | null;
|
|
58
|
+
imports: { iri: string; label: string }[];
|
|
59
|
+
license: string | null;
|
|
60
|
+
created: string | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AnnotationProperty {
|
|
64
|
+
iri: string;
|
|
65
|
+
compact: string;
|
|
66
|
+
label: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type EntityType = 'class' | 'objectProperty' | 'datatypeProperty' | 'shape' | 'annotationProperty';
|
|
70
|
+
|
|
71
|
+
export const ENTITY_TYPE_META: Record<EntityType, { label: string; color: string }> = {
|
|
72
|
+
class: { label: 'Classes', color: 'blue' },
|
|
73
|
+
objectProperty: { label: 'Object Properties', color: 'emerald' },
|
|
74
|
+
datatypeProperty: { label: 'Datatype Properties', color: 'amber' },
|
|
75
|
+
shape: { label: 'SHACL Shapes', color: 'purple' },
|
|
76
|
+
annotationProperty: { label: 'Annotation Properties', color: 'pink' },
|
|
77
|
+
};
|
|
78
|
+
|
|
31
79
|
interface OntologySchema {
|
|
80
|
+
ontology: OwlOntology | null;
|
|
32
81
|
ontologyIri: string;
|
|
33
82
|
ontologyLabel: string;
|
|
34
83
|
classes: Record<string, OwlClass>;
|
|
35
84
|
classHierarchyRoots: string[];
|
|
36
85
|
properties: Record<string, OwlProperty>;
|
|
37
86
|
propertiesByDomain: Record<string, { object: string[]; datatype: string[] }>;
|
|
38
|
-
|
|
87
|
+
shapes: Record<string, OwlShape>;
|
|
88
|
+
shapesByTargetClass: Record<string, string[]>;
|
|
89
|
+
annotationProperties: AnnotationProperty[];
|
|
90
|
+
stats: {
|
|
91
|
+
classCount: number;
|
|
92
|
+
objectPropertyCount: number;
|
|
93
|
+
datatypePropertyCount: number;
|
|
94
|
+
shapeCount: number;
|
|
95
|
+
annotationPropertyCount: number;
|
|
96
|
+
};
|
|
39
97
|
}
|
|
40
98
|
|
|
41
99
|
const data = schemaData as unknown as OntologySchema;
|
|
@@ -48,6 +106,10 @@ export function getProperty(id: string): OwlProperty | null {
|
|
|
48
106
|
return data.properties[id] ?? null;
|
|
49
107
|
}
|
|
50
108
|
|
|
109
|
+
export function getShape(id: string): OwlShape | null {
|
|
110
|
+
return data.shapes[id] ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
51
113
|
export function getPropertiesForDomain(domain: string): { object: OwlProperty[]; datatype: OwlProperty[] } {
|
|
52
114
|
const group = data.propertiesByDomain[domain];
|
|
53
115
|
if (!group) return { object: [], datatype: [] };
|
|
@@ -57,7 +119,6 @@ export function getPropertiesForDomain(domain: string): { object: OwlProperty[];
|
|
|
57
119
|
};
|
|
58
120
|
}
|
|
59
121
|
|
|
60
|
-
/** Get all properties applicable to a class, including inherited ones. */
|
|
61
122
|
export function getAllPropertiesForClass(classId: string): { object: OwlProperty[]; datatype: OwlProperty[] } {
|
|
62
123
|
const cls = data.classes[classId];
|
|
63
124
|
if (!cls) return { object: [], datatype: [] };
|
|
@@ -80,7 +141,11 @@ export function getAllPropertiesForClass(classId: string): { object: OwlProperty
|
|
|
80
141
|
return { object: objectProps, datatype: datatypeProps };
|
|
81
142
|
}
|
|
82
143
|
|
|
83
|
-
|
|
144
|
+
export function getShapesForClass(classId: string): OwlShape[] {
|
|
145
|
+
const shapeIds = data.shapesByTargetClass[classId] ?? [];
|
|
146
|
+
return shapeIds.map(id => data.shapes[id]).filter(Boolean);
|
|
147
|
+
}
|
|
148
|
+
|
|
84
149
|
export function getClassTree(): OwlClass[] {
|
|
85
150
|
return data.classHierarchyRoots
|
|
86
151
|
.map(id => data.classes[id])
|
|
@@ -95,6 +160,26 @@ export function getAllProperties(): OwlProperty[] {
|
|
|
95
160
|
return Object.values(data.properties);
|
|
96
161
|
}
|
|
97
162
|
|
|
163
|
+
export function getObjectProperties(): OwlProperty[] {
|
|
164
|
+
return Object.values(data.properties).filter(p => p.type === 'object');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function getDatatypeProperties(): OwlProperty[] {
|
|
168
|
+
return Object.values(data.properties).filter(p => p.type === 'datatype');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getAllShapes(): OwlShape[] {
|
|
172
|
+
return Object.values(data.shapes);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function getAnnotationProperties(): AnnotationProperty[] {
|
|
176
|
+
return data.annotationProperties;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getOntology(): OwlOntology | null {
|
|
180
|
+
return data.ontology;
|
|
181
|
+
}
|
|
182
|
+
|
|
98
183
|
export function getStats() {
|
|
99
184
|
return data.stats;
|
|
100
185
|
}
|
package/src/adapters/types.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue';
|
|
3
3
|
import { useSiteConfig } from '../config/use-site-config';
|
|
4
|
+
import { useI18n } from '../i18n';
|
|
4
5
|
|
|
5
6
|
const { config } = useSiteConfig();
|
|
7
|
+
const { t } = useI18n();
|
|
6
8
|
|
|
7
9
|
const poweredBy = computed(() => {
|
|
8
10
|
const pb = config.value?.features?.poweredBy as { message?: string; url?: string } | undefined;
|
|
9
|
-
return { message: pb?.message || '
|
|
11
|
+
return { message: pb?.message || t('footer.builtWith'), url: pb?.url || 'https://github.com/glossarist/concept-browser' };
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
const socialLinks = computed(() => {
|
|
@@ -3,13 +3,23 @@ import { useRouter } from 'vue-router';
|
|
|
3
3
|
import { useUiStore } from '../stores/ui';
|
|
4
4
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
5
5
|
import { useSiteConfig } from '../config/use-site-config';
|
|
6
|
-
import {
|
|
6
|
+
import { useI18n } from '../i18n';
|
|
7
|
+
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
|
7
8
|
|
|
8
9
|
const router = useRouter();
|
|
9
10
|
const ui = useUiStore();
|
|
10
11
|
const store = useVocabularyStore();
|
|
11
12
|
const { config: siteConfig } = useSiteConfig();
|
|
13
|
+
const { locale, t, setLocale } = useI18n();
|
|
12
14
|
const searchInput = ref('');
|
|
15
|
+
const langOpen = ref(false);
|
|
16
|
+
|
|
17
|
+
const uiLanguages = computed(() => siteConfig.value?.uiLanguages || []);
|
|
18
|
+
const showLangSelector = computed(() => uiLanguages.value.length > 1);
|
|
19
|
+
const currentLangLabel = computed(() => {
|
|
20
|
+
const cur = uiLanguages.value.find(l => l.code === locale.value);
|
|
21
|
+
return cur?.label || locale.value.toUpperCase();
|
|
22
|
+
});
|
|
13
23
|
|
|
14
24
|
function doSearch() {
|
|
15
25
|
const q = searchInput.value.trim();
|
|
@@ -22,6 +32,18 @@ function doSearch() {
|
|
|
22
32
|
function goHome() {
|
|
23
33
|
router.push({ name: 'home' });
|
|
24
34
|
}
|
|
35
|
+
|
|
36
|
+
function selectLang(code: string) {
|
|
37
|
+
setLocale(code);
|
|
38
|
+
langOpen.value = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function closeLangOnOutside(e: MouseEvent) {
|
|
42
|
+
if (langOpen.value) langOpen.value = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onMounted(() => document.addEventListener('click', closeLangOnOutside));
|
|
46
|
+
onBeforeUnmount(() => document.removeEventListener('click', closeLangOnOutside));
|
|
25
47
|
</script>
|
|
26
48
|
|
|
27
49
|
<template>
|
|
@@ -64,8 +86,8 @@ function goHome() {
|
|
|
64
86
|
<input
|
|
65
87
|
v-model="searchInput"
|
|
66
88
|
type="text"
|
|
67
|
-
aria-label="
|
|
68
|
-
placeholder="
|
|
89
|
+
:aria-label="t('search.conceptSearch')"
|
|
90
|
+
:placeholder="t('search.placeholder')"
|
|
69
91
|
class="w-full pl-9 pr-3 py-2 text-sm bg-surface border border-ink-100 rounded-lg focus:ring-2 focus:ring-ink-200 focus:border-ink-400 outline-none placeholder:text-ink-300 transition-all"
|
|
70
92
|
/>
|
|
71
93
|
<svg class="absolute left-3 top-2.5 w-4 h-4 text-ink-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -76,7 +98,27 @@ function goHome() {
|
|
|
76
98
|
|
|
77
99
|
<!-- Stats -->
|
|
78
100
|
<div class="text-xs text-ink-400 flex-shrink-0 hidden md:block">
|
|
79
|
-
{{ store.datasetList.length }} datasets
|
|
101
|
+
{{ store.datasetList.length }} {{ t('header.datasets') }}
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Language selector -->
|
|
105
|
+
<div v-if="showLangSelector" class="relative flex-shrink-0" @click.stop>
|
|
106
|
+
<button
|
|
107
|
+
@click="langOpen = !langOpen"
|
|
108
|
+
class="px-2.5 py-1 rounded-lg text-xs font-semibold text-ink-500 hover:text-ink-700 hover:bg-ink-50 transition-colors border border-ink-100 flex items-center gap-1"
|
|
109
|
+
>
|
|
110
|
+
{{ currentLangLabel }}
|
|
111
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
|
112
|
+
</button>
|
|
113
|
+
<div v-if="langOpen" class="absolute right-0 top-full mt-1 bg-surface-raised border border-ink-100 rounded-lg shadow-lg py-1 min-w-[120px] z-50">
|
|
114
|
+
<button
|
|
115
|
+
v-for="lang in uiLanguages"
|
|
116
|
+
:key="lang.code"
|
|
117
|
+
@click="selectLang(lang.code)"
|
|
118
|
+
class="w-full text-left px-3 py-1.5 text-sm transition-colors"
|
|
119
|
+
:class="locale === lang.code ? 'bg-ink-50 text-ink-800 font-medium' : 'text-ink-600 hover:bg-ink-50'"
|
|
120
|
+
>{{ lang.label }}</button>
|
|
121
|
+
</div>
|
|
80
122
|
</div>
|
|
81
123
|
|
|
82
124
|
<!-- Theme toggle -->
|