@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.
Files changed (46) hide show
  1. package/README.md +32 -0
  2. package/cli/index.mjs +21 -0
  3. package/package.json +1 -1
  4. package/scripts/generate-data.mjs +43 -1
  5. package/scripts/generate-ontology-schema.mjs +312 -10
  6. package/src/App.vue +3 -0
  7. package/src/__tests__/concept-card.test.ts +16 -2
  8. package/src/__tests__/markdown-lite.test.ts +26 -0
  9. package/src/adapters/factory.ts +3 -2
  10. package/src/adapters/model-bridge.ts +2 -0
  11. package/src/adapters/ontology-schema.ts +89 -4
  12. package/src/adapters/types.ts +1 -0
  13. package/src/components/AppFooter.vue +3 -1
  14. package/src/components/AppHeader.vue +46 -4
  15. package/src/components/AppSidebar.vue +286 -46
  16. package/src/components/ConceptCard.vue +16 -4
  17. package/src/components/ConceptDetail.vue +42 -35
  18. package/src/components/ConceptRdfView.vue +3 -3
  19. package/src/components/ConceptTimeline.vue +2 -14
  20. package/src/components/GraphPanel.vue +19 -0
  21. package/src/components/LanguageDetail.vue +11 -8
  22. package/src/composables/use-ontology-nav.ts +183 -13
  23. package/src/composables/use-render-options.ts +2 -2
  24. package/src/config/types.ts +1 -0
  25. package/src/config/use-site-config.ts +3 -2
  26. package/src/data/ontology-schema.json +1721 -153
  27. package/src/i18n/index.ts +49 -0
  28. package/src/i18n/locales/eng.yml +66 -0
  29. package/src/i18n/locales/fra.yml +66 -0
  30. package/src/router/index.ts +10 -0
  31. package/src/shims/glossarist-tags.ts +10 -0
  32. package/src/stores/vocabulary.ts +1 -1
  33. package/src/style.css +12 -0
  34. package/src/utils/lang.ts +13 -0
  35. package/src/utils/markdown-lite.ts +15 -0
  36. package/src/views/AboutView.vue +1 -1
  37. package/src/views/ContributorsView.vue +1 -1
  38. package/src/views/DatasetView.vue +77 -6
  39. package/src/views/HomeView.vue +21 -17
  40. package/src/views/NewsView.vue +2 -2
  41. package/src/views/OntologySchemaView.vue +331 -14
  42. package/src/views/PageView.vue +27 -11
  43. package/src/views/ResolveView.vue +1 -1
  44. package/src/views/SearchView.vue +4 -2
  45. package/src/views/StatsView.vue +1 -1
  46. 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
- stats: { classCount: number; objectPropertyCount: number; datatypePropertyCount: number };
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
- /** Get the full class hierarchy tree starting from roots. */
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
  }
@@ -82,6 +82,7 @@ export interface ConceptEntry {
82
82
  id: string;
83
83
  designations: Record<string, string>;
84
84
  groups: string[];
85
+ tags: string[];
85
86
  status: string;
86
87
  }
87
88
 
@@ -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 || 'Built with the Glossarist Concept Browser', url: pb?.url || 'https://github.com/glossarist/concept-browser' };
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 { ref } from 'vue';
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="Search concepts"
68
- placeholder="Search..."
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 -->