@glossarist/concept-browser 0.2.6 → 0.2.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glossarist/concept-browser",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
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": {
@@ -90,12 +90,35 @@ function sourcesToJsonLd(sources) {
90
90
  });
91
91
  }
92
92
 
93
- function refsToJsonLd(refs) {
93
+ function refsToJsonLd(refs, refMaps) {
94
94
  if (!refs || !Array.isArray(refs)) return [];
95
- return refs.map(r => ({
96
- '@id': r.id,
97
- 'gl:term': r.term,
98
- })).filter(r => r['@id']);
95
+ return refs.map(r => {
96
+ if (r.id) return { '@id': r.id, 'gl:term': r.term };
97
+ if (r.term && refMaps) {
98
+ const uri = resolveRefUri(r.term, refMaps);
99
+ if (uri) return { '@id': uri, 'gl:term': r.term };
100
+ }
101
+ return { '@id': r.id || r.term, 'gl:term': r.term };
102
+ }).filter(r => r['@id']);
103
+ }
104
+
105
+ function resolveRefUri(term, refMaps) {
106
+ const base = refMaps.uriBase;
107
+ const urnPrefix = 'urn:iso:std:iso:';
108
+ if (term.startsWith(urnPrefix)) {
109
+ const rest = term.slice(urnPrefix.length);
110
+ const match = rest.match(/^(\d+):(.+)$/);
111
+ if (match) {
112
+ const dsId = refMaps.urnStandardMap[match[1]];
113
+ if (dsId) return `${base}/${dsId}/concept/${match[2]}`;
114
+ }
115
+ }
116
+ const ievMatch = term.match(/^IEV:(\d+[-\d]+)$/);
117
+ if (ievMatch) {
118
+ const dsId = refMaps.refPrefixMap['IEV'];
119
+ if (dsId) return `${base}/${dsId}/concept/${ievMatch[1]}`;
120
+ }
121
+ return null;
99
122
  }
100
123
 
101
124
  function buildRefMaps(config) {
@@ -119,12 +142,14 @@ function buildRefMaps(config) {
119
142
  if (xref.refPrefixMap) Object.assign(refPrefixMap, xref.refPrefixMap);
120
143
  if (xref.urnStandardMap) Object.assign(urnStandardMap, xref.urnStandardMap);
121
144
 
122
- return { refPrefixMap, urnStandardMap };
145
+ const uriBase = config.uriBase || `https://${config.domain}`;
146
+ return { refPrefixMap, urnStandardMap, uriBase };
123
147
  }
124
148
 
125
- function extractInlineRefs(localizedData, refPrefixMap, urnStandardMap) {
149
+ function extractInlineRefs(localizedData, refMaps) {
126
150
  const refs = [];
127
151
  const texts = [];
152
+ const { refPrefixMap, urnStandardMap, uriBase } = refMaps;
128
153
 
129
154
  if (localizedData.definition) {
130
155
  const defs = Array.isArray(localizedData.definition) ? localizedData.definition : [localizedData.definition];
@@ -140,17 +165,17 @@ function extractInlineRefs(localizedData, refPrefixMap, urnStandardMap) {
140
165
 
141
166
  for (const m of fullText.matchAll(/\{\{([^,}]+),\s*IEV:([^}]+)\}\}/g)) {
142
167
  const datasetId = refPrefixMap['IEV'];
143
- if (datasetId) refs.push({ id: `https://glossarist.org/${datasetId}/concept/${m[2]}`, term: m[1].trim() });
168
+ if (datasetId) refs.push({ id: `${uriBase}/${datasetId}/concept/${m[2]}`, term: m[1].trim() });
144
169
  }
145
170
 
146
171
  for (const m of fullText.matchAll(/\{urn:iso:std:iso:(\d+):([^,}]+),([^,}]+)(?:,([^}]+))?\}/g)) {
147
172
  const datasetId = urnStandardMap[m[1]];
148
- if (datasetId) refs.push({ id: `https://glossarist.org/${datasetId}/concept/${m[2]}`, term: (m[4] || m[3]).trim() });
173
+ if (datasetId) refs.push({ id: `${uriBase}/${datasetId}/concept/${m[2]}`, term: (m[4] || m[3]).trim() });
149
174
  }
150
175
 
151
176
  for (const m of fullText.matchAll(/\{\{urn:iso:std:iso:(\d+):([^,}]+),([^,}]+)(?:,([^}]+))?\}\}/g)) {
152
177
  const datasetId = urnStandardMap[m[1]];
153
- if (datasetId) refs.push({ id: `https://glossarist.org/${datasetId}/concept/${m[2]}`, term: (m[4] || m[3]).trim() });
178
+ if (datasetId) refs.push({ id: `${uriBase}/${datasetId}/concept/${m[2]}`, term: (m[4] || m[3]).trim() });
154
179
  }
