@glossarist/concept-browser 0.7.26 → 0.7.28
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 +1 -1
- package/scripts/generate-data.mjs +20 -1
- package/src/__tests__/annotations-bridge.test.ts +87 -0
- package/src/__tests__/dataset-adapter.test.ts +58 -0
- package/src/__tests__/designation-relationship.test.ts +211 -0
- package/src/__tests__/graph.test.ts +15 -0
- package/src/__tests__/relationship-comprehensive.test.ts +162 -0
- package/src/__tests__/taxonomy-colors.test.ts +71 -0
- package/src/adapters/model-bridge.ts +251 -27
- package/src/adapters/ontology-registry.ts +30 -2
- package/src/components/AppSidebar.vue +1 -1
- package/src/components/ConceptDetail.vue +32 -64
- package/src/components/DesignationList.vue +78 -0
- package/src/data/ontology-schema.json +66 -8
- package/src/data/taxonomies.json +261 -89
- package/src/i18n/locales/eng.yml +2 -0
- package/src/i18n/locales/fra.yml +2 -0
- package/src/stores/vocabulary.ts +3 -5
- package/src/style.css +8 -0
- package/src/utils/concept-helpers.ts +2 -20
- package/src/utils/designation-registry.ts +3 -26
- package/src/utils/relationship-categories.ts +68 -122
package/src/style.css
CHANGED
|
@@ -434,6 +434,14 @@
|
|
|
434
434
|
.dark .bg-red-50 { background-color: rgba(239, 68, 68, 0.15) !important; }
|
|
435
435
|
.dark .text-red-600 { color: #fca5a5 !important; }
|
|
436
436
|
|
|
437
|
+
/* ── Sidebar group labels (brand colors are too dark in dark mode) ── */
|
|
438
|
+
.dark .sidebar-group-label {
|
|
439
|
+
filter: brightness(2);
|
|
440
|
+
}
|
|
441
|
+
.dark .sidebar-group-label:hover {
|
|
442
|
+
filter: brightness(1.8);
|
|
443
|
+
}
|
|
444
|
+
|
|
437
445
|
/* Scrollbar hide utility */
|
|
438
446
|
@layer utilities {
|
|
439
447
|
.scrollbar-none { -ms-overflow-style: none; scrollbar-width: none; }
|
|
@@ -2,30 +2,12 @@ import type { LocalizedConcept } from 'glossarist';
|
|
|
2
2
|
import { ontology } from '../adapters/ontology-registry';
|
|
3
3
|
|
|
4
4
|
export function entryStatusColor(status: string): string {
|
|
5
|
-
|
|
6
|
-
valid: 'badge-green',
|
|
7
|
-
not_valid: 'bg-red-50 text-red-700',
|
|
8
|
-
superseded: 'bg-red-50 text-red-700',
|
|
9
|
-
retired: 'badge-gray',
|
|
10
|
-
withdrawn: 'bg-red-100 text-red-800',
|
|
11
|
-
draft: 'badge-yellow',
|
|
12
|
-
Standard: 'badge-green',
|
|
13
|
-
};
|
|
14
|
-
return colors[status] ?? 'badge-gray';
|
|
5
|
+
return ontology.getColor('entryStatus', status) ?? 'badge-gray';
|
|
15
6
|
}
|
|
16
7
|
|
|
17
8
|
export function conceptStatusColor(status: string | null): string {
|
|
18
9
|
if (!status) return 'badge-gray';
|
|
19
|
-
|
|
20
|
-
draft: 'badge-yellow',
|
|
21
|
-
submitted: 'badge-blue',
|
|
22
|
-
valid: 'badge-green',
|
|
23
|
-
not_valid: 'bg-red-50 text-red-700',
|
|
24
|
-
invalid: 'bg-red-50 text-red-700',
|
|
25
|
-
superseded: 'bg-red-50 text-red-700',
|
|
26
|
-
retired: 'badge-gray',
|
|
27
|
-
};
|
|
28
|
-
return colors[status] ?? 'badge-gray';
|
|
10
|
+
return ontology.getColor('conceptStatus', status) ?? 'badge-gray';
|
|
29
11
|
}
|
|
30
12
|
|
|
31
13
|
export function conceptStatusLabel(status: string | null): string {
|
|
@@ -8,45 +8,28 @@ export interface DesignationTypeInfo {
|
|
|
8
8
|
definition?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const TYPE_COLORS: Record<string, string> = {
|
|
12
|
-
expression: 'bg-sky-50 text-sky-700',
|
|
13
|
-
abbreviation: 'bg-amber-50 text-amber-700',
|
|
14
|
-
symbol: 'bg-violet-50 text-violet-700',
|
|
15
|
-
letter_symbol: 'bg-violet-50 text-violet-700',
|
|
16
|
-
graphical_symbol: 'bg-violet-50 text-violet-700',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
11
|
export function designationTypeInfo(designation: Designation): DesignationTypeInfo {
|
|
20
12
|
const type = designation.type;
|
|
21
13
|
const concept = ontology.getConcept('designationType', type);
|
|
22
14
|
return {
|
|
23
15
|
label: concept?.prefLabel ?? type,
|
|
24
|
-
color:
|
|
16
|
+
color: ontology.getColor('designationType', type) ?? 'bg-gray-50 text-gray-700',
|
|
25
17
|
definition: concept?.definition ?? undefined,
|
|
26
18
|
};
|
|
27
19
|
}
|
|
28
20
|
|
|
29
21
|
export function normativeStatusInfo(status: string | null): { label: string; color: string; definition?: string } {
|
|
30
22
|
if (!status) return { label: '', color: 'bg-gray-50 text-gray-700' };
|
|
31
|
-
|
|
32
|
-
const colors: Record<string, string> = {
|
|
33
|
-
preferred: 'bg-emerald-50 text-emerald-700',
|
|
34
|
-
admitted: 'bg-amber-50 text-amber-700',
|
|
35
|
-
deprecated: 'bg-red-50 text-red-700',
|
|
36
|
-
superseded: 'bg-red-50 text-red-700',
|
|
37
|
-
};
|
|
38
|
-
|
|
39
23
|
const concept = ontology.getConcept('normativeStatus', status);
|
|
40
24
|
return {
|
|
41
25
|
label: concept?.prefLabel ?? status,
|
|
42
|
-
color:
|
|
26
|
+
color: ontology.getColor('normativeStatus', status) ?? 'bg-gray-50 text-gray-700',
|
|
43
27
|
definition: concept?.definition ?? undefined,
|
|
44
28
|
};
|
|
45
29
|
}
|
|
46
30
|
|
|
47
31
|
export function sourceStatusInfo(status: string | null): { label: string; color: string; definition?: string } {
|
|
48
32
|
if (!status) return { label: '', color: 'badge-gray' };
|
|
49
|
-
|
|
50
33
|
const concept = ontology.getConcept('sourceStatus', status);
|
|
51
34
|
return {
|
|
52
35
|
label: concept?.prefLabel ?? status,
|
|
@@ -57,16 +40,10 @@ export function sourceStatusInfo(status: string | null): { label: string; color:
|
|
|
57
40
|
|
|
58
41
|
export function sourceTypeInfo(type: string | null): { label: string; color: string; definition?: string } {
|
|
59
42
|
if (!type) return { label: '', color: 'badge-gray' };
|
|
60
|
-
|
|
61
|
-
const colors: Record<string, string> = {
|
|
62
|
-
authoritative: 'badge-purple',
|
|
63
|
-
lineage: 'badge-blue',
|
|
64
|
-
};
|
|
65
|
-
|
|
66
43
|
const concept = ontology.getConcept('sourceType', type);
|
|
67
44
|
return {
|
|
68
45
|
label: concept?.prefLabel ?? type,
|
|
69
|
-
color:
|
|
46
|
+
color: ontology.getColor('sourceType', type) ?? 'badge-gray',
|
|
70
47
|
definition: concept?.definition ?? undefined,
|
|
71
48
|
};
|
|
72
49
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relationship categorization — fully derived from the ontology taxonomy.
|
|
3
|
+
*
|
|
4
|
+
* RELATIONSHIP_CATEGORIES, INVERSE_RELATIONSHIPS, and the category lookup
|
|
5
|
+
* map are all computed from `taxonomies.json` at module load time.
|
|
6
|
+
* Adding a new relationship type requires only a taxonomy edit — no code changes.
|
|
7
|
+
*/
|
|
1
8
|
import { ontology } from '../adapters/ontology-registry';
|
|
9
|
+
import type { TaxonomyCategory } from '../adapters/ontology-registry';
|
|
2
10
|
|
|
3
11
|
export interface RelationshipCategory {
|
|
4
12
|
id: string;
|
|
@@ -7,125 +15,64 @@ export interface RelationshipCategory {
|
|
|
7
15
|
color: string;
|
|
8
16
|
}
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
types: ['abbreviated_form_for', 'short_form_for'],
|
|
69
|
-
color: 'text-gray-600 bg-gray-50',
|
|
70
|
-
},
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
export const INVERSE_RELATIONSHIPS: Record<string, string> = {
|
|
76
|
-
// Lifecycle (ISO 10241-1)
|
|
77
|
-
supersedes: 'superseded_by',
|
|
78
|
-
superseded_by: 'supersedes',
|
|
79
|
-
deprecates: 'deprecated_by',
|
|
80
|
-
deprecated_by: 'deprecates',
|
|
81
|
-
replaces: 'replaced_by',
|
|
82
|
-
replaced_by: 'replaces',
|
|
83
|
-
invalidates: 'invalidated_by',
|
|
84
|
-
invalidated_by: 'invalidates',
|
|
85
|
-
retires: 'retired_by',
|
|
86
|
-
retired_by: 'retires',
|
|
87
|
-
|
|
88
|
-
// Hierarchical (generic — SKOS)
|
|
89
|
-
broader: 'narrower',
|
|
90
|
-
narrower: 'broader',
|
|
91
|
-
broader_generic: 'narrower_generic',
|
|
92
|
-
narrower_generic: 'broader_generic',
|
|
93
|
-
|
|
94
|
-
// Hierarchical (partitive — ISO 25964)
|
|
95
|
-
broader_partitive: 'narrower_partitive',
|
|
96
|
-
narrower_partitive: 'broader_partitive',
|
|
97
|
-
has_part: 'is_part_of',
|
|
98
|
-
is_part_of: 'has_part',
|
|
99
|
-
|
|
100
|
-
// Hierarchical (instantial — ISO 25964)
|
|
101
|
-
broader_instantial: 'narrower_instantial',
|
|
102
|
-
narrower_instantial: 'broader_instantial',
|
|
103
|
-
instance_of: 'has_instance',
|
|
104
|
-
has_instance: 'instance_of',
|
|
105
|
-
|
|
106
|
-
// ISO 19135 concept-to-concept
|
|
107
|
-
has_concept: 'is_concept_of',
|
|
108
|
-
is_concept_of: 'has_concept',
|
|
109
|
-
inherits: 'inherited_by',
|
|
110
|
-
inherited_by: 'inherits',
|
|
111
|
-
has_definition: 'definition_of',
|
|
112
|
-
definition_of: 'has_definition',
|
|
113
|
-
|
|
114
|
-
// ISO 19135 versioning
|
|
115
|
-
has_version: 'version_of',
|
|
116
|
-
version_of: 'has_version',
|
|
117
|
-
current_version: 'current_version_of',
|
|
118
|
-
current_version_of: 'current_version',
|
|
119
|
-
|
|
120
|
-
// Symmetric (self-inverse)
|
|
121
|
-
equivalent: 'equivalent',
|
|
122
|
-
exact_match: 'exact_match',
|
|
123
|
-
compare: 'compare',
|
|
124
|
-
contrast: 'contrast',
|
|
125
|
-
close_match: 'close_match',
|
|
126
|
-
related_match: 'related_match',
|
|
127
|
-
related_concept: 'related_concept',
|
|
128
|
-
};
|
|
18
|
+
// ── Derived data ──────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const CATEGORY_ORDER = [
|
|
21
|
+
'hierarchical', 'mapping', 'associative', 'lifecycle',
|
|
22
|
+
'comparative', 'definitional', 'spatiotemporal', 'lexical', 'designation',
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
function buildRelationshipData() {
|
|
26
|
+
const allTypes = ontology.getAll('relationshipType');
|
|
27
|
+
const categoryTypes = new Map<string, string[]>();
|
|
28
|
+
const inverseMap: Record<string, string> = {};
|
|
29
|
+
|
|
30
|
+
for (const concept of allTypes) {
|
|
31
|
+
if (concept.inverseOf) {
|
|
32
|
+
inverseMap[concept.id] = concept.inverseOf;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (concept.category) {
|
|
36
|
+
const list = categoryTypes.get(concept.category) ?? [];
|
|
37
|
+
list.push(concept.id);
|
|
38
|
+
categoryTypes.set(concept.category, list);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const categories: RelationshipCategory[] = [];
|
|
43
|
+
for (const catId of CATEGORY_ORDER) {
|
|
44
|
+
const types = categoryTypes.get(catId);
|
|
45
|
+
if (!types) continue;
|
|
46
|
+
const config = ontology.getCategoryConfig('relationshipType', catId);
|
|
47
|
+
categories.push({
|
|
48
|
+
id: catId,
|
|
49
|
+
label: config?.label ?? catId,
|
|
50
|
+
types,
|
|
51
|
+
color: config?.color ?? 'text-gray-600 bg-gray-50',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add any categories not in the canonical order
|
|
56
|
+
for (const [catId, types] of categoryTypes) {
|
|
57
|
+
if (categories.some(c => c.id === catId)) continue;
|
|
58
|
+
const config = ontology.getCategoryConfig('relationshipType', catId);
|
|
59
|
+
categories.push({
|
|
60
|
+
id: catId,
|
|
61
|
+
label: config?.label ?? catId,
|
|
62
|
+
types,
|
|
63
|
+
color: config?.color ?? 'text-gray-600 bg-gray-50',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { categories, inverseMap };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { categories: RELATIONSHIP_CATEGORIES, inverseMap: INVERSE_RELATIONSHIPS } = buildRelationshipData();
|
|
71
|
+
|
|
72
|
+
export { RELATIONSHIP_CATEGORIES, INVERSE_RELATIONSHIPS };
|
|
73
|
+
|
|
74
|
+
// ── Lookup maps ───────────────────────────────────────────────────────────
|
|
75
|
+
|
|
129
76
|
const CATEGORY_MAP = new Map<string, RelationshipCategory>();
|
|
130
77
|
for (const cat of RELATIONSHIP_CATEGORIES) {
|
|
131
78
|
for (const t of cat.types) {
|
|
@@ -133,16 +80,15 @@ for (const cat of RELATIONSHIP_CATEGORIES) {
|
|
|
133
80
|
}
|
|
134
81
|
}
|
|
135
82
|
|
|
83
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
136
85
|
export function categorizeRelationship(type: string): RelationshipCategory {
|
|
137
86
|
return CATEGORY_MAP.get(type) ?? { id: 'other', label: 'Other', types: [type], color: 'text-gray-600 bg-gray-50' };
|
|
138
87
|
}
|
|
139
88
|
|
|
140
89
|
export function relationshipLabel(type: string): string {
|
|
141
|
-
// Check the ontology taxonomy first (for glossarist-specific types)
|
|
142
90
|
const concept = ontology.getConcept('relationshipType', type);
|
|
143
91
|
if (concept?.prefLabel) return concept.prefLabel;
|
|
144
|
-
|
|
145
|
-
// Fallback: humanize the type string
|
|
146
92
|
return type.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
147
93
|
}
|
|
148
94
|
|