@glossarist/concept-browser 0.1.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 (68) hide show
  1. package/README.md +319 -0
  2. package/cli/index.mjs +119 -0
  3. package/env.d.ts +7 -0
  4. package/index.html +16 -0
  5. package/package.json +78 -0
  6. package/postcss.config.js +6 -0
  7. package/scripts/build-edges.js +112 -0
  8. package/scripts/fetch-datasets.mjs +195 -0
  9. package/scripts/generate-404.js +15 -0
  10. package/scripts/generate-data.mjs +606 -0
  11. package/scripts/load-site-config.mjs +56 -0
  12. package/src/App.vue +98 -0
  13. package/src/__tests__/data-integration.test.ts +135 -0
  14. package/src/__tests__/data-integrity.test.ts +101 -0
  15. package/src/__tests__/dataset-adapter.test.ts +336 -0
  16. package/src/__tests__/dataset-style.test.ts +37 -0
  17. package/src/__tests__/graph.test.ts +187 -0
  18. package/src/__tests__/lang.test.ts +29 -0
  19. package/src/__tests__/math.test.ts +113 -0
  20. package/src/__tests__/reference-resolver.test.ts +122 -0
  21. package/src/__tests__/site-config.test.ts +52 -0
  22. package/src/__tests__/uri-router.test.ts +76 -0
  23. package/src/adapters/DatasetAdapter.ts +270 -0
  24. package/src/adapters/ReferenceResolver.ts +95 -0
  25. package/src/adapters/UriRouter.ts +41 -0
  26. package/src/adapters/factory.ts +78 -0
  27. package/src/adapters/types.ts +162 -0
  28. package/src/components/AppHeader.vue +99 -0
  29. package/src/components/AppSidebar.vue +133 -0
  30. package/src/components/ConceptCard.vue +65 -0
  31. package/src/components/ConceptDetail.vue +540 -0
  32. package/src/components/ConceptTimeline.vue +410 -0
  33. package/src/components/FormatDownloads.vue +46 -0
  34. package/src/components/GraphPanel.vue +499 -0
  35. package/src/components/LanguageDetail.vue +211 -0
  36. package/src/components/NavIcon.vue +20 -0
  37. package/src/components/SearchBar.vue +241 -0
  38. package/src/composables/use-dataset-loader.ts +27 -0
  39. package/src/config/types.ts +130 -0
  40. package/src/config/use-site-config.ts +144 -0
  41. package/src/graph/GraphEngine.ts +137 -0
  42. package/src/graph/index.ts +1 -0
  43. package/src/main.ts +11 -0
  44. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  45. package/src/router/index.ts +43 -0
  46. package/src/router/page-routes.ts +35 -0
  47. package/src/stores/ui.ts +59 -0
  48. package/src/stores/vocabulary.ts +309 -0
  49. package/src/style.css +314 -0
  50. package/src/utils/asciidoc-lite.ts +123 -0
  51. package/src/utils/concept-formats.ts +157 -0
  52. package/src/utils/dataset-style.ts +54 -0
  53. package/src/utils/index.ts +1 -0
  54. package/src/utils/lang.ts +32 -0
  55. package/src/utils/math.ts +100 -0
  56. package/src/views/AboutView.vue +122 -0
  57. package/src/views/ConceptView.vue +119 -0
  58. package/src/views/ContributorsView.vue +110 -0
  59. package/src/views/DatasetView.vue +249 -0
  60. package/src/views/GraphView.vue +65 -0
  61. package/src/views/HomeView.vue +168 -0
  62. package/src/views/NewsView.vue +146 -0
  63. package/src/views/ResolveView.vue +63 -0
  64. package/src/views/SearchView.vue +33 -0
  65. package/src/views/StatsView.vue +121 -0
  66. package/tailwind.config.js +43 -0
  67. package/tsconfig.json +24 -0
  68. package/vite.config.ts +27 -0