155
180
 
156
181
  const seen = new Set();
@@ -165,9 +190,10 @@ const LANG_CODES = ['eng', 'ara', 'deu', 'fra', 'spa', 'ita', 'jpn', 'kor', 'pol
165
190
 
166
191
  function yamlToJsonLd(conceptYaml, register, refMaps) {
167
192
  const termid = String(conceptYaml.termid);
193
+ const base = refMaps.uriBase;
168
194
  const doc = {
169
195
  '@context': 'https://glossarist.org/ns/context.jsonld',
170
- '@id': `https://glossarist.org/${register}/concept/${termid}`,
196
+ '@id': `${base}/${register}/concept/${termid}`,
171
197
  '@type': 'gl:Concept',
172
198
  'gl:identifier': termid,
173
199
  };
@@ -178,7 +204,7 @@ function yamlToJsonLd(conceptYaml, register, refMaps) {
178
204
  if (!lc) continue;
179
205
 
180
206
  const lDoc = {
181
- '@id': `https://glossarist.org/${register}/concept/${termid}/${lang}`,
207
+ '@id': `${base}/${register}/concept/${termid}/${lang}`,
182
208
  '@type': 'gl:LocalizedConcept',
183
209
  'gl:languageCode': lang,
184
210
  };
@@ -204,11 +230,11 @@ function yamlToJsonLd(conceptYaml, register, refMaps) {
204
230
  }));
205
231
  }
206
232
  if (lc.references && lc.references.length > 0) {
207
- lDoc['gl:references'] = refsToJsonLd(lc.references);
233
+ lDoc['gl:references'] = refsToJsonLd(lc.references, refMaps);
208
234
  } else if (refMaps) {
209
- const inlineRefs = extractInlineRefs(lc, refMaps.refPrefixMap, refMaps.urnStandardMap);
235
+ const inlineRefs = extractInlineRefs(lc, refMaps);
210
236
  if (inlineRefs.length > 0) {
211
- lDoc['gl:references'] = refsToJsonLd(inlineRefs);
237
+ lDoc['gl:references'] = refsToJsonLd(inlineRefs, refMaps);
212
238
  }
213
239
  }
214
240
 
