@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.
- package/README.md +319 -0
- package/cli/index.mjs +119 -0
- package/env.d.ts +7 -0
- package/index.html +16 -0
- package/package.json +78 -0
- package/postcss.config.js +6 -0
- package/scripts/build-edges.js +112 -0
- package/scripts/fetch-datasets.mjs +195 -0
- package/scripts/generate-404.js +15 -0
- package/scripts/generate-data.mjs +606 -0
- package/scripts/load-site-config.mjs +56 -0
- package/src/App.vue +98 -0
- package/src/__tests__/data-integration.test.ts +135 -0
- package/src/__tests__/data-integrity.test.ts +101 -0
- package/src/__tests__/dataset-adapter.test.ts +336 -0
- package/src/__tests__/dataset-style.test.ts +37 -0
- package/src/__tests__/graph.test.ts +187 -0
- package/src/__tests__/lang.test.ts +29 -0
- package/src/__tests__/math.test.ts +113 -0
- package/src/__tests__/reference-resolver.test.ts +122 -0
- package/src/__tests__/site-config.test.ts +52 -0
- package/src/__tests__/uri-router.test.ts +76 -0
- package/src/adapters/DatasetAdapter.ts +270 -0
- package/src/adapters/ReferenceResolver.ts +95 -0
- package/src/adapters/UriRouter.ts +41 -0
- package/src/adapters/factory.ts +78 -0
- package/src/adapters/types.ts +162 -0
- package/src/components/AppHeader.vue +99 -0
- package/src/components/AppSidebar.vue +133 -0
- package/src/components/ConceptCard.vue +65 -0
- package/src/components/ConceptDetail.vue +540 -0
- package/src/components/ConceptTimeline.vue +410 -0
- package/src/components/FormatDownloads.vue +46 -0
- package/src/components/GraphPanel.vue +499 -0
- package/src/components/LanguageDetail.vue +211 -0
- package/src/components/NavIcon.vue +20 -0
- package/src/components/SearchBar.vue +241 -0
- package/src/composables/use-dataset-loader.ts +27 -0
- package/src/config/types.ts +130 -0
- package/src/config/use-site-config.ts +144 -0
- package/src/graph/GraphEngine.ts +137 -0
- package/src/graph/index.ts +1 -0
- package/src/main.ts +11 -0
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/router/index.ts +43 -0
- package/src/router/page-routes.ts +35 -0
- package/src/stores/ui.ts +59 -0
- package/src/stores/vocabulary.ts +309 -0
- package/src/style.css +314 -0
- package/src/utils/asciidoc-lite.ts +123 -0
- package/src/utils/concept-formats.ts +157 -0
- package/src/utils/dataset-style.ts +54 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/lang.ts +32 -0
- package/src/utils/math.ts +100 -0
- package/src/views/AboutView.vue +122 -0
- package/src/views/ConceptView.vue +119 -0
- package/src/views/ContributorsView.vue +110 -0
- package/src/views/DatasetView.vue +249 -0
- package/src/views/GraphView.vue +65 -0
- package/src/views/HomeView.vue +168 -0
- package/src/views/NewsView.vue +146 -0
- package/src/views/ResolveView.vue +63 -0
- package/src/views/SearchView.vue +33 -0
- package/src/views/StatsView.vue +121 -0
- package/tailwind.config.js +43 -0
- package/tsconfig.json +24 -0
- 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
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,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.');
|