@glossarist/concept-browser 0.7.99 → 0.7.101
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/src/__tests__/components/hyperedge-diagram.test.ts +55 -0
- package/src/__tests__/components/learn/learn-card-dimension-card.test.ts +112 -0
- package/src/__tests__/components/rake-bundles.test.ts +318 -0
- package/src/__tests__/views/learn/learn-designations-view.test.ts +84 -0
- package/src/__tests__/views/learn/learn-index-view.test.ts +45 -0
- package/src/__tests__/views/learn/learn-relationships-view.test.ts +83 -0
- package/src/__tests__/views/learn/learn-statuses-view.test.ts +60 -0
- package/src/components/HyperedgeDiagram.vue +115 -0
- package/src/components/RelationSphere.vue +30 -13
- package/src/components/learn/DimensionCard.vue +71 -0
- package/src/components/learn/LearnCard.vue +59 -0
- package/src/config/page-types.ts +1 -0
- package/src/i18n/locales/eng.yml +106 -0
- package/src/i18n/locales/fra.yml +106 -0
- package/src/router/index.ts +24 -0
- package/src/views/RelationTypesView.vue +159 -0
- package/src/views/learn/LearnDesignationsView.vue +190 -0
- package/src/views/learn/LearnIndexView.vue +75 -0
- package/src/views/learn/LearnRelationshipsView.vue +254 -0
- package/src/views/learn/LearnStatusesView.vue +114 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import LearnRelationshipsView from '../../../views/learn/LearnRelationshipsView.vue';
|
|
6
|
+
|
|
7
|
+
async function makeRouter() {
|
|
8
|
+
const router = createRouter({
|
|
9
|
+
history: createMemoryHistory(),
|
|
10
|
+
routes: [
|
|
11
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
12
|
+
{ path: '/learn/relationships', name: 'learn-relationships', component: LearnRelationshipsView },
|
|
13
|
+
],
|
|
14
|
+
});
|
|
15
|
+
await router.push('/learn/relationships');
|
|
16
|
+
await router.isReady();
|
|
17
|
+
return router;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('LearnRelationshipsView', () => {
|
|
21
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
22
|
+
|
|
23
|
+
it('renders the page header with translated title', async () => {
|
|
24
|
+
const router = await makeRouter();
|
|
25
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
26
|
+
expect(w.find('h1').text()).toContain('Relationships');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders three top-level sections (bilateral, partitive, general)', async () => {
|
|
30
|
+
const router = await makeRouter();
|
|
31
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
32
|
+
const sections = w.findAll('section[aria-label]');
|
|
33
|
+
expect(sections.length).toBe(3);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renders the hyperedge cards with the expected anchors', async () => {
|
|
37
|
+
const router = await makeRouter();
|
|
38
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
39
|
+
const ids = w.findAll('.learn-card[id]').map(c => c.attributes('id'));
|
|
40
|
+
expect(ids).toContain('__partitive__');
|
|
41
|
+
expect(ids).toContain('__generic__');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders three DimensionCards for the partitive dimensions', async () => {
|
|
45
|
+
const router = await makeRouter();
|
|
46
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
47
|
+
const dimIds = w.findAll('.dimension-card[id]').map(c => c.attributes('id'));
|
|
48
|
+
expect(dimIds).toEqual(expect.arrayContaining(['partitive-presence', 'partitive-count', 'partitive-delimiting']));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders the bilateral section grouped by category', async () => {
|
|
52
|
+
const router = await makeRouter();
|
|
53
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
54
|
+
const bilateralArticles = w.find('section[aria-label="Bilateral relations (one-to-one)"]').findAll('article');
|
|
55
|
+
expect(bilateralArticles.length).toBeGreaterThanOrEqual(5);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('every bilateral type row has an id anchor matching the type key', async () => {
|
|
59
|
+
const router = await makeRouter();
|
|
60
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
61
|
+
const rows = w.findAll('.bilateral-row');
|
|
62
|
+
expect(rows.length).toBeGreaterThan(10);
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
const id = row.attributes('id');
|
|
65
|
+
expect(id).toBeTruthy();
|
|
66
|
+
expect(id).toMatch(/^[a-z_]+$/);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders a live diagram for each hyperedge card', async () => {
|
|
71
|
+
const router = await makeRouter();
|
|
72
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
73
|
+
expect(w.findAll('svg.hyperedge-diagram').length).toBe(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('renders ISO references on each hyperedge card', async () => {
|
|
77
|
+
const router = await makeRouter();
|
|
78
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
79
|
+
const isoRefs = w.findAll('.learn-card span.font-mono').map(s => s.text());
|
|
80
|
+
expect(isoRefs.some(t => t.includes('ISO 704:2022 §5.5.4.3'))).toBe(true);
|
|
81
|
+
expect(isoRefs.some(t => t.includes('ISO 704:2022 §5.5.4.2'))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import LearnStatusesView from '../../../views/learn/LearnStatusesView.vue';
|
|
6
|
+
|
|
7
|
+
async function makeRouter() {
|
|
8
|
+
const router = createRouter({
|
|
9
|
+
history: createMemoryHistory(),
|
|
10
|
+
routes: [
|
|
11
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
12
|
+
{ path: '/learn/statuses', name: 'learn-statuses', component: LearnStatusesView },
|
|
13
|
+
{ path: '/learn/designations', name: 'learn-designations', component: { template: '<div/>' } },
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
await router.push('/learn/statuses');
|
|
17
|
+
await router.isReady();
|
|
18
|
+
return router;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('LearnStatusesView', () => {
|
|
22
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
23
|
+
|
|
24
|
+
it('renders the page header', async () => {
|
|
25
|
+
const router = await makeRouter();
|
|
26
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
27
|
+
expect(w.find('h1').text()).toContain('Statuses');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders three top-level sections (entry, normative, source)', async () => {
|
|
31
|
+
const router = await makeRouter();
|
|
32
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
33
|
+
const sections = w.findAll('section[aria-label]');
|
|
34
|
+
expect(sections.length).toBe(3);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('entry section renders all four entry statuses with anchors', async () => {
|
|
38
|
+
const router = await makeRouter();
|
|
39
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
40
|
+
const entrySection = w.find('section[aria-label="Entry status"]');
|
|
41
|
+
const ids = entrySection.findAll('.learn-card[id]').map(c => c.attributes('id'));
|
|
42
|
+
expect(ids).toEqual(expect.arrayContaining(['entry-valid', 'entry-withdrawn', 'entry-draft', 'entry-superseded']));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('normative section cross-links to /learn/designations', async () => {
|
|
46
|
+
const router = await makeRouter();
|
|
47
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
48
|
+
const normative = w.find('section[aria-label="Normative status"]');
|
|
49
|
+
const link = normative.find('a');
|
|
50
|
+
expect(link.attributes('href')).toBe('/learn/designations');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('source section renders the four source statuses with anchors', async () => {
|
|
54
|
+
const router = await makeRouter();
|
|
55
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
56
|
+
const sourceSection = w.find('section[aria-label="Source status"]');
|
|
57
|
+
const ids = sourceSection.findAll('.learn-card[id]').map(c => c.attributes('id'));
|
|
58
|
+
expect(ids).toEqual(expect.arrayContaining(['source-identical', 'source-modified', 'source-restyled', 'source-lineage']));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* HyperedgeDiagram — small inline SVG that calls the actual sphere
|
|
4
|
+
* renderers (drawRakeBundles / drawGenericPipes) on hand-picked
|
|
5
|
+
* sample data. Used on the Relation Types teaching page.
|
|
6
|
+
*
|
|
7
|
+
* The diagrams stay in sync with the real sphere rendering by
|
|
8
|
+
* construction — no static SVG to drift out of date.
|
|
9
|
+
*/
|
|
10
|
+
import { onMounted, ref } from 'vue';
|
|
11
|
+
import { drawRakeBundles } from './relation-sphere/rake-bundles';
|
|
12
|
+
import { drawGenericPipes } from './relation-sphere/pipe-bundles';
|
|
13
|
+
import type {
|
|
14
|
+
PartitiveRelationWire,
|
|
15
|
+
PartitiveMemberWire,
|
|
16
|
+
GenericRelationWire,
|
|
17
|
+
} from '../adapters/types';
|
|
18
|
+
import { useUiStore } from '../stores/ui';
|
|
19
|
+
|
|
20
|
+
const props = defineProps<{
|
|
21
|
+
kind: 'partitive' | 'generic';
|
|
22
|
+
}>();
|
|
23
|
+
|
|
24
|
+
const svgRef = ref<SVGSVGElement | null>(null);
|
|
25
|
+
const uiStore = useUiStore();
|
|
26
|
+
|
|
27
|
+
/* Synthetic positions: comp at top-center, members fanned across the
|
|
28
|
+
bottom. Mirrors the layout the sphere produces for a 3-member
|
|
29
|
+
hyperedge with the focus at the center. */
|
|
30
|
+
const POS = new Map<string, { x: number; y: number }>([
|
|
31
|
+
['vim-2012/1.3', { x: 120, y: 24 }],
|
|
32
|
+
['vim-2012/1.4', { x: 36, y: 132 }],
|
|
33
|
+
['vim-2012/1.5', { x: 120, y: 144 }],
|
|
34
|
+
['vim-2012/1.22', { x: 204, y: 132 }],
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const PARTITIVE: PartitiveRelationWire = {
|
|
38
|
+
source: 'https://example.org/vim-2012/concept/1.3',
|
|
39
|
+
comprehensive: 'https://example.org/vim-2012/concept/1.3',
|
|
40
|
+
completeness: 'partial',
|
|
41
|
+
register: 'vim-2012',
|
|
42
|
+
partitives: [
|
|
43
|
+
/* required + multiple → solid double-line */
|
|
44
|
+
{ uri: 'https://example.org/vim-2012/concept/1.4', presence: 'required', count: 'multiple', isDelimiting: false },
|
|
45
|
+
/* required + exactly_one + delimiting → 3× width single solid */
|
|
46
|
+
{ uri: 'https://example.org/vim-2012/concept/1.5', presence: 'required', count: 'exactly_one', isDelimiting: true },
|
|
47
|
+
/* optional + at_least_one → dashed double-line */
|
|
48
|
+
{ uri: 'https://example.org/vim-2012/concept/1.22', presence: 'optional', count: 'at_least_one', isDelimiting: false },
|
|
49
|
+
] satisfies PartitiveMemberWire[],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const GENERIC: GenericRelationWire = {
|
|
53
|
+
source: 'https://example.org/vim-2012/concept/1.9',
|
|
54
|
+
comprehensive: 'https://example.org/vim-2012/concept/1.9',
|
|
55
|
+
completeness: 'partial',
|
|
56
|
+
register: 'vim-2012',
|
|
57
|
+
criterion: { eng: 'by magnitude relationship', fra: 'selon la relation de grandeur' },
|
|
58
|
+
members: [
|
|
59
|
+
{ uri: 'https://example.org/vim-2012/concept/1.17', presence: 'required', count: 'at_least_one',
|
|
60
|
+
delimitingCharacteristic: { eng: 'multiple of a unit', fra: "multiple d'une unité" } },
|
|
61
|
+
{ uri: 'https://example.org/vim-2012/concept/1.18', presence: 'required', count: 'at_least_one',
|
|
62
|
+
delimitingCharacteristic: { eng: 'submultiple of a unit', fra: "sous-multiple d'une unité" } },
|
|
63
|
+
{ uri: 'https://example.org/vim-2012/concept/1.12', presence: 'required', count: 'at_least_one',
|
|
64
|
+
delimitingCharacteristic: { eng: 'coherent derived unit', fra: 'unité dérivée cohérente' } },
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/* Override positions for the generic sample (comp is 1.9, members are 1.17/1.18/1.12). */
|
|
69
|
+
const GENERIC_POS = new Map<string, { x: number; y: number }>([
|
|
70
|
+
['vim-2012/1.9', { x: 120, y: 24 }],
|
|
71
|
+
['vim-2012/1.17', { x: 36, y: 132 }],
|
|
72
|
+
['vim-2012/1.18', { x: 120, y: 144 }],
|
|
73
|
+
['vim-2012/1.12', { x: 204, y: 132 }],
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
onMounted(() => {
|
|
77
|
+
const svg = svgRef.value;
|
|
78
|
+
if (!svg) return;
|
|
79
|
+
if (props.kind === 'partitive') {
|
|
80
|
+
drawRakeBundles(svg, POS, {
|
|
81
|
+
isDark: uiStore.isDark,
|
|
82
|
+
color: uiStore.isDark ? '#2dd4bf' : '#0d9488',
|
|
83
|
+
isMuted: false,
|
|
84
|
+
relations: [PARTITIVE],
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
drawGenericPipes(svg, GENERIC_POS, {
|
|
88
|
+
isDark: uiStore.isDark,
|
|
89
|
+
color: uiStore.isDark ? '#fbbf24' : '#b45309',
|
|
90
|
+
isMuted: false,
|
|
91
|
+
relations: [GENERIC],
|
|
92
|
+
locale: 'eng',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<template>
|
|
99
|
+
<svg
|
|
100
|
+
ref="svgRef"
|
|
101
|
+
class="hyperedge-diagram"
|
|
102
|
+
viewBox="0 0 240 168"
|
|
103
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
104
|
+
aria-hidden="true"
|
|
105
|
+
/>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<style scoped>
|
|
109
|
+
.hyperedge-diagram {
|
|
110
|
+
width: 100%;
|
|
111
|
+
max-width: 240px;
|
|
112
|
+
height: auto;
|
|
113
|
+
display: block;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -1068,7 +1068,7 @@ const legendItems = computed(() => {
|
|
|
1068
1068
|
if (partitiveCount > 0) {
|
|
1069
1069
|
typeMap.set('__partitive__', {
|
|
1070
1070
|
type: '__partitive__',
|
|
1071
|
-
label: '
|
|
1071
|
+
label: 'partitive relation',
|
|
1072
1072
|
color: uiStore.isDark ? '#2dd4bf' : '#0d9488',
|
|
1073
1073
|
count: partitiveCount,
|
|
1074
1074
|
});
|
|
@@ -1078,7 +1078,7 @@ const legendItems = computed(() => {
|
|
|
1078
1078
|
if (genericCount > 0) {
|
|
1079
1079
|
typeMap.set('__generic__', {
|
|
1080
1080
|
type: '__generic__',
|
|
1081
|
-
label: '
|
|
1081
|
+
label: 'general relation',
|
|
1082
1082
|
color: uiStore.isDark ? '#fbbf24' : '#b45309',
|
|
1083
1083
|
count: genericCount,
|
|
1084
1084
|
});
|
|
@@ -1405,16 +1405,23 @@ defineExpose({ navigate });
|
|
|
1405
1405
|
<div v-if="legendItems.length" class="sp-section">
|
|
1406
1406
|
<div class="sp-section-label">{{ t('sphere.relationTypes') }}</div>
|
|
1407
1407
|
<div class="sp-legend-grid">
|
|
1408
|
-
<
|
|
1408
|
+
<div v-for="item in legendItems" :key="item.type"
|
|
1409
|
+
class="sp-legend-item"
|
|
1409
1410
|
:class="{ muted: isTypeMuted(item.type), 'is-partitive': item.type === '__partitive__' }"
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
:
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1411
|
+
>
|
|
1412
|
+
<button class="sp-legend-toggle"
|
|
1413
|
+
@click="toggleType(item.type)"
|
|
1414
|
+
:title="item.type"
|
|
1415
|
+
:aria-label="`${t('sphere.toggleMute')}: ${item.label}`">
|
|
1416
|
+
<span v-if="item.type === '__partitive__'" class="sp-swatch sp-swatch-rake"
|
|
1417
|
+
:style="{ '--rake-color': isTypeMuted(item.type) ? '#b8b9cc' : item.color }"></span>
|
|
1418
|
+
<span v-else class="sp-swatch" :style="{ background: isTypeMuted(item.type) ? '#b8b9cc' : item.color }"></span>
|
|
1419
|
+
<span class="sp-count">{{ item.count }}</span>
|
|
1420
|
+
</button>
|
|
1421
|
+
<router-link class="sp-legend-label"
|
|
1422
|
+
:to="{ name: 'learn-relationships', hash: '#' + item.type }"
|
|
1423
|
+
:title="t('sphere.learnAbout') + ': ' + item.label">{{ item.label }}</router-link>
|
|
1424
|
+
</div>
|
|
1418
1425
|
</div>
|
|
1419
1426
|
</div>
|
|
1420
1427
|
|
|
@@ -1653,12 +1660,22 @@ defineExpose({ navigate });
|
|
|
1653
1660
|
/* Type legend */
|
|
1654
1661
|
.sp-legend-grid { display: flex; flex-direction: column; gap: 2px; }
|
|
1655
1662
|
.sp-legend-item {
|
|
1656
|
-
display: flex; align-items: center; gap:
|
|
1657
|
-
|
|
1663
|
+
display: flex; align-items: center; gap: 6px; padding: 3px 6px; border-radius: 3px;
|
|
1664
|
+
border: 1px solid transparent; background: transparent; text-align: left;
|
|
1658
1665
|
width: 100%; font-family: inherit; font-size: 11px; color: var(--ink-mute);
|
|
1659
1666
|
}
|
|
1660
1667
|
.sp-legend-item:hover { background: var(--rule); }
|
|
1661
1668
|
.sp-legend-item.muted { opacity: 0.35; }
|
|
1669
|
+
.sp-legend-toggle {
|
|
1670
|
+
display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
|
|
1671
|
+
cursor: pointer; border: none; background: transparent; padding: 0;
|
|
1672
|
+
font: inherit; color: inherit;
|
|
1673
|
+
}
|
|
1674
|
+
.sp-legend-label {
|
|
1675
|
+
color: inherit; text-decoration: none; flex: 1; min-width: 0;
|
|
1676
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1677
|
+
}
|
|
1678
|
+
.sp-legend-label:hover { text-decoration: underline; }
|
|
1662
1679
|
.sp-swatch { width: 18px; height: 2px; flex-shrink: 0; border-radius: 1px; }
|
|
1663
1680
|
/* Rake swatch — small bracket icon signaling a PartitiveRelation hyperedge
|
|
1664
1681
|
(not a single binary edge). Two parallel solid lines (close-set double)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DimensionCard — structured breakdown of a single dimension along
|
|
4
|
+
* which hyperedge members vary. Used on /learn/relationships for the
|
|
5
|
+
* partitive dimensions (presence, count, delimiting).
|
|
6
|
+
*
|
|
7
|
+
* Renders one row per value showing:
|
|
8
|
+
* - the raw value (machine key)
|
|
9
|
+
* - the human label
|
|
10
|
+
* - a short meaning (what it encodes)
|
|
11
|
+
* - a small visual representation (slot — typically a mini SVG)
|
|
12
|
+
*
|
|
13
|
+
* The user's specific concern: "individually show what dimensions
|
|
14
|
+
* there are and how lines look like". This component is the answer.
|
|
15
|
+
*/
|
|
16
|
+
export interface DimensionValue {
|
|
17
|
+
value: string;
|
|
18
|
+
label: string;
|
|
19
|
+
meaning: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
defineProps<{
|
|
23
|
+
id?: string;
|
|
24
|
+
name: string;
|
|
25
|
+
isoReference?: string;
|
|
26
|
+
/** Plain-language summary of what this dimension captures. */
|
|
27
|
+
summary: string;
|
|
28
|
+
values: readonly DimensionValue[];
|
|
29
|
+
}>();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<article :id="id" class="dimension-card card p-5 scroll-mt-20">
|
|
34
|
+
<header class="flex items-baseline justify-between gap-3 mb-2 flex-wrap">
|
|
35
|
+
<h4 class="text-base font-semibold text-ink-800 dark:text-ink-50">{{ name }}</h4>
|
|
36
|
+
<span v-if="isoReference" class="text-xs font-mono text-ink-400 dark:text-ink-500">
|
|
37
|
+
{{ isoReference }}
|
|
38
|
+
</span>
|
|
39
|
+
</header>
|
|
40
|
+
<p class="text-sm text-ink-600 dark:text-ink-200 mb-4">{{ summary }}</p>
|
|
41
|
+
<ul class="dimension-values space-y-3">
|
|
42
|
+
<li v-for="v in values" :key="v.value" class="dimension-value-row">
|
|
43
|
+
<div class="flex items-center gap-3">
|
|
44
|
+
<div class="dimension-visual">
|
|
45
|
+
<slot name="visual" :value="v.value" />
|
|
46
|
+
</div>
|
|
47
|
+
<div class="flex-1 min-w-0">
|
|
48
|
+
<div class="flex items-baseline gap-2 flex-wrap">
|
|
49
|
+
<span class="font-medium text-ink-800 dark:text-ink-100">{{ v.label }}</span>
|
|
50
|
+
<code class="text-xs text-ink-400 dark:text-ink-500 font-mono">{{ v.value }}</code>
|
|
51
|
+
</div>
|
|
52
|
+
<p class="text-sm text-ink-600 dark:text-ink-300 mt-0.5">{{ v.meaning }}</p>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</li>
|
|
56
|
+
</ul>
|
|
57
|
+
</article>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<style scoped>
|
|
61
|
+
.dimension-card {
|
|
62
|
+
scroll-margin-top: 5rem;
|
|
63
|
+
}
|
|
64
|
+
.dimension-visual {
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
width: 48px;
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: center;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LearnCard — the standard topic container across all /learn pages.
|
|
4
|
+
*
|
|
5
|
+
* Layout: title + ISO reference on top, then a flexible body. Optional
|
|
6
|
+
* named slots:
|
|
7
|
+
* - `diagram` — a small visual (live SVG, badge samples, etc.)
|
|
8
|
+
* - `example` — a concrete real-world example
|
|
9
|
+
* Optional `learnMoreUrl` renders a "Learn more on glossarist.org"
|
|
10
|
+
* link at the bottom — keeps concept-browser reader-focused while
|
|
11
|
+
* bridging to the canonical author docs on glossarist.org.
|
|
12
|
+
*
|
|
13
|
+
* Anchored at the `id` prop so URLs like `/learn/relationships#partitive`
|
|
14
|
+
* deep-link to the right card.
|
|
15
|
+
*/
|
|
16
|
+
defineProps<{
|
|
17
|
+
id?: string;
|
|
18
|
+
title: string;
|
|
19
|
+
isoReference?: string;
|
|
20
|
+
learnMoreUrl?: string;
|
|
21
|
+
learnMoreLabel?: string;
|
|
22
|
+
}>();
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<article :id="id" class="learn-card card p-5 scroll-mt-20">
|
|
27
|
+
<header class="flex items-baseline justify-between gap-3 mb-3 flex-wrap">
|
|
28
|
+
<h3 class="text-lg font-semibold text-ink-800 dark:text-ink-50">{{ title }}</h3>
|
|
29
|
+
<span v-if="isoReference" class="text-xs font-mono text-ink-400 dark:text-ink-500">
|
|
30
|
+
{{ isoReference }}
|
|
31
|
+
</span>
|
|
32
|
+
</header>
|
|
33
|
+
<div v-if="$slots.diagram" class="learn-card-diagram mb-3">
|
|
34
|
+
<slot name="diagram" />
|
|
35
|
+
</div>
|
|
36
|
+
<div class="learn-card-body text-sm text-ink-600 dark:text-ink-200">
|
|
37
|
+
<slot />
|
|
38
|
+
</div>
|
|
39
|
+
<div v-if="$slots.example" class="learn-card-example mt-3 pt-3 border-t border-ink-100 dark:border-ink-700 text-xs text-ink-500 dark:text-ink-400">
|
|
40
|
+
<slot name="example" />
|
|
41
|
+
</div>
|
|
42
|
+
<footer v-if="learnMoreUrl" class="learn-card-footer mt-3 text-xs">
|
|
43
|
+
<a :href="learnMoreUrl" target="_blank" rel="noopener"
|
|
44
|
+
class="text-ink-500 dark:text-ink-400 hover:text-ink-800 dark:hover:text-ink-100 underline">
|
|
45
|
+
{{ learnMoreLabel ?? 'Learn more on glossarist.org' }} ↗
|
|
46
|
+
</a>
|
|
47
|
+
</footer>
|
|
48
|
+
</article>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<style scoped>
|
|
52
|
+
.learn-card {
|
|
53
|
+
scroll-margin-top: 5rem;
|
|
54
|
+
}
|
|
55
|
+
.learn-card-diagram {
|
|
56
|
+
display: flex;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
package/src/config/page-types.ts
CHANGED
|
@@ -35,6 +35,7 @@ export const PAGE_TYPES: readonly PageTypeDefinition[] = [
|
|
|
35
35
|
{ type: 'stats', route: 'stats', title: 'Statistics', icon: 'chart', scope: 'dataset', featureFlag: '!stats', autoSynthesize: true },
|
|
36
36
|
{ type: 'sources', route: 'sources', title: 'Sources', icon: 'database', scope: 'dataset', autoSynthesize: true },
|
|
37
37
|
{ type: 'about', route: 'about', title: 'About', icon: 'info', scope: 'dataset', featureFlag: '!about', autoSynthesize: true },
|
|
38
|
+
{ type: 'learn', route: 'learn', title: 'Learn', icon: 'book-open', scope: 'global', autoSynthesize: true },
|
|
38
39
|
] as const;
|
|
39
40
|
|
|
40
41
|
export type BuiltinPageType = typeof PAGE_TYPES[number]['type'];
|
package/src/i18n/locales/eng.yml
CHANGED
|
@@ -12,6 +12,110 @@ nav.about: About
|
|
|
12
12
|
nav.sources: Sources
|
|
13
13
|
nav.navigation: Navigation
|
|
14
14
|
nav.datasets: Datasets
|
|
15
|
+
nav.relationTypes: Relation Types
|
|
16
|
+
nav.learn: Learn
|
|
17
|
+
|
|
18
|
+
# Learn section (reader-focused teaching)
|
|
19
|
+
learn.intro: This section explains what you see on this site and what each label means. For canonical methodology docs (authoring datasets, ISO alignment), see glossarist.org.
|
|
20
|
+
learn.glossaristOrgNote: Methodology documentation for dataset authors lives on glossarist.org.
|
|
21
|
+
learn.example: Example
|
|
22
|
+
learn.learnMore: Learn more on glossarist.org
|
|
23
|
+
learn.relationships.title: Relationships
|
|
24
|
+
learn.relationships.short: How to read the bilateral edges, partitive rake, and general pipe-and-thread.
|
|
25
|
+
learn.relationships.intro: Every line and label on the Relation Sphere, the detail page, and the graph view maps to a typed semantic relation. This page explains what each kind means and how to read its visual encoding.
|
|
26
|
+
learn.relationships.bilateralSection: Bilateral relations (one-to-one)
|
|
27
|
+
learn.relationships.bilateralIntro: One-to-one typed relations between two concepts. Drawn as straight colored lines with arrowheads in the sphere.
|
|
28
|
+
learn.relationships.partitiveSection: Partitive hyperedge (one-to-many)
|
|
29
|
+
learn.relationships.partitiveTitle: partitive relation
|
|
30
|
+
learn.relationships.partitiveBody: A whole/part decomposition. The comprehensive concept is the whole; each member is a part. Rendered as a rake — a stem from the comprehensive to a perpendicular spine, with teeth dropping to each partitive member.
|
|
31
|
+
learn.relationships.generalSection: General hyperedge (one-to-many)
|
|
32
|
+
learn.relationships.generalTitle: general relation
|
|
33
|
+
learn.relationships.generalBody: A genus/species decomposition. The comprehensive concept is the genus; each member is a coordinate species differing along a shared dimension. Rendered as a tree branch — a thick parent pipe from the comprehensive to a middle node, with thin threads radiating to each member.
|
|
34
|
+
learn.relationships.criterionTitle: Criterion of subdivision
|
|
35
|
+
learn.relationships.criterionBody: "The dimension along which coordinate concepts differ. Shown once on the parent pipe body. Optional: when no criterion is described, no label is drawn."
|
|
36
|
+
learn.relationships.thickThinTitle: Thick/thin hierarchy
|
|
37
|
+
learn.relationships.thickThinBody: ISO 704 traditionally uses thicker lines to mark different criteria of subdivision (multidimensionality). The pipe-and-thread honors this with a thick parent pipe (3.2px) and thinner child threads (1.0px) — the tree-branch metaphor (trunk vs twigs).
|
|
38
|
+
learn.dimensions.sectionTitle: Partitive dimensions (how to read the rake teeth)
|
|
39
|
+
learn.dimensions.presence.title: Presence
|
|
40
|
+
learn.dimensions.presence.summary: Whether the part must be present (compulsory) or may be absent (optional).
|
|
41
|
+
learn.dimensions.presence.required.label: required
|
|
42
|
+
learn.dimensions.presence.required.meaning: The part must be present. Drawn as a solid line.
|
|
43
|
+
learn.dimensions.presence.optional.label: optional
|
|
44
|
+
learn.dimensions.presence.optional.meaning: The part may be absent. Drawn as a dashed line.
|
|
45
|
+
learn.dimensions.count.title: Count
|
|
46
|
+
learn.dimensions.count.summary: How many instances of the part occur in the whole.
|
|
47
|
+
learn.dimensions.count.exactly_one.label: exactly one
|
|
48
|
+
learn.dimensions.count.exactly_one.meaning: One and only one. Drawn as a single line.
|
|
49
|
+
learn.dimensions.count.at_least_one.label: at least one
|
|
50
|
+
learn.dimensions.count.at_least_one.meaning: One or more. Drawn as a double line, the secondary dashed ("possibly more").
|
|
51
|
+
learn.dimensions.count.multiple.label: multiple
|
|
52
|
+
learn.dimensions.count.multiple.meaning: Two or more. Drawn as a double line, both the same style as the primary.
|
|
53
|
+
learn.dimensions.delimiting.title: Delimiting
|
|
54
|
+
learn.dimensions.delimiting.summary: Whether the part reflects a delimiting characteristic — one that distinguishes the whole from coordinate concepts.
|
|
55
|
+
learn.dimensions.delimiting.false.label: not delimiting
|
|
56
|
+
learn.dimensions.delimiting.false.meaning: An ordinary part. Drawn at normal stroke width (1.5px).
|
|
57
|
+
learn.dimensions.delimiting.true.label: delimiting
|
|
58
|
+
learn.dimensions.delimiting.true.meaning: The part is essential to the whole's identity. Drawn at 3× normal stroke width (4.5px).
|
|
59
|
+
learn.designations.title: Designations
|
|
60
|
+
learn.designations.short: Terms and symbols — preferred, admitted, deprecated, full form, abbreviation, acronym, initialism.
|
|
61
|
+
learn.designations.intro: Concepts are designated by terms, proper names, and symbols. This page explains the vocabulary you'll see on each concept page.
|
|
62
|
+
learn.designations.normativeSection: Normative status
|
|
63
|
+
learn.designations.normativeIntro: Each term carries a normative rating from the standardizing body. There can be MORE THAN ONE of each rating — preferred terms aren't unique.
|
|
64
|
+
learn.designations.preferred.label: preferred term
|
|
65
|
+
learn.designations.preferred.meaning: The primary term for the concept, rated by a standardizing body.
|
|
66
|
+
learn.designations.preferred.multipleNote: There can be more than one preferred term.
|
|
67
|
+
learn.designations.admitted.label: admitted term
|
|
68
|
+
learn.designations.admitted.meaning: A synonym for the preferred term, but not rated as preferred.
|
|
69
|
+
learn.designations.admitted.multipleNote: There can be more than one admitted term.
|
|
70
|
+
learn.designations.deprecated.label: deprecated term
|
|
71
|
+
learn.designations.deprecated.meaning: A synonym for the preferred term, but rated as undesired.
|
|
72
|
+
learn.designations.deprecated.multipleNote: There can be more than one deprecated term.
|
|
73
|
+
learn.designations.kindsSection: Kinds and forms of term
|
|
74
|
+
learn.designations.fullForm.label: full form
|
|
75
|
+
learn.designations.fullForm.meaning: The complete representation of a designation.
|
|
76
|
+
learn.designations.abbreviatedForm.label: abbreviated form
|
|
77
|
+
learn.designations.abbreviatedForm.meaning: A representation resulting from the omission of any part of the full form.
|
|
78
|
+
learn.designations.abbreviatedForm.multipleNote: For some full forms, two or more abbreviated forms may exist.
|
|
79
|
+
learn.designations.borrowed.label: borrowed term
|
|
80
|
+
learn.designations.borrowed.meaning: A term taken from another language or from another domain.
|
|
81
|
+
learn.designations.variant.label: variant
|
|
82
|
+
learn.designations.variant.meaning: One of the alternative forms of a designation (e.g. spelling differences across regions).
|
|
83
|
+
learn.designations.typesSection: Designation types
|
|
84
|
+
learn.designations.typesIntro: Three top-level types — terms, proper names, and symbols. Within terms, abbreviations split further into acronyms vs initialisms based on how they're pronounced.
|
|
85
|
+
learn.designations.term.label: term (expression)
|
|
86
|
+
learn.designations.term.meaning: A spoken or written designation — the most common type.
|
|
87
|
+
learn.designations.acronym.label: acronym
|
|
88
|
+
learn.designations.acronym.meaning: An abbreviated form pronounced as a single word.
|
|
89
|
+
learn.designations.initialism.label: initialism
|
|
90
|
+
learn.designations.initialism.meaning: An abbreviated form pronounced letter by letter.
|
|
91
|
+
learn.designations.symbol.label: symbol
|
|
92
|
+
learn.designations.symbol.meaning: A graphical or mathematical designation governed by ISO 80000 / IEC 60027.
|
|
93
|
+
learn.statuses.title: Statuses
|
|
94
|
+
learn.statuses.short: Entry status (valid/withdrawn/draft), normative status, source status — what each badge means.
|
|
95
|
+
learn.statuses.intro: Status badges appear throughout the site. This page is a quick reference for what each one means.
|
|
96
|
+
learn.statuses.entrySection: Entry status
|
|
97
|
+
learn.statuses.entry.valid.label: valid
|
|
98
|
+
learn.statuses.entry.valid.meaning: The concept entry is currently in force.
|
|
99
|
+
learn.statuses.entry.withdrawn.label: withdrawn
|
|
100
|
+
learn.statuses.entry.withdrawn.meaning: The entry has been removed from active use.
|
|
101
|
+
learn.statuses.entry.draft.label: draft
|
|
102
|
+
learn.statuses.entry.draft.meaning: The entry is under development and not yet finalized.
|
|
103
|
+
learn.statuses.entry.superseded.label: superseded
|
|
104
|
+
learn.statuses.entry.superseded.meaning: The entry has been replaced by a newer one.
|
|
105
|
+
learn.statuses.normativeSection: Normative status
|
|
106
|
+
learn.statuses.normativeTitle: preferred / admitted / deprecated
|
|
107
|
+
learn.statuses.normativeSummary: Per-term rating (preferred, admitted, deprecated). Each rating can apply to more than one term per concept.
|
|
108
|
+
learn.statuses.normativeGoDeeper: See designation types for examples
|
|
109
|
+
learn.statuses.sourceSection: Source status
|
|
110
|
+
learn.statuses.sourceIntro: Each citation carries a status describing how faithfully it reproduces the source.
|
|
111
|
+
learn.statuses.source.identical.label: identical
|
|
112
|
+
learn.statuses.source.identical.meaning: The definition matches the source verbatim.
|
|
113
|
+
learn.statuses.source.modified.label: modified
|
|
114
|
+
learn.statuses.source.modified.meaning: The definition is adapted from the source with editorial changes.
|
|
115
|
+
learn.statuses.source.restyled.label: restyled
|
|
116
|
+
learn.statuses.source.restyled.meaning: The definition preserves the source meaning but rewords it heavily.
|
|
117
|
+
learn.statuses.source.lineage.label: lineage
|
|
118
|
+
learn.statuses.source.lineage.meaning: The source is the historical ancestor of this concept, not a direct citation.
|
|
15
119
|
|
|
16
120
|
# Home page
|
|
17
121
|
home.search: Search
|
|
@@ -213,6 +317,8 @@ sphere.tight: tight
|
|
|
213
317
|
sphere.loose: loose
|
|
214
318
|
sphere.redraw: Redraw
|
|
215
319
|
sphere.focus: FOCUS
|
|
320
|
+
sphere.toggleMute: Toggle visibility
|
|
321
|
+
sphere.learnAbout: Learn about this relation type
|
|
216
322
|
concept.detailView: Detail
|
|
217
323
|
concept.relationSphere: Relation Sphere
|
|
218
324
|
concept.permalink: Permalink
|