@@ -515,7 +541,7 @@ function processDataset(dir, register, opts) {
515
541
  fs.writeFileSync(
516
542
  path.join(DATA, register, 'graph-nodes.json'),
517
543
  JSON.stringify({
518
- uriPrefix: `https://glossarist.org/${register}/concept/`,
544
+ uriPrefix: `${refMaps.uriBase}/${register}/concept/`,
519
545
  registerId: register,
520
546
  nodes: graphNodeEntries,
521
547
  }),
@@ -583,7 +609,7 @@ function processDataset(dir, register, opts) {
583
609
  conceptUrlTemplate: '{baseUrl}/concepts/{conceptId}.json',
584
610
  indexUrl: '{baseUrl}/index.json',
585
611
  contextUrl: 'https://glossarist.org/ns/context.jsonld',
586
- uriBase: 'https://glossarist.org',
612
+ uriBase: refMaps.uriBase,
587
613
  status: 'valid',
588
614
  schemaVersion: '1.0.0',
589
615
  tags: opts.tags,
@@ -23,26 +23,13 @@ function findConfigFile(args = []) {
23
23
  return resolve(process.env.SITE_CONFIG);
24
24
  }
25
25
 
26
- const siteId = process.env.SITE_ID || args.find(a => !a.startsWith('-')) || null;
27
- if (siteId) {
28
- // Check project configs/ dir first
29
- const p = resolve(projectRoot, 'configs', `${siteId}.yml`);
30
- if (existsSync(p)) return p;
31
- // Check CWD (deployment repo may have configs locally)
32
- for (const name of [`${siteId}.yml`, 'site-config.yml']) {
33
- const cwdP = resolve(process.cwd(), name);
34
- if (existsSync(cwdP)) return cwdP;
35
- }
36
- throw new Error(`Site config not found for '${siteId}'. Checked configs/${siteId}.yml, ${siteId}.yml, site-config.yml in CWD`);
37
- }
38
-
39
26
  // Check CWD first (deployment repos), then project root
40
27
  for (const dir of [process.cwd(), projectRoot]) {
41
28
  const p = resolve(dir, 'site-config.yml');
42
29
  if (existsSync(p)) return p;
43
30
  }
44
31
 
45
- throw new Error('No site config found. Set SITE_CONFIG, SITE_ID, or create site-config.yml');
32
+ throw new Error('No site config found. Set SITE_CONFIG or create site-config.yml');
46
33
  }
47
34
 
48
35
  export function loadSiteConfig(args = []) {
@@ -56,6 +56,7 @@ describe('AdapterFactory', () => {
56
56
  mockFetch.mockReturnValueOnce(mockJsonResponse({
57
57
  id: 'test',
58
58
  datasetUri: 'https://glossarist.org/test/*',
59
+ uriBase: 'https://glossarist.org',
59
60
  title: 'Test',
60
61
  languages: ['eng'],
61
62
  chunkSize: 500,
@@ -106,7 +107,7 @@ describe('AdapterFactory', () => {
106
107
 
107
108
  // Load IEV
108
109
  mockFetch.mockReturnValueOnce(mockJsonResponse({
109
- id: 'iev', datasetUri: 'urn:iec:std:iec:60050:*', title: 'IEV', languages: ['eng'], chunkSize: 500,
110
+ id: 'iev', datasetUri: 'urn:iec:std:iec:60050:*', uriBase: 'https://glossarist.org', title: 'IEV', languages: ['eng'], chunkSize: 500,
110
111
  }));
111
112
  mockFetch.mockReturnValueOnce(mockJsonResponse({
112
113
  registerId: 'iev', conceptCount: 0, chunkSize: 500, chunks: [], concepts: [],
@@ -115,7 +116,7 @@ describe('AdapterFactory', () => {
115
116
 
116
117
  // Load TC 204
117
118
  mockFetch.mockReturnValueOnce(mockJsonResponse({
118
- id: 'isotc204', datasetUri: 'urn:iso:std:iso:14812:*', title: 'TC 204', languages: ['eng'], chunkSize: 500,
119
+ id: 'isotc204', datasetUri: 'urn:iso:std:iso:14812:*', uriBase: 'https://glossarist.org', title: 'TC 204', languages: ['eng'], chunkSize: 500,
119
120
  }));
120
121
  mockFetch.mockReturnValueOnce(mockJsonResponse({
121
122
  registerId: 'isotc204', conceptCount: 0, chunkSize: 500, chunks: [], concepts: [],
@@ -1,10 +1,12 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { UriRouter } from '../adapters/UriRouter';
3
3
 
4
+ const MOCK_MANIFEST = { uriBase: 'https://glossarist.org' } as any;
5
+
4
6
  describe('UriRouter', () => {
5
7
  it('resolves URIs for registered datasets', () => {
6
8
  const router = new UriRouter();
7
- router.registerDataset('iev', '/data/iev');
9
+ router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
8
10
 
9
11
  const resolved = router.resolveUri('https://glossarist.org/iev/concept/103-01-02');
10
12
  expect(resolved).toEqual({ registerId: 'iev', conceptId: '103-01-02' });
@@ -12,7 +14,7 @@ describe('UriRouter', () => {
12
14
 
13
15
  it('resolves URIs with multi-part concept IDs', () => {
14
16
  const router = new UriRouter();
15
- router.registerDataset('isotc204', '/data/isotc204');
17
+ router.registerDataset('isotc204', '/data/isotc204', MOCK_MANIFEST);
16
18
 
17
19
  const resolved = router.resolveUri('https://glossarist.org/isotc204/concept/3.1.1.1');
18
20
  expect(resolved).toEqual({ registerId: 'isotc204', conceptId: '3.1.1.1' });
@@ -20,36 +22,37 @@ describe('UriRouter', () => {
20
22
 
21
23
  it('returns null for unknown register', () => {
22
24
  const router = new UriRouter();
23
- router.registerDataset('iev', '/data/iev');
25
+ router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
24
26
 
25
27
  expect(router.resolveUri('https://glossarist.org/unknown/concept/123')).toBeNull();
26
28
  });
27
29
 
28
30
  it('returns null for non-matching URI pattern', () => {
29
31
  const router = new UriRouter();
30
- router.registerDataset('iev', '/data/iev');
32
+ router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
31
33
 
32
34
  expect(router.resolveUri('https://example.com/other')).toBeNull();
33
35
  });
34
36
 
35
37
  it('builds URIs from register and concept ID', () => {
36
38
  const router = new UriRouter();
39
+ router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
37
40
  expect(router.buildUri('iev', '103-01-02')).toBe('https://glossarist.org/iev/concept/103-01-02');
38
41
  });
39
42
 
40
43
  it('lists all registered IDs', () => {
41
44
  const router = new UriRouter();
42
- router.registerDataset('iev', '/data/iev');
43
- router.registerDataset('isotc211', '/data/isotc211');
45
+ router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
46
+ router.registerDataset('isotc211', '/data/isotc211', MOCK_MANIFEST);
44
47
 
45
48
  expect(router.getRegisteredIds()).toEqual(['iev', 'isotc211']);
46
49
  });
47
50
 
48
51
  it('resolves across multiple registers', () => {
49
52
  const router = new UriRouter();
50
- router.registerDataset('iev', '/data/iev');
51
- router.registerDataset('isotc211', '/data/isotc211');
52
- router.registerDataset('isotc204', '/data/isotc204');
53
+ router.registerDataset('iev', '/data/iev', MOCK_MANIFEST);
54
+ router.registerDataset('isotc211', '/data/isotc211', MOCK_MANIFEST);
55
+ router.registerDataset('isotc204', '/data/isotc204', MOCK_MANIFEST);
53
56
 
54
57
  expect(router.resolveUri('https://glossarist.org/iev/concept/102-01-01')?.registerId).toBe('iev');
55
58
  expect(router.resolveUri('https://glossarist.org/isotc211/concept/10')?.registerId).toBe('isotc211');
@@ -9,7 +9,7 @@ export class UriRouter {
9
9
  this.registerMap.set(registerId, {
10
10
  baseUrl,
11
11
  manifest: manifest ?? null,
12
- uriBase: manifest?.uriBase ?? 'https://glossarist.org',
12
+ uriBase: manifest?.uriBase ?? '',
13
13
  });
14
14
  }
15
15
 
@@ -31,7 +31,7 @@ export class UriRouter {
31
31
 
32
32
  buildUri(registerId: string, conceptId: string): string {
33
33
  const info = this.registerMap.get(registerId);
34
- const uriBase = info?.uriBase ?? 'https://glossarist.org';
34
+ const uriBase = info?.uriBase ?? '';
35
35
  return `${uriBase}/${registerId}/concept/${conceptId}`;
36
36
  }
37
37
 
@@ -50,8 +50,8 @@ export class AdapterFactory {
50
50
  const uriPatterns = [
51
51
  manifest.datasetUri,
52
52
  ...(manifest.uriAliases ?? []),
53
- `https://glossarist.org/${registerId}/*`,
54
- ];
53
+ manifest.uriBase ? `${manifest.uriBase}/${registerId}/*` : undefined,
54
+ ].filter(Boolean) as string[];
55
55
  this.resolver.registerDataset(registerId, uriPatterns);
56
56
 
57
57
  return adapter;
@@ -130,7 +130,7 @@ function isActive(page: { route: string; datasetScoped?: boolean }): boolean {
130
130
  <div class="text-[11px] text-ink-300">
131
131
  Built with the
132
132
  <a
133
- :href="(siteConfig?.features?.poweredBy as any)?.url || 'https://glossarist.org'"
133
+ :href="(siteConfig?.features?.poweredBy as any)?.url || 'https://github.com/glossarist/concept-browser'"
134
134
  target="_blank"
135
135
  rel="noopener"
136
136
  class="concept-link"
@@ -137,6 +137,7 @@ export interface PageConfig {
137
137
  export interface SiteConfig {
138
138
  id: string;
139
139
  domain: string;
140
+ uriBase?: string;
140
141
  title: string;
141
142
  subtitle?: string;
142
143
  description?: string;
@@ -4,6 +4,7 @@ import type { PageConfig } from './types';
4
4
  export interface RuntimeSiteConfig {
5
5
  id: string;
6
6
  domain: string;
7
+ uriBase?: string;
7
8
  title: string;
8
9
  subtitle?: string;
9
10
  description?: string;