@glossarist/concept-browser 0.7.48 → 0.7.51

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.7.48",
3
+ "version": "0.7.51",
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": {
@@ -23,8 +23,8 @@
23
23
  * ambiguity with the HTML `<img alt>` attribute.
24
24
  */
25
25
 
26
- import type { Figure, FigureImage, FigureImageFormat, FigureImageRole } from './types';
27
- import { isType, pickField, pickFieldArray, pickFieldRecord, localized } from './prefix';
26
+ import { Figure, FigureImage } from 'glossarist';
27
+ import { isType, pickField, pickFieldArray, localized } from './prefix';
28
28
  import { sourcesFromJsonLd } from './source-bridge';
29
29
 
30
30
  const FORMAT_SET: ReadonlySet<string> = new Set(['svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif']);
@@ -35,18 +35,20 @@ function imageFromJsonLd(raw: Record<string, unknown>): FigureImage | null {
35
35
  const src = pickField<string>(raw, 'src');
36
36
  if (!src) return null;
37
37
  const formatRaw = (pickField<string>(raw, 'format') ?? '').toLowerCase();
38
- const format = (FORMAT_SET.has(formatRaw) ? formatRaw : 'svg') as FigureImageFormat;
38
+ const format = FORMAT_SET.has(formatRaw) ? formatRaw : 'svg';
39
39
  const roleRaw = pickField<string>(raw, 'role');
40
- const role = roleRaw && ROLE_SET.has(roleRaw) ? (roleRaw as FigureImageRole) : undefined;
40
+ const role = roleRaw && ROLE_SET.has(roleRaw) ? roleRaw : undefined;
41
41
  const width = pickField<number>(raw, 'width');
42
42
  const height = pickField<number>(raw, 'height');
43
43
  const scale = pickField<number>(raw, 'scale');
44
- const img: FigureImage = { src, format };
45
- if (role) img.role = role;
46
- if (typeof width === 'number') img.width = width;
47
- if (typeof height === 'number') img.height = height;
48
- if (typeof scale === 'number') img.scale = scale;
49
- return img;
44
+ return new FigureImage({
45
+ src,
46
+ format,
47
+ ...(role !== undefined && { role }),
48
+ ...(typeof width === 'number' && { width }),
49
+ ...(typeof height === 'number' && { height }),
50
+ ...(typeof scale === 'number' && { scale }),
51
+ });
50
52
  }
51
53
 
52
54
  function imagesFromJsonLd(raw: unknown): FigureImage[] {
@@ -85,17 +87,14 @@ export function figureFromJsonLd(doc: Record<string, unknown>): Figure | null {
85
87
  const subfigures = subfiguresFromJsonLd(pickField(doc, 'subfigure'));
86
88
  const sources = sourcesFromJsonLd(pickField(doc, 'source'));
87
89
 
88
- const fig: Figure = { kind: 'figure', id, images };
89
- if (identifier) fig.identifier = identifier;
90
- if (caption) fig.caption = caption;
91
- if (alt) fig.alt = alt;
92
- if (description) fig.description = description;
93
- if (subfigures) fig.subfigures = subfigures;
94
- if (sources.length) fig.sources = sources;
95
-
96
- // pickFieldRecord is unused for figures; keep the import meaningful by
97
- // ensuring no stray image fields leak through (silent no-op).
98
- void pickFieldRecord;
99
-
100
- return fig;
90
+ return new Figure({
91
+ id,
92
+ images,
93
+ ...(identifier && { identifier }),
94
+ ...(caption && { caption }),
95
+ ...(alt && { alt }),
96
+ ...(description && { description }),
97
+ ...(subfigures && { subfigures }),
98
+ ...(sources.length && { sources }),
99
+ });
101
100
  }
@@ -15,7 +15,7 @@
15
15
  * }
16
16
  */
17
17
 
18
- import type { Formula, FormulaNotation } from './types';
18
+ import { Formula } from 'glossarist';
19
19
  import { isType, pickField, localized } from './prefix';
20
20
  import { sourcesFromJsonLd } from './source-bridge';
21
21
 
@@ -31,18 +31,20 @@ export function formulaFromJsonLd(doc: Record<string, unknown>): Formula | null
31
31
  if (!expression) return null;
32
32
 
33
33
  const notationRaw = (pickField<string>(doc, 'notation') ?? '').toLowerCase();
34
- const notation = NOTATION_SET.has(notationRaw) ? (notationRaw as FormulaNotation) : 'latex';
34
+ const notation = NOTATION_SET.has(notationRaw) ? notationRaw : 'latex';
35
35
 
36
36
  const identifier = pickField<string>(doc, 'identifier');
37
37
  const caption = localized(doc, 'caption');
38
38
  const description = localized(doc, 'description');
39
39
  const sources = sourcesFromJsonLd(pickField(doc, 'source'));
40
40
 
41
- const f: Formula = { kind: 'formula', id, expression, notation };
42
- if (identifier) f.identifier = identifier;
43
- if (caption) f.caption = caption;
44
- if (description) f.description = description;
45
- if (sources.length) f.sources = sources;
46
-
47
- return f;
41
+ return new Formula({
42
+ id,
43
+ expression,
44
+ notation,
45
+ ...(identifier && { identifier }),
46
+ ...(caption && { caption }),
47
+ ...(description && { description }),
48
+ ...(sources.length && { sources }),
49
+ });
48
50
  }