package/README.md ADDED
@@ -0,0 +1,319 @@
1
+ # Glossarist Vocabulary Browser
2
+
3
+ A statically deployable single-page application for browsing ISO/IEC terminology datasets. Built with Vue 3, TypeScript, and Tailwind CSS. Add new datasets with zero code changes — just edit `datasets.yml`.
4
+
5
+ **Live site:** <https://www.geolexica.org>
6
+
7
+ Glossarist is the software; deployment to `www.geolexica.org` happens through the [geolexica.org](https://github.com/geolexica/geolexica.org) repository, which sources the built SPA and deploys via S3 + CloudFront.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - **Multi-dataset browsing** — Concepts from multiple terminology registers in one place
14
+ - **Full multilingual support** — Definitions, notes, and examples in all available languages
15
+ - **Concept history timeline** — Review dates, decisions, and change notes per language
16
+ - **Cross-reference graph** — D3 force-directed graph showing concept relationships with dataset filtering
17
+ - **Math rendering** — KaTeX rendering for AsciiMath notation in definitions (`stem:[...]`)
18
+ - **Responsive design** — Mobile-first layout with integrated navigation
19
+ - **Static deployment** — No server required. Deploy to any static host
20
+
21
+ ---
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ npm install
27
+ npm run dev
28
+ # Open http://localhost:5173
29
+ ```
30
+
31
+ The dev server serves pre-built data from `public/data/`. If no data is present yet, run the data pipeline first (see below).
32
+
33
+ ---
34
+
35
+ ## Commands
36
+
37
+ | Command | Description |
38
+ |---------|-------------|
39
+ | `npm run dev` | Start Vite dev server on port 5173 |
40
+ | `npm run build` | Type-check (`vue-tsc`) + Vite build + generate `404.html` |
41
+ | `npm run preview` | Preview production build locally |
42
+ | `npm test` | Run tests (Vitest with happy-dom) |
43
+ | `npm run test:watch` | Run tests in watch mode |
44
+ | `npm run fetch-datasets` | Clone/update source repos, harmonize concepts to canonical YAML |
45
+ | `npm run generate-data` | Convert harmonized YAML → JSON-LD static files |
46
+ | `npm run build:full` | Full pipeline: fetch + generate + build-edges + build |
47
+
48
+ ---
49
+
50
+ ## Data Pipeline
51
+
52
+ ```
53
+ datasets.yml
54
+ └─> scripts/fetch-datasets.mjs (clone + harmonize)
55
+ └─> .datasets/{id}/concepts/*.yaml
56
+ └─> scripts/generate-data.mjs (YAML → JSON-LD)
57
+ └─> public/data/{id}/
58
+ ├── manifest.json Dataset metadata
59
+ ├── index.json Concept listing (chunked for large sets)
60
+ ├── edges.json Pre-computed cross-references
61
+ └── concepts/*.json Individual concept documents
62
+ └─> scripts/build-edges.js (extract graph edges)
63
+ ```
64
+
65
+ ### Step-by-step
66
+
67
+ ```bash
68
+ # 1. Fetch source repos and harmonize concepts
69
+ npm run fetch-datasets
70
+
71
+ # 2. Generate static JSON-LD data files
72
+ npm run generate-data
73
+
74
+ # 3. Pre-compute cross-reference edges
75
+ node scripts/build-edges.js
76
+
77
+ # 4. Build the SPA
78
+ npm run build
79
+ ```
80
+
81
+ Or use the single-command pipeline:
82
+
83
+ ```bash
84
+ npm run build:full
85
+ ```
86
+
87
+ ### Local dataset override
88
+
89
+ To use a local checkout instead of cloning from GitHub:
90
+
91
+ ```bash
92
+ DATASET_SOURCE_IEV=/path/to/local/iev-data npm run fetch-datasets
93
+ ```
94
+
95
+ Replace `IEV` with the uppercase dataset ID.
96
+
97
+ ---
98
+
99
+ ## Configuration: `datasets.yml`
100
+
101
+ All dataset configuration lives in a single file. Adding a new dataset requires **zero code changes** — just add an entry to `datasets.yml` and run the pipeline.
102
+
103
+ ### Full reference
104
+
105
+ ```yaml
106
+ datasets:
107
+ - id: my-dataset # required — URL-safe identifier
108
+ sourceRepo: https://github.com/org/repo # required — Git repo with concept YAML
109
+ # OR use a pre-built GCR package:
110
+ gcrPackage: https://github.com/org/repo/releases/latest/download/pkg.gcr
111
+
112
+ title: "My Glossary" # optional — falls back to register.yaml name
113
+ description: "Description of dataset" # optional — shown on home and about pages
114
+ owner: My Organization # optional — shown in badges and about page
115
+ color: "#6366f1" # optional — hex color for UI accent (default: auto-assigned)
116
+ tags: [tag1, tag2] # optional — shown on dataset card
117
+ existingSiteUrl: https://example.org # optional — link to existing site for this dataset
118
+ externalConceptUrlTemplate: "https://example.org/concepts/{conceptId}/" # optional — per-concept external link
119
+ languageOrder: [eng, fra, deu, spa] # optional — custom language tab ordering
120
+ ```
121
+
122
+ ### Field details
123
+
124
+ | Field | Required | Description |
125
+ |-------|----------|-------------|
126
+ | `id` | yes | URL-safe identifier used in routes (`/dataset/{id}/concept/...`) and data paths (`public/data/{id}/`) |
127
+ | `sourceRepo` | yes* | Git repository URL containing concept YAML files in `concepts/` directory. `gcrPackage` is an alternative. |
128
+ | `gcrPackage` | no | URL to a pre-built `.gcr` ZIP archive. Used instead of `sourceRepo` when available. See `docs/gcr-spec.md`. |
129
+ | `title` | no | Display name. Falls back to `name` field in the repo's `register.yaml`. |
130
+ | `description` | no | Shown on the home page dataset card and the about page. |
131
+ | `owner` | no | Organization name shown in concept badges and the about page. |
132
+ | `color` | no | Hex color (`#RRGGBB`) for dataset accent. Used for graph nodes, sidebar highlights, and card borders. Auto-assigned if omitted. |
133
+ | `tags` | no | Array of short labels shown on the dataset card. |
134
+ | `existingSiteUrl` | no | Link to the dataset's existing website, shown as a badge on the dataset page. |
135
+ | `externalConceptUrlTemplate` | no | URL template for linking to the official source of each concept. `{conceptId}` is replaced with the concept ID. |
136
+ | `languageOrder` | no | Array of ISO 639-2 language codes controlling the display order on concept pages. Without this, languages default to English-first then alphabetical. |
137
+
138
+ ### Cross-reference mapping
139
+
140
+ The top-level `crossReferences` section maps inline references to dataset IDs:
141
+
142
+ ```yaml
143
+ crossReferences:
144
+ refPrefixMap:
145
+ IEV: iev # {{term, IEV:xxx}} → glossarist.org/iev/concept/xxx
146
+ urnStandardMap:
147
+ "14812": isotc204 # urn:iso:std:iso:14812:... → isotc204
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Adding a New Dataset
153
+
154
+ 1. Add an entry to `datasets.yml` (see configuration above)
155
+ 2. Run `npm run fetch-datasets && npm run generate-data && node scripts/build-edges.js`
156
+ 3. Verify with `npm run dev`
157
+ 4. Commit and push
158
+
159
+ For the full guide, see [Adding a Dataset](docs/adding-a-dataset.md).
160
+
161
+ ### Source repository requirements
162
+
163
+ The source repository must contain:
164
+
165
+ - `concepts/` directory with YAML concept files (one per concept)
166
+ - Optionally `register.yaml` with dataset metadata
167
+
168
+ Concepts must conform to the [canonical format](docs/dataset-schema.md). The harmonization step in `fetch-datasets` normalizes common variants automatically.
169
+
170
+ ---
171
+
172
+ ## Included Datasets
173
+
174
+ | Dataset | Concepts | Languages | Description |
175
+ |---------|----------|-----------|-------------|
176
+ | IEC Electropedia (IEV) | 22,228 | 17 | World's most comprehensive electrotechnical terminology database |
177
+ | ISO/TC 211 Multi-Lingual Glossary | 1,302 | 5+ | Geographic information terminology |
178
+ | ISO/TC 204 ITS Vocabulary | 312 | 1 | Intelligent transport systems terminology |
179
+ | OSGeo Lexicon | 444 | 1 | Open Source Geospatial Foundation glossary |
180
+
181
+ ---
182
+
183
+ ## Deployment
184
+
185
+ ### Architecture
186
+
187
+ ```
188
+ glossarist/vocabulary-browser geolexica/geolexica.org
189
+ (Glossarist software) (Deployment target)
190
+ ───────────────────── ──────────────────────
191
+ Push to main Push to main / repository_dispatch
192
+ │ │
193
+ ├─ .github/workflows/deploy.yml │
194
+ │ fetch + generate + build │
195
+ │ → deploys to GitHub Pages (preview)│
196
+ │ → triggers geolexica.org dispatch │
197
+ │ │
198
+ └──── repository_dispatch ─────────> build_deploy.yml
199
+ checkout vocabulary-browser
200
+ fetch + generate + build
201
+ → GitHub Pages → www.geolexica.org
202
+ ```
203
+
204
+ The vocabulary-browser repository is the **Glossarist software**. The [geolexica.org](https://github.com/geolexica/geolexica.org) repository is the **deployment target** — its workflow checks out vocabulary-browser, builds it, and deploys to GitHub Pages at `www.geolexica.org`.
205
+
206
+ ### Production deployment (www.geolexica.org)
207
+
208
+ Deployments are managed by the `geolexica.org` repository's `build_deploy.yml` workflow:
209
+
210
+ 1. Checks out `glossarist/vocabulary-browser` at `main`
211
+ 2. Installs dependencies (`npm ci`)
212
+ 3. Fetches datasets and generates data
213
+ 4. Builds the SPA
214
+ 5. Deploys `dist/` to GitHub Pages
215
+
216
+ The workflow triggers on:
217
+ - Push to `main` in the geolexica.org repo
218
+ - `repository_dispatch` from vocabulary-browser (automatic when vocabulary-browser pushes to main)
219
+ - Manual trigger via the "Run workflow" button
220
+
221
+ See [geolexica/geolexica.org](https://github.com/geolexica/geolexica.org) for Pages configuration.
222
+
223
+ ### This repository's build workflow
224
+
225
+ `.github/workflows/deploy.yml` runs on push to `main`:
226
+
227
+ 1. Checks out the code
228
+ 2. Runs `npm ci`
229
+ 3. Fetches datasets (`npm run fetch-datasets`)
230
+ 4. Builds GCR packages (`npm run build-gcr:all`)
231
+ 5. Generates data (`npm run generate-data`)
232
+ 6. Extracts edges (`node scripts/build-edges.js`)
233
+ 7. Builds the SPA (`npm run build`)
234
+ 8. Deploys to GitHub Pages (preview at the repository's Pages URL)
235
+ 9. Triggers `geolexica.org` deployment via `repository_dispatch`
236
+
237
+ ### Custom base path
238
+
239
+ By default the app deploys to the root (`/`). To deploy to a subdirectory (e.g., `/vocab/`):
240
+
241
+ ```bash
242
+ BASE_PATH=/vocab/ npm run build
243
+ ```
244
+
245
+ This sets the Vite `base` config so all asset paths are prefixed correctly.
246
+
247
+ ### Other hosting platforms
248
+
249
+ The build produces static files in `dist/` with an SPA `404.html` fallback. Deploy `dist/` to any static host:
250
+
251
+ - **Netlify:** Set build command to `npm run build:full`, publish directory to `dist`, add `_redirects` file with `/* /index.html 200`
252
+ - **Vercel:** Set framework to Vite, build command to `npm run build:full`, output directory to `dist`
253
+ - **AWS S3 + CloudFront:** Upload `dist/` to S3, set error document to `index.html`, configure CloudFront for SPA routing
254
+ - **GitHub Pages:** Set **Settings → Pages → Source** to "GitHub Actions", then push to `main`
255
+ - **Any static host:** Upload `dist/` and configure all 404s to serve `index.html`
256
+
257
+ ---
258
+
259
+ ## Architecture
260
+
261
+ See [Architecture Documentation](docs/architecture.md) for:
262
+ - System architecture diagrams
263
+ - Component hierarchy
264
+ - Data pipeline details
265
+ - Adapter pattern and graph engine internals
266
+
267
+ ### Tech Stack
268
+
269
+ - **Vue 3** + **TypeScript** + **Vite**
270
+ - **Pinia** (state management)
271
+ - **Vue Router** (SPA navigation)
272
+ - **Tailwind CSS 3** (utility-first styling)
273
+ - **D3.js** (force-directed graph)
274
+ - **KaTeX** (math rendering)
275
+ - **DM Serif Display** + **DM Sans** + **JetBrains Mono** (typography)
276
+
277
+ ### Project structure
278
+
279
+ ```
280
+ src/
281
+ ├── adapters/ Data access layer (DatasetAdapter, AdapterFactory, UriRouter)
282
+ ├── components/ Reusable Vue components (ConceptDetail, GraphPanel, SearchBar, etc.)
283
+ ├── graph/ Graph engine for concept relationships (GraphEngine.ts)
284
+ ├── stores/ Pinia stores (vocabulary, ui)
285
+ ├── views/ Page-level components (HomeView, DatasetView, ConceptView, etc.)
286
+ ├── utils/ Utilities (math rendering, language names, dataset styling)
287
+ └── style.css Global styles and Tailwind layers
288
+
289
+ scripts/
290
+ ├── fetch-datasets.mjs Clone + harmonize source repos
291
+ ├── generate-data.mjs Convert YAML → JSON-LD
292
+ ├── build-edges.js Extract cross-reference edges
293
+ ├── build-gcr.mjs Build GCR packages (optional)
294
+ └── generate-404.js SPA fallback for GitHub Pages
295
+
296
+ docs/
297
+ ├── adding-a-dataset.md Step-by-step guide for adding datasets
298
+ ├── dataset-schema.md Canonical concept YAML format reference
299
+ ├── gcr-spec.md GCR packaging format specification
300
+ └── architecture.md Full architecture documentation
301
+ ```
302
+
303
+ ---
304
+
305
+ ## Testing
306
+
307
+ ```bash
308
+ npm test # Run all tests
309
+ npm run test:watch # Watch mode
310
+ npx vitest run src/__tests__/graph.test.ts # Single test file
311
+ ```
312
+
313
+ Tests use Vitest with happy-dom environment. Vue Test Utils for component tests.
314
+
315
+ ---
316
+
317
+ ## License
318
+
319
+ This project is part of the [Glossarist](https://github.com/glossarist) ecosystem.
package/cli/index.mjs ADDED
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Glossarist Concept Browser CLI.
5
+ *
6
+ * Commands:
7
+ * fetch Fetch/update datasets (from GCR packages or source repos)
8
+ * generate Convert harmonized YAML concepts to JSON-LD static files
9
+ * edges Build cross-reference edges from generated concept data
10
+ * build Full pipeline: fetch + generate + edges + vite build
11
+ * site Same as build (alias)
12
+ *
13
+ * Options:
14
+ * --site <id> Site config to use (looks for site-config.yml in CWD)
15
+ *
16
+ * Environment:
17
+ * SITE_CONFIG Path to site config file (overrides --site)
18
+ * SITE_ID Site config ID (overrides --site)
19
+ * GITHUB_TOKEN GitHub token for private repos
20
+ * DATASET_SOURCE_{ID} Override dataset source with local path
21
+ */
22
+
23
+ import { loadSiteConfig } from '../scripts/load-site-config.mjs';
24
+ import { execSync } from 'child_process';
25
+ import { resolve, dirname } from 'path';
26
+ import { fileURLToPath } from 'url';
27
+
28
+ const __dirname = dirname(fileURLToPath(import.meta.url));
29
+ const pkgRoot = resolve(__dirname, '..');
30
+
31
+ const commands = {
32
+ fetch: () => import('../scripts/fetch-datasets.mjs'),
33
+ generate: () => import('../scripts/generate-data.mjs'),
34
+ edges: () => import('../scripts/build-edges.js'),
35
+ };
36
+
37
+ function parseArgs(argv) {
38
+ const positional = [];
39
+ const named = {};
40
+ for (let i = 0; i < argv.length; i++) {
41
+ if (argv[i].startsWith('--') && i + 1 < argv.length) {
42
+ named[argv[i].slice(2)] = argv[i + 1];
43
+ i++;
44
+ } else if (!argv[i].startsWith('-')) {
45
+ positional.push(argv[i]);
46
+ }
47
+ }
48
+ return { positional, named };
49
+ }
50
+
51
+ async function main() {
52
+ const args = process.argv.slice(2);
53
+ const { positional, named } = parseArgs(args);
54
+ const cmd = positional[0];
55
+
56
+ if (!cmd || cmd === 'help' || cmd === '--help') {
57
+ console.log(`concept-browser <command> [options]
58
+
59
+ Commands:
60
+ fetch Fetch/update datasets from GCR packages or source repos
61
+ generate Convert YAML concepts to JSON-LD static data
62
+ edges Build cross-reference edges from generated concepts
63
+ build Full pipeline (fetch + generate + edges + vite build)
64
+ site Same as build
65
+
66
+ Options:
67
+ --site <id> Site config ID (looks for site-config.yml in CWD)
68
+
69
+ Environment:
70
+ SITE_CONFIG Site config file path (highest priority)
71
+ SITE_ID Site config ID (same as --site)
72
+ GITHUB_TOKEN GitHub token for private repos
73
+ DATASET_SOURCE_{ID} Override dataset source with local path`);
74
+ process.exit(cmd ? 0 : 1);
75
+ }
76
+
77
+ // Pre-load site config so scripts can use it
78
+ if (!process.env.SITE_CONFIG && !process.env.SITE_ID && named.site) {
79
+ process.env.SITE_ID = named.site;
80
+ }
81
+ loadSiteConfig(named.site ? [named.site] : []);
82
+
83
+ if (cmd === 'build' || cmd === 'site') {
84
+ for (const step of ['fetch', 'generate', 'edges']) {
85
+ console.log(`\n=== ${step.toUpperCase()} ===\n`);
86
+ await commands[step]();
87
+ }
88
+
89
+ // Run vite build using the package's vite.config.ts
90
+ console.log(`\n=== BUILD SPA ===\n`);
91
+ const viteConfig = resolve(pkgRoot, 'vite.config.ts');
92
+ execSync(`npx vite build --config ${viteConfig}`, {
93
+ stdio: 'inherit',
94
+ env: { ...process.env },
95
+ });
96
+
97
+ // Run postbuild (404 page)
98
+ try {
99
+ const postbuild = resolve(pkgRoot, 'scripts', 'generate-404.js');
100
+ execSync(`node ${postbuild}`, { stdio: 'inherit' });
101
+ } catch {}
102
+
103
+ return;
104
+ }
105
+
106
+ const runner = commands[cmd];
107
+ if (!runner) {
108
+ console.error(`Unknown command: ${cmd}`);
109
+ console.error('Run `concept-browser help` for usage.');
110
+ process.exit(1);
111
+ }
112
+
113
+ await runner();
114
+ }
115
+
116
+ main().catch(e => {
117
+ console.error(e);
118
+ process.exit(1);
119
+ });
package/env.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue'
5
+ const component: DefineComponent<{}, {}, any>
6
+ export default component
7
+ }
package/index.html ADDED
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=DM+Serif+Display:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
+ <title>Glossarist — ISO Terminology Register Browser</title>
11
+ </head>
12
+ <body>
13
+ <div id="app"></div>
14
+ <script type="module" src="/src/main.ts"></script>
15
+ </body>
16
+ </html>
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@glossarist/concept-browser",
3
+ "version": "0.1.0",
4
+ "description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
5
+ "type": "module",
6
+ "bin": {
7
+ "concept-browser": "./cli/index.mjs"
8
+ },
9
+ "scripts": {
10
+ "dev": "vite",
11
+ "build": "vue-tsc --noEmit && vite build",
12
+ "postbuild": "node scripts/generate-404.js",
13
+ "preview": "vite preview",
14
+ "fetch-datasets": "node scripts/fetch-datasets.mjs",
15
+ "generate-data": "node scripts/generate-data.mjs",
16
+ "build:full": "npm run fetch-datasets && npm run generate-data && node scripts/build-edges.js && npm run build",
17
+ "build:site": "concept-browser --site build",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
20
+ },
21
+ "dependencies": {
22
+ "@vitejs/plugin-vue": "^5.2.3",
23
+ "autoprefixer": "^10.4.21",
24
+ "d3": "^7.9.0",
25
+ "glossarist": "^0.1.1",
26
+ "js-yaml": "^4.1.0",
27
+ "katex": "^0.16.45",
28
+ "pinia": "^2.3.1",
29
+ "postcss": "^8.5.3",
30
+ "tailwindcss": "^3.4.17",
31
+ "vite": "^6.3.5",
32
+ "vue": "^3.5.13",
33
+ "vue-router": "^4.5.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/d3": "^7.4.3",
37
+ "@vue/test-utils": "^2.4.8",
38
+ "happy-dom": "^20.9.0",
39
+ "typescript": "~5.7.3",
40
+ "vitest": "^4.1.5",
41
+ "vue-tsc": "^2.2.8"
42
+ },
43
+ "keywords": [
44
+ "glossarist",
45
+ "terminology",
46
+ "vocabulary",
47
+ "iso",
48
+ "iec",
49
+ "geolexica",
50
+ "gcr",
51
+ "concept-browser"
52
+ ],
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/glossarist/concept-browser.git"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/glossarist/concept-browser/issues"
60
+ },
61
+ "engines": {
62
+ "node": ">=20"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "files": [
68
+ "cli",
69
+ "scripts",
70
+ "src",
71
+ "index.html",
72
+ "vite.config.ts",
73
+ "tsconfig.json",
74
+ "tailwind.config.js",
75
+ "postcss.config.js",
76
+ "env.d.ts"
77
+ ]
78
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Pre-computes cross-reference edges for each dataset.
3
+ * Reads all concept JSON files, extracts structured and inline references,
4
+ * and writes edges.json for each dataset.
5
+ *
6
+ * Usage: node scripts/build-edges.js
7
+ */
8
+ import { readFileSync, writeFileSync, readdirSync, existsSync } from 'fs';
9
+ import { join, dirname } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const ROOT = process.cwd();
14
+ const DATA_DIR = join(ROOT, 'public', 'data');
15
+
16
+ function extractEdgesFromConcept(concept, registerId) {
17
+ const edges = [];
18
+ const sourceUri = concept['@id'];
19
+
20
+ for (const [_lang, lc] of Object.entries(concept['gl:localizedConcept'] || {})) {
21
+ // Structured cross-references (gl:references array, pre-computed during data generation)
22
+ if (lc['gl:references']) {
23
+ for (const ref of lc['gl:references']) {
24
+ if (ref['@id'] && ref['@id'] !== sourceUri) {
25
+ edges.push({
26
+ source: sourceUri,
27
+ target: ref['@id'],
28
+ type: 'references',
29
+ label: ref['gl:term'] || undefined,
30
+ register: registerId,
31
+ });
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ return edges;
38
+ }
39
+
40
+ function buildEdgesForDataset(datasetDir, registerId) {
41
+ const conceptsDir = join(datasetDir, 'concepts');
42
+ if (!existsSync(conceptsDir)) {
43
+ console.log(` Skipping ${registerId}: no concepts directory`);
44
+ return;
45
+ }
46
+
47
+ const files = readdirSync(conceptsDir).filter(f => f.endsWith('.json'));
48
+ console.log(` Processing ${files.length} concepts...`);
49
+
50
+ const allEdges = [];
51
+ let processed = 0;
52
+
53
+ for (const file of files) {
54
+ try {
55
+ const data = JSON.parse(readFileSync(join(conceptsDir, file), 'utf-8'));
56
+ const edges = extractEdgesFromConcept(data, registerId);
57
+ allEdges.push(...edges);
58
+ } catch (e) {
59
+ console.error(` Error processing ${file}: ${e.message}`);
60
+ }
61
+ processed++;
62
+ if (processed % 5000 === 0) {
63
+ console.log(` ... ${processed}/${files.length}`);
64
+ }
65
+ }
66
+
67
+ // Deduplicate edges by source+target pair
68
+ const seen = new Set();
69
+ const deduped = [];
70
+ for (const edge of allEdges) {
71
+ const key = `${edge.source}→${edge.target}`;
72
+ if (!seen.has(key)) {
73
+ seen.add(key);
74
+ deduped.push(edge);
75
+ }
76
+ }
77
+
78
+ const output = {
79
+ registerId,
80
+ edgeCount: deduped.length,
81
+ edges: deduped,
82
+ };
83
+
84
+ const outputPath = join(datasetDir, 'edges.json');
85
+ writeFileSync(outputPath, JSON.stringify(output, null, 2));
86
+ console.log(` Written ${deduped.length} edges to edges.json (${(JSON.stringify(output).length / 1024).toFixed(1)} KB)`);
87
+ }
88
+
89
+ // Main
90
+ console.log('Building edge indexes...\n');
91
+
92
+ const datasets = readdirSync(DATA_DIR).filter(f => {
93
+ try {
94
+ return existsSync(join(DATA_DIR, f, 'manifest.json'));
95
+ } catch {
96
+ return false;
97
+ }
98
+ });
99
+
100
+ for (const ds of datasets) {
101
+ const manifestPath = join(DATA_DIR, ds, 'manifest.json');
102
+ try {
103
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
104
+ console.log(`${manifest.title} (${ds}):`);
105
+ buildEdgesForDataset(join(DATA_DIR, ds), ds);
106
+ } catch (e) {
107
+ console.error(`Error reading manifest for ${ds}: ${e.message}`);
108
+ }
109
+ console.log();
110
+ }
111
+
112
+ console.log('Done.');