@glossarist/concept-browser 0.1.7 → 0.1.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.1.7",
3
+ "version": "0.1.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": {
@@ -373,6 +373,30 @@ function processDataset(dir, register, opts) {
373
373
  };
374
374
  }
375
375
 
376
+ // Copy bulk format files from compiled/ directory (full GCR)
377
+ const compiledDir = path.join(ROOT, '.datasets', register, 'compiled');
378
+ const bulkFormats = [];
379
+ if (fs.existsSync(compiledDir)) {
380
+ for (const file of fs.readdirSync(compiledDir)) {
381
+ const src = path.join(compiledDir, file);
382
+ const dest = path.join(DATA, register, file);
383
+ fs.copyFileSync(src, dest);
384
+ const ext = path.extname(file);
385
+ const formatMap = {
386
+ '.ttl': 'turtle',
387
+ '.jsonld': 'jsonld',
388
+ '.xml': 'tbx',
389
+ '.jsonl': 'jsonl',
390
+ '.yaml': 'yaml',
391
+ };
392
+ const formatName = formatMap[ext] || ext.slice(1);
393
+ bulkFormats.push({ file, format: formatName, size: fs.statSync(src).size });
394
+ }
395
+ if (bulkFormats.length) {
396
+ console.log(` Copied ${bulkFormats.length} bulk format files`);
397
+ }
398
+ }
399
+
376
400
  const manifest = {
377
401
  id: register,
378
402
  datasetUri: opts.datasetUri,
@@ -396,6 +420,7 @@ function processDataset(dir, register, opts) {
396
420
  color: opts.color,
397
421
  languageStats: langStats,
398
422
  availableFormats,
423
+ bulkFormats,
399
424
  };
400
425
  if (opts.languageOrder) manifest.languageOrder = opts.languageOrder;
401
426
  writeJson(path.join(DATA, register, 'manifest.json'), manifest);
@@ -25,6 +25,7 @@ export interface Manifest {
25
25
  languageOrder?: string[];
26
26
  languageStats?: Record<string, { terms: number; definitions: number }>;
27
27
  availableFormats?: string[];
28
+ bulkFormats?: { file: string; format: string; size: number }[];
28
29
  }
29
30
 
30
31
  export interface ConceptIndex {
@@ -90,8 +90,25 @@ export interface DatasetConfig {
90
90
  color?: string;
91
91
  tags?: string[];
92
92
  languageOrder?: string[];
93
+ downloads?: string[];
93
94
  }
94
95
 
96
+ // === Downloads ===
97
+
98
+ export interface BulkFormatInfo {
99
+ file: string;
100
+ format: string;
101
+ size: number;
102
+ }
103
+
104
+ export const FORMAT_LABELS: Record<string, string> = {
105
+ turtle: 'Turtle (RDF)',
106
+ jsonld: 'JSON-LD (SKOS)',
107
+ tbx: 'TBX-XML',
108
+ jsonl: 'JSONL',
109
+ yaml: 'YAML',
110
+ };
111
+
95
112
  // === Pages ===
96
113
 
97
114
  export type PageType = 'news' | 'contributors' | 'about' | 'stats' | 'custom';
@@ -3,6 +3,7 @@ import { computed, ref, watch, onMounted, onUnmounted } from 'vue';
3
3
  import { useVocabularyStore } from '../stores/vocabulary';
4
4
  import { useDsStyle } from '../utils/dataset-style';
5
5
  import { useDatasetLoader } from '../composables/use-dataset-loader';
6
+ import { FORMAT_LABELS } from '../config/types';
6
7
  import ConceptCard from '../components/ConceptCard.vue';
7
8
 
8
9
  const props = defineProps<{ registerId: string }>();
@@ -15,6 +16,23 @@ const manifest = computed(() => store.manifests.get(props.registerId));
15
16
  const adapter = computed(() => store.datasets.get(props.registerId));
16
17
  const chunkLoading = ref(false);
17
18
 
19
+ function formatSize(bytes: number): string {
20
+ if (bytes < 1024) return `${bytes} B`;
21
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
22
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
23
+ }
24
+
25
+ const bulkDownloads = computed(() => {
26
+ const m = manifest.value;
27
+ if (!m?.bulkFormats?.length) return [];
28
+ return m.bulkFormats.map(f => ({
29
+ ...f,
30
+ url: `${m.baseUrl}/${f.file}`,
31
+ label: FORMAT_LABELS[f.format] || f.format.toUpperCase(),
32
+ sizeLabel: formatSize(f.size),
33
+ }));
34
+ });
35
+
18
36
  const totalConceptCount = computed(() => adapter.value?.getConceptCount() ?? 0);
19
37
 
20
38
  const filter = ref('');
@@ -145,6 +163,26 @@ function goToPage(p: number) {
145
163
  </div>
146
164
  </div>
147
165
 
166
+ <!-- Downloads -->
167
+ <div v-if="bulkDownloads.length" class="card p-4 mb-6">
168
+ <h3 class="text-xs font-semibold text-ink-400 uppercase tracking-wide mb-3">Download</h3>
169
+ <div class="flex flex-wrap gap-2">
170
+ <a
171
+ v-for="dl in bulkDownloads"
172
+ :key="dl.file"
173
+ :href="dl.url"
174
+ download
175
+ class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-ink-100 bg-surface-raised text-sm font-medium text-ink-700 hover:bg-ink-50 hover:border-ink-200 transition-colors"
176
+ >
177
+ <svg class="w-3.5 h-3.5 text-ink-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
178
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
179
+ </svg>
180
+ {{ dl.label }}
181
+ <span class="text-ink-300 text-xs">{{ dl.sizeLabel }}</span>
182
+ </a>
183
+ </div>
184
+ </div>
185
+
148
186
  <!-- Loading state (initial dataset load) -->
149
187
  <div v-if="loading || (!adapter?.index && !localError)" class="space-y-4 py-4">
150
188
  <div class="space-y-2">