@@ -0,0 +1,126 @@
1
+ // Local module augmentation for glossarist 0.4.2.
2
+ //
3
+ // Upstream's published src/models/index.d.ts declares ZERO classes for the
4
+ // non-verbal hierarchy (Figure, Table, Formula, FigureImage, NonVerbalEntity,
5
+ // SharedNonVerbalEntity, NonVerbalReference + subclasses, BibliographyEntry,
6
+ // BibliographyData) plus the localized-string helpers. The top-level
7
+ // index.d.ts re-exports the names, so TypeScript silently resolves every
8
+ // consumer import to `any`.
9
+ //
10
+ // This file declares the runtime shape so consumer code can be type-checked.
11
+ // DELETE this file when upstream ships proper declarations — tracked by
12
+ // PR glossarist/glossarist-js#31 (targets v0.4.3+).
13
+
14
+ import type { ConceptSource, GlossaristModel } from 'glossarist';
15
+
16
+ declare module 'glossarist' {
17
+ class RegistrableModel extends GlossaristModel {
18
+ static register(type: string, cls: typeof RegistrableModel): void;
19
+ static fromData(data: Record<string, unknown>): RegistrableModel;
20
+ }
21
+
22
+ class FigureImage extends GlossaristModel {
23
+ constructor(data?: {
24
+ src?: string | null;
25
+ format?: string | null;
26
+ role?: string | null;
27
+ width?: number | null;
28
+ height?: number | null;
29
+ scale?: number | null;
30
+ });
31
+ readonly src: string | null;
32
+ readonly format: string | null;
33
+ readonly role: string | null;
34
+ readonly width: number | null;
35
+ readonly height: number | null;
36
+ readonly scale: number | null;
37
+ static fromJSON(data: Record<string, unknown>): FigureImage;
38
+ }
39
+
40
+ class NonVerbalEntity extends RegistrableModel {
41
+ constructor(data?: Record<string, unknown>);
42
+ readonly caption: Record<string, string> | null;
43
+ readonly description: Record<string, string> | null;
44
+ readonly alt: Record<string, string> | null;
45
+ readonly sources: ConceptSource[];
46
+ findById(targetId: string): NonVerbalEntity | null;
47
+ allIds(): string[];
48
+ static fromJSON(data: Record<string, unknown>): NonVerbalEntity;
49
+ }
50
+
51
+ class SharedNonVerbalEntity extends NonVerbalEntity {
52
+ constructor(data?: Record<string, unknown>);
53
+ readonly id: string | null;
54
+ readonly identifier: string | null;
55
+ findById(targetId: string): SharedNonVerbalEntity | null;
56
+ allIds(): string[];
57
+ static fromJSON(data: Record<string, unknown>): SharedNonVerbalEntity;
58
+ }
59
+
60
+ class Figure extends SharedNonVerbalEntity {
61
+ constructor(data?: Record<string, unknown>);
62
+ readonly images: FigureImage[];
63
+ readonly subfigures: Figure[];
64
+ findById(targetId: string): Figure | null;
65
+ allIds(): string[];
66
+ static fromJSON(data: Record<string, unknown>): Figure;
67
+ }
68
+
69
+ class Table extends SharedNonVerbalEntity {
70
+ constructor(data?: Record<string, unknown>);
71
+ readonly content: Record<string, unknown> | null;
72
+ readonly format: string | null;
73
+ static fromJSON(data: Record<string, unknown>): Table;
74
+ }
75
+
76
+ class Formula extends SharedNonVerbalEntity {
77
+ constructor(data?: Record<string, unknown>);
78
+ readonly expression: Record<string, string> | null;
79
+ readonly notation: string | null;
80
+ static fromJSON(data: Record<string, unknown>): Formula;
81
+ }
82
+
83
+ const NON_VERBAL_TYPES: readonly string[];
84
+
85
+ class NonVerbalReference extends RegistrableModel {
86
+ constructor(data?: Record<string, unknown>);
87
+ readonly entityId: string | null;
88
+ readonly display: string | null;
89
+ readonly dedupKey: readonly [string, string | null];
90
+ static fromJSON(data: Record<string, unknown> | string): NonVerbalReference;
91
+ static register(type: string, cls: typeof NonVerbalReference): void;
92
+ }
93
+
94
+ class FigureReference extends NonVerbalReference {
95
+ static fromJSON(data: Record<string, unknown> | string): FigureReference;
96
+ }
97
+
98
+ class TableReference extends NonVerbalReference {
99
+ static fromJSON(data: Record<string, unknown> | string): TableReference;
100
+ }
101
+
102
+ class FormulaReference extends NonVerbalReference {
103
+ static fromJSON(data: Record<string, unknown> | string): FormulaReference;
104
+ }
105
+
106
+ class BibliographyEntry extends GlossaristModel {
107
+ constructor(data?: Record<string, unknown>);
108
+ readonly id: string | null;
109
+ readonly reference: string | null;
110
+ readonly title: string | null;
111
+ readonly link: string | null;
112
+ readonly type: string | null;
113
+ static fromJSON(data: Record<string, unknown>): BibliographyEntry;
114
+ }
115
+
116
+ class BibliographyData extends GlossaristModel {
117
+ constructor(data?: Record<string, unknown>);
118
+ readonly entries: BibliographyEntry[];
119
+ find(id: string): BibliographyEntry | null;
120
+ readonly keys: string[];
121
+ toYAML(): string;
122
+ toJSON(): { bibliography: BibliographyEntry[] };
123
+ static fromYAML(yamlString: string): BibliographyData;
124
+ static fromJSON(data: Record<string, unknown>): BibliographyData;
125
+ }
126
+ }
@@ -1,34 +1,35 @@
1
1
  /**
2
2
  * Public API for the non-verbal entity model layer.
3
3
  *
4
- * Re-exports the types, bridges, and dispatch table. Components and
5
- * composables import from herenever from individual files so the
6
- * internal layout can evolve without breaking the public surface.
4
+ * Model classes (Figure, Table, Formula, FigureImage, NonVerbalEntity) are
5
+ * re-exported from `glossarist`upstream is the SSOT for the model.
6
+ * Consumer-owned types live in `./types`.
7
7
  */
