@glossarist/concept-browser 0.1.9 → 0.2.1
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 +1 -38
- package/src/App.vue +1 -9
- package/src/components/AppSidebar.vue +4 -2
- package/src/config/types.ts +11 -0
- package/src/config/use-site-config.ts +32 -22
- package/src/router/index.ts +22 -0
- package/src/views/ContributorsView.vue +20 -72
- package/src/router/page-routes.ts +0 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -678,46 +678,8 @@ function processNewsPage(config, page) {
|
|
|
678
678
|
console.log(`Generated news index: ${index.length} posts, ${postFiles.length} files copied to public/news/`);
|
|
679
679
|
}
|
|
680
680
|
|
|
681
|
-
function processContributorsPage(config, page) {
|
|
682
|
-
const contributors = { register: config.id, contributors: [] };
|
|
683
|
-
|
|
684
|
-
for (const ds of config.datasets) {
|
|
685
|
-
const infoYamlPath = path.join(ROOT, '.datasets', ds.id, 'info.yaml');
|
|
686
|
-
if (!fs.existsSync(infoYamlPath)) continue;
|
|
687
|
-
|
|
688
|
-
try {
|
|
689
|
-
const info = readYaml(infoYamlPath);
|
|
690
|
-
if (info.header) {
|
|
691
|
-
contributors.owner = info.header['register-owner'];
|
|
692
|
-
contributors.manager = info.header['register-manager'];
|
|
693
|
-
}
|
|
694
|
-
if (info.languages) {
|
|
695
|
-
for (const [lang, data] of Object.entries(info.languages)) {
|
|
696
|
-
contributors.contributors.push({
|
|
697
|
-
language: lang,
|
|
698
|
-
registerName: data['register-name'] || '',
|
|
699
|
-
organization: data['submitting-organisation-name'] || '',
|
|
700
|
-
contact: data['submitting-organisation-contact'] || '',
|
|
701
|
-
email: data['submitting-organisation-contact-email'] || '',
|
|
702
|
-
uri: data['uniform-resource-identifier-uri'] || '',
|
|
703
|
-
country: data['operating-language-country'] || '',
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
} catch (e) {
|
|
708
|
-
console.warn(` Skipping contributors for ${ds.id}: ${e.message}`);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
if (contributors.contributors.length > 0 || contributors.owner) {
|
|
713
|
-
writeJson(path.join(PUBLIC, 'contributors.json'), contributors);
|
|
714
|
-
console.log(`Generated contributors: ${contributors.contributors.length} languages`);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
681
|
const pageProcessors = {
|
|
719
682
|
news: processNewsPage,
|
|
720
|
-
contributors: processContributorsPage,
|
|
721
683
|
};
|
|
722
684
|
|
|
723
685
|
function synthesizePages(config) {
|
|
@@ -766,6 +728,7 @@ writeJson(path.join(PUBLIC, 'site-config.json'), {
|
|
|
766
728
|
defaults: config.defaults,
|
|
767
729
|
email: config.email,
|
|
768
730
|
pages: processedPages.length > 0 ? processedPages : undefined,
|
|
731
|
+
contributors: config.contributors || undefined,
|
|
769
732
|
});
|
|
770
733
|
console.log('Generated site-config.json');
|
|
771
734
|
|
package/src/App.vue
CHANGED
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
import { onMounted, onUnmounted, ref } from 'vue';
|
|
3
3
|
import { useVocabularyStore } from './stores/vocabulary';
|
|
4
4
|
import { useSiteConfig } from './config/use-site-config';
|
|
5
|
-
import { buildPageRoutes } from './router/page-routes';
|
|
6
|
-
import router from './router';
|
|
7
5
|
import AppHeader from './components/AppHeader.vue';
|
|
8
6
|
import AppSidebar from './components/AppSidebar.vue';
|
|
9
7
|
import AppFooter from './components/AppFooter.vue';
|
|
10
8
|
|
|
11
9
|
const store = useVocabularyStore();
|
|
12
|
-
const { loadConfig, config
|
|
10
|
+
const { loadConfig, config } = useSiteConfig();
|
|
13
11
|
const appReady = ref(false);
|
|
14
12
|
const showScrollTop = ref(false);
|
|
15
13
|
let mainEl: HTMLElement | null = null;
|
|
@@ -27,12 +25,6 @@ onMounted(async () => {
|
|
|
27
25
|
if (cfg?.title) {
|
|
28
26
|
document.title = cfg.title;
|
|
29
27
|
}
|
|
30
|
-
const allPages = [...globalPages.value, ...datasetPages.value];
|
|
31
|
-
if (allPages.length > 0) {
|
|
32
|
-
for (const route of buildPageRoutes(allPages)) {
|
|
33
|
-
router.addRoute(route);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
28
|
appReady.value = true;
|
|
37
29
|
// Watch scroll on main content area
|
|
38
30
|
mainEl = document.querySelector('main');
|
|
@@ -48,8 +48,10 @@ function pageRoute(page: { route: string; datasetScoped?: boolean }): string {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function isActive(page: { route: string; datasetScoped?: boolean }): boolean {
|
|
51
|
-
if (!page.route)
|
|
52
|
-
|
|
51
|
+
if (!page.route) {
|
|
52
|
+
if (page.datasetScoped) return route.name === 'dataset' || route.name === 'concept';
|
|
53
|
+
return route.name === 'home';
|
|
54
|
+
}
|
|
53
55
|
return route.name === page.route;
|
|
54
56
|
}
|
|
55
57
|
</script>
|
package/src/config/types.ts
CHANGED
|
@@ -93,6 +93,16 @@ export interface DatasetConfig {
|
|
|
93
93
|
downloads?: string[];
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// === Contributors ===
|
|
97
|
+
|
|
98
|
+
export interface Contributor {
|
|
99
|
+
name: string;
|
|
100
|
+
role?: string;
|
|
101
|
+
organization?: string;
|
|
102
|
+
url?: string;
|
|
103
|
+
email?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
// === Downloads ===
|
|
97
107
|
|
|
98
108
|
export interface BulkFormatInfo {
|
|
@@ -144,4 +154,5 @@ export interface SiteConfig {
|
|
|
144
154
|
};
|
|
145
155
|
email?: string;
|
|
146
156
|
pages?: PageConfig[];
|
|
157
|
+
contributors?: Contributor[];
|
|
147
158
|
}
|
|
@@ -29,6 +29,7 @@ export interface RuntimeSiteConfig {
|
|
|
29
29
|
defaults?: { language?: string; languageOrder?: string[] };
|
|
30
30
|
email?: string;
|
|
31
31
|
pages?: PageConfig[];
|
|
32
|
+
contributors?: { name: string; role?: string; organization?: string; url?: string; email?: string }[];
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const siteConfig = ref<RuntimeSiteConfig | null>(null);
|
|
@@ -103,42 +104,51 @@ async function loadConfig(): Promise<RuntimeSiteConfig | null> {
|
|
|
103
104
|
return siteConfig.value;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{ type: 'custom', route: 'search', title: 'Search', icon: 'search' },
|
|
109
|
-
{ type: 'custom', route: 'graph', title: 'Graph', icon: 'graph' },
|
|
110
|
-
];
|
|
107
|
+
function synthesizeGlobalPages(features?: Record<string, unknown>, pages?: PageConfig[]): PageConfig[] {
|
|
108
|
+
if (pages && pages.length > 0) return pages.filter(p => !p.datasetScoped);
|
|
111
109
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const result = [...BUILTIN_GLOBAL_PAGES];
|
|
110
|
+
const result: PageConfig[] = [
|
|
111
|
+
{ type: 'custom', route: '', title: 'Home', icon: 'home' },
|
|
112
|
+
];
|
|
113
|
+
if (features?.search !== false) {
|
|
114
|
+
result.push({ type: 'custom', route: 'search', title: 'Search', icon: 'search' });
|
|
115
|
+
}
|
|
116
|
+
if (features?.graph !== false) {
|
|
117
|
+
result.push({ type: 'custom', route: 'graph', title: 'Graph', icon: 'graph' });
|
|
118
|
+
}
|
|
122
119
|
if (features?.news) {
|
|
123
120
|
result.push({ type: 'news', route: 'news', title: 'News', icon: 'newspaper' });
|
|
124
121
|
}
|
|
125
122
|
return result;
|
|
126
123
|
}
|
|
127
124
|
|
|
125
|
+
function synthesizeDatasetPages(features?: Record<string, unknown>, pages?: PageConfig[]): PageConfig[] {
|
|
126
|
+
const declared = pages?.filter(p => p.datasetScoped) ?? [];
|
|
127
|
+
if (declared.length > 0) return declared;
|
|
128
|
+
|
|
129
|
+
const result: PageConfig[] = [
|
|
130
|
+
{ type: 'custom', route: '', title: 'Concepts', icon: 'list', datasetScoped: true },
|
|
131
|
+
];
|
|
132
|
+
if (features?.stats !== false) {
|
|
133
|
+
result.push({ type: 'stats', route: 'stats', title: 'Statistics', icon: 'chart', datasetScoped: true });
|
|
134
|
+
}
|
|
135
|
+
if (features?.about !== false) {
|
|
136
|
+
result.push({ type: 'about', route: 'about', title: 'About', icon: 'info', datasetScoped: true });
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
export function useSiteConfig() {
|
|
129
142
|
const config = computed(() => siteConfig.value);
|
|
130
143
|
const visibleDatasets = computed(() => siteConfig.value?.datasets ?? []);
|
|
131
144
|
|
|
132
145
|
const globalPages = computed<PageConfig[]>(() =>
|
|
133
|
-
|
|
134
|
-
.filter(p => !p.datasetScoped),
|
|
146
|
+
synthesizeGlobalPages(siteConfig.value?.features, siteConfig.value?.pages),
|
|
135
147
|
);
|
|
136
148
|
|
|
137
|
-
const datasetPages = computed<PageConfig[]>(() =>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return BUILTIN_DATASET_PAGES;
|
|
141
|
-
});
|
|
149
|
+
const datasetPages = computed<PageConfig[]>(() =>
|
|
150
|
+
synthesizeDatasetPages(siteConfig.value?.features, siteConfig.value?.pages),
|
|
151
|
+
);
|
|
142
152
|
|
|
143
153
|
return { config, visibleDatasets, loadConfig, globalPages, datasetPages };
|
|
144
154
|
}
|
package/src/router/index.ts
CHANGED
|
@@ -18,6 +18,18 @@ const routes: RouteRecordRaw[] = [
|
|
|
18
18
|
component: () => import('../views/ConceptView.vue'),
|
|
19
19
|
props: true,
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
path: '/dataset/:registerId/stats',
|
|
23
|
+
name: 'stats',
|
|
24
|
+
component: () => import('../views/StatsView.vue'),
|
|
25
|
+
props: true,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
path: '/dataset/:registerId/about',
|
|
29
|
+
name: 'about',
|
|
30
|
+
component: () => import('../views/AboutView.vue'),
|
|
31
|
+
props: true,
|
|
32
|
+
},
|
|
21
33
|
{
|
|
22
34
|
path: '/search',
|
|
23
35
|
name: 'search',
|
|
@@ -28,6 +40,16 @@ const routes: RouteRecordRaw[] = [
|
|
|
28
40
|
name: 'graph',
|
|
29
41
|
component: () => import('../views/GraphView.vue'),
|
|
30
42
|
},
|
|
43
|
+
{
|
|
44
|
+
path: '/news',
|
|
45
|
+
name: 'news',
|
|
46
|
+
component: () => import('../views/NewsView.vue'),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: '/contributors',
|
|
50
|
+
name: 'contributors',
|
|
51
|
+
component: () => import('../views/ContributorsView.vue'),
|
|
52
|
+
},
|
|
31
53
|
{
|
|
32
54
|
path: '/resolve/:uri(.*)',
|
|
33
55
|
name: 'resolve',
|
|
@@ -1,38 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, onMounted } from 'vue';
|
|
3
2
|
import { useSiteConfig } from '../config/use-site-config';
|
|
4
3
|
|
|
5
4
|
interface Contributor {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
organization
|
|
9
|
-
|
|
10
|
-
email
|
|
11
|
-
uri: string;
|
|
12
|
-
country: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface ContributorsData {
|
|
16
|
-
register: string;
|
|
17
|
-
owner: string;
|
|
18
|
-
manager: string;
|
|
19
|
-
contributors: Contributor[];
|
|
5
|
+
name: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
organization?: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
email?: string;
|
|
20
10
|
}
|
|
21
11
|
|
|
22
12
|
const { config } = useSiteConfig();
|
|
23
|
-
const
|
|
24
|
-
const loading = ref(true);
|
|
25
|
-
const error = ref<string | null>(null);
|
|
26
|
-
|
|
27
|
-
onMounted(async () => {
|
|
28
|
-
try {
|
|
29
|
-
const resp = await fetch('/contributors.json');
|
|
30
|
-
if (resp.ok) data.value = await resp.json();
|
|
31
|
-
} catch (e: any) {
|
|
32
|
-
error.value = e.message;
|
|
33
|
-
}
|
|
34
|
-
loading.value = false;
|
|
35
|
-
});
|
|
13
|
+
const contributors = config.value?.contributors as Contributor[] | undefined;
|
|
36
14
|
</script>
|
|
37
15
|
|
|
38
16
|
<template>
|
|
@@ -44,62 +22,32 @@ onMounted(async () => {
|
|
|
44
22
|
</nav>
|
|
45
23
|
<h1 class="font-serif text-3xl text-ink-800 mb-2">Contributors</h1>
|
|
46
24
|
<p class="text-ink-400 mb-8">
|
|
47
|
-
|
|
25
|
+
People and organizations contributing to {{ config?.branding?.ownerName || config?.title || 'this glossary' }}.
|
|
48
26
|
</p>
|
|
49
27
|
|
|
50
|
-
<template v-if="
|
|
51
|
-
<div class="
|
|
52
|
-
<div class="card p-6 space-y-3">
|
|
53
|
-
<div class="h-4 bg-ink-100 rounded w-40"></div>
|
|
54
|
-
<div class="h-6 bg-ink-100 rounded w-3/4"></div>
|
|
55
|
-
<div class="h-4 bg-ink-100 rounded w-1/2"></div>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</template>
|
|
59
|
-
|
|
60
|
-
<template v-else-if="error">
|
|
61
|
-
<div class="card p-8 text-center">
|
|
62
|
-
<p class="text-ink-500">Failed to load contributors.</p>
|
|
63
|
-
</div>
|
|
64
|
-
</template>
|
|
65
|
-
|
|
66
|
-
<template v-else-if="data">
|
|
67
|
-
<!-- Register metadata -->
|
|
68
|
-
<div v-if="data.owner" class="card p-6 mb-6">
|
|
69
|
-
<h2 class="section-label">Register Information</h2>
|
|
70
|
-
<dl class="space-y-3 mt-3">
|
|
71
|
-
<div v-if="data.owner" class="flex items-start gap-4">
|
|
72
|
-
<dt class="text-ink-400 text-sm w-40 flex-shrink-0 pt-0.5">Owner</dt>
|
|
73
|
-
<dd class="text-ink-800 font-medium">{{ data.owner }}</dd>
|
|
74
|
-
</div>
|
|
75
|
-
<div v-if="data.manager" class="flex items-start gap-4">
|
|
76
|
-
<dt class="text-ink-400 text-sm w-40 flex-shrink-0 pt-0.5">Manager</dt>
|
|
77
|
-
<dd class="text-ink-800">{{ data.manager }}</dd>
|
|
78
|
-
</div>
|
|
79
|
-
</dl>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<!-- Per-language contributors -->
|
|
83
|
-
<div v-if="data.contributors.length" class="card overflow-x-auto">
|
|
28
|
+
<template v-if="contributors?.length">
|
|
29
|
+
<div class="card overflow-x-auto">
|
|
84
30
|
<table class="w-full text-sm">
|
|
85
31
|
<thead>
|
|
86
32
|
<tr class="border-b border-ink-100/60 bg-ink-50/50">
|
|
87
|
-
<th class="text-left px-5 py-3 text-ink-500 font-medium">
|
|
33
|
+
<th class="text-left px-5 py-3 text-ink-500 font-medium">Name</th>
|
|
34
|
+
<th class="text-left px-5 py-3 text-ink-500 font-medium">Role</th>
|
|
88
35
|
<th class="text-left px-5 py-3 text-ink-500 font-medium">Organization</th>
|
|
89
36
|
<th class="text-left px-5 py-3 text-ink-500 font-medium">Contact</th>
|
|
90
37
|
</tr>
|
|
91
38
|
</thead>
|
|
92
39
|
<tbody>
|
|
93
|
-
<tr v-for="c in
|
|
94
|
-
<td class="px-5 py-3">
|
|
95
|
-
<
|
|
96
|
-
<span v-
|
|
40
|
+
<tr v-for="c in contributors" :key="c.name" class="border-b border-ink-50 last:border-0">
|
|
41
|
+
<td class="px-5 py-3 text-ink-800 font-medium">
|
|
42
|
+
<a v-if="c.url" :href="c.url" target="_blank" class="concept-link">{{ c.name }}</a>
|
|
43
|
+
<span v-else>{{ c.name }}</span>
|
|
97
44
|
</td>
|
|
98
|
-
<td class="px-5 py-3 text-ink-
|
|
99
|
-
|
|
100
|
-
|
|
45
|
+
<td class="px-5 py-3 text-ink-600">{{ c.role || '—' }}</td>
|
|
46
|
+
<td class="px-5 py-3 text-ink-700">{{ c.organization || '—' }}</td>
|
|
47
|
+
<td class="px-5 py-3">
|
|
48
|
+
<a v-if="c.email" :href="`mailto:${c.email}`" class="concept-link text-xs">{{ c.email }}</a>
|
|
49
|
+
<span v-else class="text-ink-300">—</span>
|
|
101
50
|
</td>
|
|
102
|
-
<td class="px-5 py-3 text-ink-500 text-xs">{{ c.contact }}</td>
|
|
103
51
|
</tr>
|
|
104
52
|
</tbody>
|
|
105
53
|
</table>
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { RouteRecordRaw } from 'vue-router';
|
|
2
|
-
import type { PageConfig } from '../config/types';
|
|
3
|
-
|
|
4
|
-
const pageComponents: Record<string, () => Promise<any>> = {
|
|
5
|
-
news: () => import('../views/NewsView.vue'),
|
|
6
|
-
contributors: () => import('../views/ContributorsView.vue'),
|
|
7
|
-
about: () => import('../views/AboutView.vue'),
|
|
8
|
-
stats: () => import('../views/StatsView.vue'),
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export function buildPageRoutes(pages: PageConfig[]): RouteRecordRaw[] {
|
|
12
|
-
const routes: RouteRecordRaw[] = [];
|
|
13
|
-
|
|
14
|
-
for (const page of pages) {
|
|
15
|
-
const component = pageComponents[page.type];
|
|
16
|
-
if (!component) continue;
|
|
17
|
-
|
|
18
|
-
if (page.datasetScoped) {
|
|
19
|
-
routes.push({
|
|
20
|
-
path: `/dataset/:registerId/${page.route}`,
|
|
21
|
-
name: page.route,
|
|
22
|
-
component,
|
|
23
|
-
props: true,
|
|
24
|
-
});
|
|
25
|
-
} else {
|
|
26
|
-
routes.push({
|
|
27
|
-
path: `/${page.route}`,
|
|
28
|
-
name: page.route,
|
|
29
|
-
component,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return routes;
|
|
35
|
-
}
|