8
8
 
9
9
  export type {
10
10
  LocalizedString,
11
11
  NonVerbalKind,
12
- FigureImage,
13
12
  FigureImageFormat,
14
13
  FigureImageRole,
15
14
  NonVerbalSource,
16
15
  NonVerbalSourceOrigin,
17
16
  NonVerbalSourceRef,
18
17
  NonVerbalSourceLocality,
19
- NonVerbalEntityBase,
20
- Figure,
21
- Table,
22
18
  TableContent,
23
19
  TableFormat,
24
- Formula,
25
20
  FormulaNotation,
26
- NonVerbalEntity,
27
21
  NonVerbRepV3,
28
22
  NonVerbalReference,
29
23
  } from './types';
30
24
 
31
- export { isFigure, isTable, isFormula } from './types';
25
+ export type {
26
+ Figure,
27
+ FigureImage,
28
+ Table,
29
+ Formula,
30
+ NonVerbalEntity,
31
+ SharedNonVerbalEntity,
32
+ } from 'glossarist';
32
33
 
33
34
  export { figureFromJsonLd } from './figure-bridge';
34
35
  export { tableFromJsonLd } from './table-bridge';
@@ -1,4 +1,5 @@
1
- import type { NonVerbalEntity, NonVerbalKind } from './types';
1
+ import type { NonVerbalKind } from './types';
2
+ import type { NonVerbalEntity } from 'glossarist';
2
3
  import {
3
4
  ENTITY_DIRECTORIES,
4
5
  ENTITY_TYPES,
@@ -20,7 +20,8 @@
20
20
  * }
21
21
  */
22
22
 
23
- import type { Table, TableContent, TableFormat } from './types';
23
+ import { Table } from 'glossarist';
24
+ import type { TableContent } from './types';
24
25
  import { isType, pickField, localized } from './prefix';
25
26
  import { sourcesFromJsonLd } from './source-bridge';
26
27
 
@@ -83,16 +84,17 @@ export function tableFromJsonLd(doc: Record<string, unknown>): Table | null {
83
84
  if (!content) return null;
84
85
 
85
86
  const formatRaw = (pickField<string>(doc, 'format') ?? '').toLowerCase();
86
- const format = FORMAT_SET.has(formatRaw) ? (formatRaw as TableFormat) : undefined;
87
+ const format = FORMAT_SET.has(formatRaw) ? formatRaw : undefined;
87
88
 
88
89
  const sources = sourcesFromJsonLd(pickField(doc, 'source'));
89
90
 
90
- const t: Table = { kind: 'table', id, content };
91
- if (identifier) t.identifier = identifier;
92
- if (caption) t.caption = caption;
93
- if (description) t.description = description;
94
- if (format) t.format = format;
95
- if (sources.length) t.sources = sources;
96
-
97
- return t;
91
+ return new Table({
92
+ id,
93
+ content: content as Record<string, unknown>,
94
+ ...(identifier && { identifier }),
95
+ ...(caption && { caption }),
96
+ ...(description && { description }),
97
+ ...(format && { format }),
98
+ ...(sources.length && { sources }),
99
+ });
98
100
  }
@@ -1,18 +1,23 @@
1
1
  /**
2
- * Non-verbal entity model TypeScript projection of the authoritative
3
- * glossarist-ruby model.
2
+ * Consumer-side types for non-verbal entities.
4
3
  *
5
- * The authoritative model lives in glossarist-ruby (Figure, Table, Formula
6
- * inherit from NonVerbalEntity). This file mirrors that model in TypeScript
7
- * for the consumer side. It does not redefine the model every field here
8
- * corresponds to a field in the authoritative source.
4
+ * Model classes (Figure, Table, Formula, FigureImage, NonVerbalEntity) are
5
+ * imported from `glossarist` the upstream library is the SSOT for the
6
+ * model. This file holds only what is genuinely consumer-owned:
9
7
  *
10
- * See:
11
- * ../glossarist-ruby/lib/glossarist/non_verbal_entity.rb
12
- * ../glossarist-ruby/lib/glossarist/figure.rb
13
- * ../glossarist-ruby/lib/glossarist/table.rb
14
- * ../glossarist-ruby/lib/glossarist/formula.rb
15
- * ../glossarist-ruby/lib/glossarist/figure_image.rb
8
+ * - `NonVerbalKind`: routing discriminator used by the resolver, the
9
+ * anchor scheme, the mention dispatcher, and the section router.
10
+ * - `NonVerbRepV3`: local view of NonVerbRep's V3 shape. Upstream's
11
+ * published .d.ts still describes the pre-V3 `ref`/`text` shape; this
12
+ * interface lets the consumer type-check against runtime reality.
13
+ * Delete when upstream ships a corrected declaration.
14
+ * - `NonVerbalReference`: consumer-side view of inline mentions like
15
+ * `{{fig:foo}}`. Carries a `kind` for UI routing.
16
+ * - `LocalizedString`, `FigureImageFormat`, `FigureImageRole`,
17
+ * `TableFormat`, `TableContent`, `FormulaNotation`: string-union
18
+ * refinements the consumer validates at bridge time.
19
+ * - `NonVerbalSource*`: wire shape for JSON-LD source entries. Stays
20
+ * consumer-side until upstream ships a V3 NonVerbalSource model.
16
21
  */
17
22
 
18
23
  export type LocalizedString = Record<string, string>;
@@ -23,15 +28,6 @@ export type FigureImageFormat = 'svg' | 'png' | 'jpg' | 'jpeg' | 'gif' | 'webp'
23
28
 
24
29
  export type FigureImageRole = 'vector' | 'raster' | 'dark' | 'light' | 'print';
25
30
 
26
- export interface FigureImage {
27
- src: string;
28
- format: FigureImageFormat;
29
- role?: FigureImageRole;
30
- width?: number;
31
- height?: number;
32
- scale?: number;
33
- }
34
-
35
31
  export interface NonVerbalSourceRef {
36
32
  source?: string;
37
33
  id?: string;
@@ -62,56 +58,42 @@ export interface NonVerbalSource {
62
58
  origin?: NonVerbalSourceOrigin;
63
59
  }
64
60
 
65
- export interface NonVerbalEntityBase {
66
- id: string;
67
- identifier?: string;
68
- caption?: LocalizedString;
69
- description?: LocalizedString;
70
- alt?: LocalizedString;
71
- sources?: NonVerbalSource[];
72
- }
73
-
74
- export interface Figure extends NonVerbalEntityBase {
75
- kind: 'figure';
76
- images: FigureImage[];
77
- subfigures?: Figure[];
78
- }
79
-
80
61
  export type TableFormat = 'html' | 'markdown' | 'asciidoc';
81
62
 
82
63
  export type TableContent =
83
64
  | { kind: 'structured'; headers: LocalizedString[]; rows: LocalizedString[][] }
84
65
  | { kind: 'markup'; markup: LocalizedString };
85
66
 
86
- export interface Table extends NonVerbalEntityBase {
87
- kind: 'table';
88
- content: TableContent;
89
- format?: TableFormat;
90
- }
91
-
92
67
  export type FormulaNotation = 'latex' | 'mathml' | 'asciimath';
93
68
 
94
- export interface Formula extends NonVerbalEntityBase {
95
- kind: 'formula';
96
- expression: LocalizedString;
97
- notation: FormulaNotation;
98
- }
99
-
100
- export type NonVerbalEntity = Figure | Table | Formula;
101
-
102
69
  /**
103
70
  * V3 NonVerbRep runtime shape.
104
71
  *
105
72
  * glossarist-js's runtime `NonVerbRep` (post-V3 reshape) holds the same
106
- * localized fields as `NonVerbalEntityBase` plus a `type` discriminator
107
- * and an `images[]` array. The published `.d.ts` still describes the
108
- * pre-V3 `ref`/`text` shape; this local interface lets consumer code
109
- * type-check against reality. Remove when upstream ships a corrected
110
- * `.d.ts` (TODO.figures/19).
73
+ * localized fields as the base NonVerbalEntity plus a `type` discriminator
74
+ * and an `images[]` array. The published `.d.ts` (still stale at v0.4.2)
75
+ * describes the pre-V3 `ref`/`text` shape; this local interface lets
76
+ * consumer code type-check against reality. Drop when upstream ships a
77
+ * corrected `.d.ts` (glossarist/glossarist-js#31).
111
78
  */
112
- export interface NonVerbRepV3 extends NonVerbalEntityBase {
79
+ export interface NonVerbRepV3 {
80
+ id: string;
81
+ identifier?: string | null;
113
82
  type: string | null;
114
- images: FigureImage[];
83
+ caption?: LocalizedString | null;
84
+ description?: LocalizedString | null;
85
+ alt?: LocalizedString | null;
86
+ images: NonVerbRepImage[];
87
+ sources?: NonVerbalSource[];
88
+ }
89
+
90
+ export interface NonVerbRepImage {
91
+ src: string;
92
+ format?: string | null;
93
+ role?: string | null;
94
+ width?: number | null;
95
+ height?: number | null;
96
+ scale?: number | null;
115
97
  }
116
98
 
117
99
  export interface NonVerbalReference {
@@ -119,15 +101,3 @@ export interface NonVerbalReference {
119
101
  entityId: string;
120
102
  display?: string;
121
103
  }
122
-
123
- export function isFigure(entity: NonVerbalEntity): entity is Figure {
124
- return entity.kind === 'figure';
125
- }
126
-
127
- export function isTable(entity: NonVerbalEntity): entity is Table {
128
- return entity.kind === 'table';
129
- }
130
-
131
- export function isFormula(entity: NonVerbalEntity): entity is Formula {
132
- return entity.kind === 'formula';
133
- }
@@ -15,11 +15,13 @@
15
15
  * exactly one instance per app.
16
16
  */
17
17
 
18
- import type { NonVerbalEntity, NonVerbalKind } from './non-verbal/types';
18
+ import type { NonVerbalKind } from './non-verbal/types';
19
+ import type { NonVerbalEntity } from 'glossarist';
19
20
  import { KIND_TO_DIR, KIND_TO_BRIDGE } from './non-verbal/kind';
20
21
  import { anchorId } from '../utils/non-verbal-anchor';
21
22
 
22
- export type { NonVerbalEntity, NonVerbalKind } from './non-verbal/types';
23
+ export type { NonVerbalKind } from './non-verbal/types';
24
+ export type { NonVerbalEntity } from 'glossarist';
23
25
 
24
26
  export interface NonVerbalEntityResolverOptions {
25
27
  basePath?: string;
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue';
3
3
  import type { NonVerbRep, Citation } from 'glossarist';
4
- import type { FigureImage, LocalizedString, NonVerbRepV3, NonVerbalSource } from '../adapters/non-verbal/types';
4
+ import type { LocalizedString, NonVerbRepV3, NonVerbRepImage, NonVerbalSource } from '../adapters/non-verbal/types';
5
5
  import { resolveFallbackChain } from '../utils/locale';
6
6
  import FigureImages from './figure/FigureImages.vue';
7
7
  import NonVerbalCaption from './non-verbal/NonVerbalCaption.vue';
@@ -21,7 +21,7 @@ const fallbackChain = computed(() => resolveFallbackChain(props.datasetLocales))
21
21
  // (images/alt/caption/description/sources). See TODO.figures/19.
22
22
  const v3Reps = computed<NonVerbRepV3[]>(() => props.reps as unknown as NonVerbRepV3[]);
23
23
 
24
- function imagesOf(rep: NonVerbRepV3): FigureImage[] {
24
+ function imagesOf(rep: NonVerbRepV3): NonVerbRepImage[] {
25
25
  return rep.images ?? [];
26
26
  }
27
27
 
@@ -10,7 +10,7 @@
10
10
  * so `{{fig:id}}` mentions can scroll to it via the cross-ref composable.
11
11
  */
12
12
  import { computed, ref } from 'vue';
13
- import type { Figure } from '../../adapters/non-verbal/types';
13
+ import type { Figure } from 'glossarist';
14
14
  import { useNonVerbalEntity } from '../../composables/use-non-verbal-entity';
15
15
  import { resolveFallbackChain } from '../../utils/locale';
16
16
  import { anchorId } from '../../utils/non-verbal-anchor';
@@ -34,39 +34,40 @@ const id = () => props.entityId;
34
34
  const { entity, state, error } = useNonVerbalEntity(k, ds, id);
35
35
 
36
36
  const fallbackChain = computed(() => resolveFallbackChain(props.datasetLocales));
37
- const layout = computed(() => entity.value ? deriveLayout(entity.value as Figure) : 'single');
37
+ const fig = computed(() => entity.value as Figure | null);
38
+ const layout = computed(() => fig.value ? deriveLayout(fig.value) : 'single');
38
39
  const anchor = computed(() => anchorId('figure', props.datasetId, props.entityId));
39
40
  const descriptionId = computed(() => `${anchor.value}-desc`);
40
41
 
41
- const topLevelImages = computed(() => (entity.value as Figure | null)?.images ?? []);
42
+ const topLevelImages = computed(() => fig.value?.images ?? []);
42
43
  </script>
43
44
 
44
45
  <template>
45
46
  <figure
46
- v-if="entity && state === 'loaded'"
47
+ v-if="fig && state === 'loaded'"
47
48
  :id="anchor"
48
49
  :class="`figure figure--${layout}`"
49
50
  >
50
51
  <div v-if="topLevelImages.length" :class="`figure__media figure__media--${layout}`">
51
52
  <FigureImages
52
53
  :images="topLevelImages"
53
- :alt="(entity as Figure).alt"
54
+ :alt="fig.alt"
54
55
  :dataset-id="datasetId"
55
56
  :locale="locale"
56
57
  :fallback-chain="fallbackChain"
57
- :hasDescription="!!(entity as Figure).description && Object.keys((entity as Figure).description ?? {}).length > 0"
58
+ :hasDescription="!!fig.description && Object.keys(fig.description ?? {}).length > 0"
58
59
  :description-id="descriptionId"
59
60
  entity-label="Figure"
60
61
  />
61
62
  </div>
62
63
 
63
- <template v-if="(entity as Figure).subfigures?.length">
64
+ <template v-if="fig.subfigures?.length">
64
65
  <div :class="`figure__subfigures figure__subfigures--${layout}`">
65
66
  <FigureDisplay
66
- v-for="sub in (entity as Figure).subfigures"
67
- :key="sub.id"
67
+ v-for="sub in fig.subfigures"
68
+ :key="sub.id ?? ''"
68
69
  :dataset-id="datasetId"
69
- :entity-id="sub.id"
70
+ :entity-id="sub.id ?? ''"
70
71
  :locale="locale"
71
72
  :dataset-locales="datasetLocales"
72
73
  />
@@ -74,17 +75,17 @@ const topLevelImages = computed(() => (entity.value as Figure | null)?.images ??
74
75
  </template>
75
76
 
76
77
  <NonVerbalCaption
77
- :identifier="(entity as Figure).identifier"
78
- :caption="(entity as Figure).caption"
79
- :description="(entity as Figure).description"
78
+ :identifier="fig.identifier"
79
+ :caption="fig.caption"
80
+ :description="fig.description"
80
81
  :locale="locale"
81
82
  :fallback-chain="fallbackChain"
82
83
  :description-id="descriptionId"
83
84
  />
84
85
 
85
86
  <NonVerbalSources
86
- v-if="(entity as Figure).sources?.length"
87
- :sources="(entity as Figure).sources!"
87
+ v-if="fig.sources?.length"
88
+ :sources="fig.sources"
88
89
  />
89
90
  </figure>
90
91
 
@@ -1,12 +1,15 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue';
3
- import type { FigureImage, LocalizedString } from '../../adapters/non-verbal/types';
3
+ import type { FigureImage } from 'glossarist';
4
+ import type { LocalizedString, NonVerbRepImage } from '../../adapters/non-verbal/types';
4
5
  import { pickLocaleText, hasLocale } from '../../utils/locale';
5
6
  import { getFactory } from '../../adapters/factory';
6
7
 
8
+ type ImageLike = FigureImage | NonVerbRepImage;
9
+
7
10
  const props = withDefaults(defineProps<{
8
- images: FigureImage[];
9
- alt?: LocalizedString;
11
+ images: ImageLike[];
12
+ alt?: LocalizedString | null;
10
13
  datasetId: string;
11
14
  locale: string;
12
15
  fallbackChain?: readonly string[];
@@ -19,7 +22,19 @@ const props = withDefaults(defineProps<{
19
22
 
20
23
  const resolver = getFactory().nonVerbalResolver;
21
24
 
22
- const altText = computed(() => pickLocaleText(props.alt, props.locale, props.fallbackChain));
25
+ function imgSrc(img: ImageLike): string {
26
+ return typeof img.src === 'string' ? img.src : '';
27
+ }
28
+ function imgFormat(img: ImageLike): string {
29
+ const f = img.format;
30
+ return typeof f === 'string' ? f : 'svg';
31
+ }
32
+ function imgRole(img: ImageLike): string | null {
33
+ const r = img.role;
34
+ return typeof r === 'string' ? r : null;
35
+ }
36
+
37
+ const altText = computed(() => pickLocaleText(props.alt ?? undefined, props.locale, props.fallbackChain));
23
38
  const altMissing = computed(() => !props.alt || Object.keys(props.alt).length === 0);
24
39
  const altEmpty = computed(() =>
25
40
  !!props.alt && hasLocale(props.alt, props.locale) && props.alt[props.locale] === '',
@@ -30,26 +45,33 @@ interface SourceVariant { src: string; type: string; media?: string; }
30
45
  const sourceVariants = computed<SourceVariant[]>(() => {
31
46
  const out: SourceVariant[] = [];
32
47
  for (const img of props.images) {
33
- if (!img.role || img.role === 'vector' || img.role === 'raster') continue;
34
- const src = resolver.resolveImageUrl(props.datasetId, img.src);
35
- const type = img.format === 'svg' ? 'image/svg+xml' : `image/${img.format === 'jpg' ? 'jpeg' : img.format}`;
48
+ const role = imgRole(img);
49
+ if (!role || role === 'vector' || role === 'raster') continue;
50
+ const src = imgSrc(img);
51
+ const format = imgFormat(img);
52
+ if (!src) continue;
53
+ const type = format === 'svg' ? 'image/svg+xml' : `image/${format === 'jpg' ? 'jpeg' : format}`;
36
54
  let media: string | undefined;
37
- if (img.role === 'dark') media = '(prefers-color-scheme: dark)';
38
- else if (img.role === 'light') media = '(prefers-color-scheme: light)';
39
- else if (img.role === 'print') media = 'print';
40
- out.push({ src, type, media });
55
+ if (role === 'dark') media = '(prefers-color-scheme: dark)';
56
+ else if (role === 'light') media = '(prefers-color-scheme: light)';
57
+ else if (role === 'print') media = 'print';
58
+ out.push({ src: resolver.resolveImageUrl(props.datasetId, src), type, media });
41
59
  }
42
60
  return out;
43
61
  });
44
62
 
45
63
  const defaultImg = computed(() => {
46
- const nonRole = props.images.find(i => !i.role || i.role === 'vector' || i.role === 'raster');
64
+ const nonRole = props.images.find(i => {
65
+ const r = imgRole(i);
66
+ return !r || r === 'vector' || r === 'raster';
67
+ });
47
68
  const chosen = nonRole ?? props.images[0];
48
- if (!chosen) return null;
69
+ const src = chosen ? imgSrc(chosen) : '';
70
+ if (!src) return null;
49
71
  return {
50
- src: resolver.resolveImageUrl(props.datasetId, chosen.src),
51
- width: chosen.width,
52
- height: chosen.height,
72
+ src: resolver.resolveImageUrl(props.datasetId, src),
73
+ width: chosen?.width ?? undefined,
74
+ height: chosen?.height ?? undefined,
53
75
  };
54
76
  });
55
77
  </script>
@@ -11,7 +11,7 @@
11
11
  * The `print` role is reserved for print stylesheets (selected via CSS
12
12
  * @media print in FigureImages.vue), not picked here.
13
13
  */
14
- import type { FigureImage } from '../../adapters/non-verbal/types';
14
+ import type { FigureImage } from 'glossarist';
15
15
 
16
16
  export interface PickOptions {
17
17
  prefersDark?: boolean;
@@ -12,7 +12,7 @@
12
12
  * Authors who want different behavior can split a composite figure into
13
13
  * multiple top-level figures. V1 does not expose a `layout` field.
14
14
  */
15
- import type { Figure } from '../../adapters/non-verbal/types';
15
+ import type { Figure } from 'glossarist';
16
16
 
17
17
  export type FigureLayout = 'single' | 'row' | 'column' | 'grid';
18
18
 
@@ -7,7 +7,8 @@
7
7
  * `<figure>` receives the anchor ID for `{{formula:id}}` mentions.
8
8
  */
9
9
  import { computed } from 'vue';
10
- import type { Formula } from '../../adapters/non-verbal/types';
10
+ import type { Formula } from 'glossarist';
11
+ import type { FormulaNotation } from '../../adapters/non-verbal/types';
11
12
  import { useNonVerbalEntity } from '../../composables/use-non-verbal-entity';
12
13
  import { resolveFallbackChain } from '../../utils/locale';
13
14
  import { anchorId } from '../../utils/non-verbal-anchor';
@@ -27,37 +28,38 @@ const k = () => 'formula' as const;
27
28
  const { entity, state, error } = useNonVerbalEntity(k, () => props.datasetId, () => props.entityId);
28
29
 
29
30
  const fallbackChain = computed(() => resolveFallbackChain(props.datasetLocales));
31
+ const form = computed(() => entity.value as Formula | null);
30
32
  const anchor = computed(() => anchorId('formula', props.datasetId, props.entityId));
31
33
  const descriptionId = computed(() => `${anchor.value}-desc`);
32
34
  </script>
33
35
 
34
36
  <template>
35
37
  <figure
36
- v-if="entity && state === 'loaded'"
38
+ v-if="form && state === 'loaded'"
37
39
  :id="anchor"
38
40
  class="formula-entity"
39
41
  >
40
42
  <div class="formula__expr-line">
41
43
  <FormulaExpression
42
- :expression="(entity as Formula).expression"
43
- :notation="(entity as Formula).notation"
44
+ :expression="form.expression"
45
+ :notation="(form.notation as FormulaNotation | null)"
44
46
  :locale="locale"
45
47
  :fallback-chain="fallbackChain"
46
48
  />
47
49
  </div>
48
50
 
49
51
  <NonVerbalCaption
50
- :identifier="(entity as Formula).identifier"
51
- :caption="(entity as Formula).caption"
52
- :description="(entity as Formula).description"
52
+ :identifier="form.identifier"
53
+ :caption="form.caption"
54
+ :description="form.description"
53
55
  :locale="locale"
54
56
  :fallback-chain="fallbackChain"
55
57
  :description-id="descriptionId"
56
58
  />
57
59
 
58
60
  <NonVerbalSources
59
- v-if="(entity as Formula).sources?.length"
60
- :sources="(entity as Formula).sources!"
61
+ v-if="form.sources?.length"
62
+ :sources="form.sources"
61
63
  />
62
64
  </figure>
63
65
 
@@ -16,13 +16,13 @@ import { pickLocaleMap, localeToBcp47 } from '../../utils/locale';
16
16
  import { loadPlurimath } from '../../utils/plurimath';
17
17
 
18
18
  const props = defineProps<{
19
- expression: LocalizedString;
20
- notation: FormulaNotation;
19
+ expression: LocalizedString | null;
20
+ notation: FormulaNotation | null;
21
21
  locale: string;
22
22
  fallbackChain?: readonly string[];
23
23
  }>();
24
24
 
25
- const resolved = computed(() => pickLocaleMap(props.expression, props.locale, props.fallbackChain));
25
+ const resolved = computed(() => pickLocaleMap(props.expression ?? undefined, props.locale, props.fallbackChain));
26
26
  const html = ref<string>('');
27
27
  const lang = computed(() => resolved.value ? localeToBcp47(resolved.value.locale) : undefined);
28
28
 
@@ -34,7 +34,7 @@ const PLURIMATH_FORMAT: Record<FormulaNotation, string> = {
34
34
 
35
35
  async function render() {
36
36
  const r = resolved.value;
37
- if (!r) { html.value = ''; return; }
37
+ if (!r || !props.notation) { html.value = ''; return; }
38
38
  try {
39
39
  const Plurimath = await loadPlurimath();
40
40
  const p = new Plurimath(r.text, PLURIMATH_FORMAT[props.notation]);
@@ -22,20 +22,20 @@ import type { LocalizedString } from '../../adapters/non-verbal/types';
22
22
  import { pickLocaleMap, localeToBcp47 } from '../../utils/locale';
23
23
 
24
24
  const props = defineProps<{
25
- identifier?: string;
26
- caption?: LocalizedString;
27
- description?: LocalizedString;
25
+ identifier?: string | null;
26
+ caption?: LocalizedString | null;
27
+ description?: LocalizedString | null;
28
28
  locale: string;
29
29
  fallbackChain?: readonly string[];
30
30
  descriptionId?: string;
31
31
  }>();
32
32
 
33
33
  const captionResolved = computed(() =>
34
- pickLocaleMap(props.caption, props.locale, props.fallbackChain),
34
+ pickLocaleMap(props.caption ?? undefined, props.locale, props.fallbackChain),
35
35
  );
36
36
 
37
37
  const descriptionResolved = computed(() =>
38
- pickLocaleMap(props.description, props.locale, props.fallbackChain),
38
+ pickLocaleMap(props.description ?? undefined, props.locale, props.fallbackChain),
39
39
  );
40
40
 
41
41
  const captionLang = computed(() =>
@@ -6,20 +6,12 @@
6
6
  * so the rendering matches the rest of the app. Each source may carry a
7
7
  * modification note (e.g. "Adapted.") which is rendered alongside.
8
8
  */
9
- import type { Citation } from 'glossarist';
10
- import type { NonVerbalSource } from '../../adapters/non-verbal/types';
9
+ import type { ConceptSource } from 'glossarist';
11
10
  import CitationDisplay from '../CitationDisplay.vue';
12
11
 
13
12
  defineProps<{
14
- sources: NonVerbalSource[];
13
+ sources: ConceptSource[];
15
14
  }>();
16
-
17
- // CitationDisplay expects glossarist's Citation class. NonVerbalSource.origin
18
- // is wire-compatible at runtime but typed differently on the consumer side;
19
- // cast once at this boundary.
20
- function asCitation(origin: NonVerbalSource['origin']): Citation | null {
21
- return (origin as unknown as Citation) ?? null;
22
- }
23
15
  </script>
24
16
 
25
17
  <template>
@@ -27,7 +19,7 @@ function asCitation(origin: NonVerbalSource['origin']): Citation | null {
27
19
  <div class="nv-sources__label">Sources</div>
28
20
  <ol class="nv-sources__list">
29
21
  <li v-for="(src, i) in sources" :key="i" class="nv-source">
30
- <CitationDisplay v-if="asCitation(src.origin)" :citation="asCitation(src.origin)!" />
22
+ <CitationDisplay v-if="src.origin" :citation="src.origin" />
31
23
  <span v-if="src.modification" class="nv-source__modification">
32
24
  — {{ src.modification }}
33
25
  </span>
@@ -6,7 +6,8 @@
6
6
  * TableMarkup (HTML / Markdown / AsciiDoc) based on `content.kind`.
7
7
  */
8
8
  import { computed } from 'vue';
9
- import type { Table } from '../../adapters/non-verbal/types';
9
+ import type { Table } from 'glossarist';
10
+ import type { TableContent, TableFormat } from '../../adapters/non-verbal/types';
10
11
  import { useNonVerbalEntity } from '../../composables/use-non-verbal-entity';
11
12
  import { resolveFallbackChain } from '../../utils/locale';
12
13
  import { anchorId } from '../../utils/non-verbal-anchor';
@@ -31,12 +32,13 @@ const anchor = computed(() => anchorId('table', props.datasetId, props.entityId)
31
32
  const descriptionId = computed(() => `${anchor.value}-desc`);
32
33
 
33
34
  const table = computed<Table | null>(() => entity.value as Table | null);
35
+ const content = computed<TableContent | null>(() => (table.value?.content ?? null) as TableContent | null);
34
36
  const structuredContent = computed(() => {
35
- const c = table.value?.content;
37
+ const c = content.value;
36
38
  return c && c.kind === 'structured' ? c : null;
37
39
  });
38
40
  const markup = computed(() => {
39
- const c = table.value?.content;
41
+ const c = content.value;
40
42
  return c && c.kind === 'markup' ? c.markup : null;
41
43
  });
42
44
  </script>
@@ -56,7 +58,7 @@ const markup = computed(() => {
56
58
  <TableMarkup
57
59
  v-else-if="markup"
58
60
  :content="markup"
59
- :format="table.format"
61
+ :format="(table.format as TableFormat | null)"
60
62
  :locale="locale"
61
63
  :fallback-chain="fallbackChain"
62
64
  />
@@ -18,7 +18,7 @@ import { renderAsciiDocLite } from '../../utils/asciidoc-lite';
18
18
 
19
19
  const props = defineProps<{
20
20
  content: LocalizedString;
21
- format?: TableFormat;
21
+ format?: TableFormat | null;
22
22
  locale: string;
23
23
  fallbackChain?: readonly string[];
24
24
  }>();
@@ -8,7 +8,8 @@
8
8
  */
9
9
  import { ref, watch, shallowRef } from 'vue';
10
10
  import { getFactory } from '../adapters/factory';
11
- import type { NonVerbalEntity, NonVerbalKind } from '../adapters/non-verbal/types';
11
+ import type { NonVerbalKind } from '../adapters/non-verbal/types';
12
+ import type { NonVerbalEntity } from 'glossarist';
12
13
 
13
14
  export type LoadState = 'loading' | 'loaded' | 'not-found' | 'error';
14
15