Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/adapters/non-verbal-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
* exactly one instance per app.
*/

import type { NonVerbalEntity, NonVerbalKind } from './non-verbal/types';
import type { NonVerbalKind } from './non-verbal/types';
import type { NonVerbalEntity } from 'glossarist';
import { KIND_TO_DIR, KIND_TO_BRIDGE } from './non-verbal/kind';
import { anchorId } from '../utils/non-verbal-anchor';

export type { NonVerbalEntity, NonVerbalKind } from './non-verbal/types';
export type { NonVerbalKind } from './non-verbal/types';
export type { NonVerbalEntity } from 'glossarist';

export interface NonVerbalEntityResolverOptions {
basePath?: string;
Expand Down
45 changes: 22 additions & 23 deletions src/adapters/non-verbal/figure-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
* ambiguity with the HTML `<img alt>` attribute.
*/

import type { Figure, FigureImage, FigureImageFormat, FigureImageRole } from './types';
import { isType, pickField, pickFieldArray, pickFieldRecord, localized } from './prefix';
import { Figure, FigureImage } from 'glossarist';
import { isType, pickField, pickFieldArray, localized } from './prefix';
import { sourcesFromJsonLd } from './source-bridge';

const FORMAT_SET: ReadonlySet<string> = new Set(['svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif']);
Expand All @@ -35,18 +35,20 @@ function imageFromJsonLd(raw: Record<string, unknown>): FigureImage | null {
const src = pickField<string>(raw, 'src');
if (!src) return null;
const formatRaw = (pickField<string>(raw, 'format') ?? '').toLowerCase();
const format = (FORMAT_SET.has(formatRaw) ? formatRaw : 'svg') as FigureImageFormat;
const format = FORMAT_SET.has(formatRaw) ? formatRaw : 'svg';
const roleRaw = pickField<string>(raw, 'role');
const role = roleRaw && ROLE_SET.has(roleRaw) ? (roleRaw as FigureImageRole) : undefined;
const role = roleRaw && ROLE_SET.has(roleRaw) ? roleRaw : undefined;
const width = pickField<number>(raw, 'width');
const height = pickField<number>(raw, 'height');
const scale = pickField<number>(raw, 'scale');
const img: FigureImage = { src, format };
if (role) img.role = role;
if (typeof width === 'number') img.width = width;
if (typeof height === 'number') img.height = height;
if (typeof scale === 'number') img.scale = scale;
return img;
return new FigureImage({
src,
format,
...(role !== undefined && { role }),
...(typeof width === 'number' && { width }),
...(typeof height === 'number' && { height }),
...(typeof scale === 'number' && { scale }),
});
}

function imagesFromJsonLd(raw: unknown): FigureImage[] {
Expand Down Expand Up @@ -85,17 +87,14 @@ export function figureFromJsonLd(doc: Record<string, unknown>): Figure | null {
const subfigures = subfiguresFromJsonLd(pickField(doc, 'subfigure'));
const sources = sourcesFromJsonLd(pickField(doc, 'source'));

const fig: Figure = { kind: 'figure', id, images };
if (identifier) fig.identifier = identifier;
if (caption) fig.caption = caption;
if (alt) fig.alt = alt;
if (description) fig.description = description;
if (subfigures) fig.subfigures = subfigures;
if (sources.length) fig.sources = sources;

// pickFieldRecord is unused for figures; keep the import meaningful by
// ensuring no stray image fields leak through (silent no-op).
void pickFieldRecord;

return fig;
return new Figure({
id,
images,
...(identifier && { identifier }),
...(caption && { caption }),
...(alt && { alt }),
...(description && { description }),
...(subfigures && { subfigures }),
...(sources.length && { sources }),
});
}
20 changes: 11 additions & 9 deletions src/adapters/non-verbal/formula-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* }
*/

import type { Formula, FormulaNotation } from './types';
import { Formula } from 'glossarist';
import { isType, pickField, localized } from './prefix';
import { sourcesFromJsonLd } from './source-bridge';

Expand All @@ -31,18 +31,20 @@ export function formulaFromJsonLd(doc: Record<string, unknown>): Formula | null
if (!expression) return null;

const notationRaw = (pickField<string>(doc, 'notation') ?? '').toLowerCase();
const notation = NOTATION_SET.has(notationRaw) ? (notationRaw as FormulaNotation) : 'latex';
const notation = NOTATION_SET.has(notationRaw) ? notationRaw : 'latex';

const identifier = pickField<string>(doc, 'identifier');
const caption = localized(doc, 'caption');
const description = localized(doc, 'description');
const sources = sourcesFromJsonLd(pickField(doc, 'source'));

const f: Formula = { kind: 'formula', id, expression, notation };
if (identifier) f.identifier = identifier;
if (caption) f.caption = caption;
if (description) f.description = description;
if (sources.length) f.sources = sources;

return f;
return new Formula({
id,
expression,
notation,
...(identifier && { identifier }),
...(caption && { caption }),
...(description && { description }),
...(sources.length && { sources }),
});
}
126 changes: 126 additions & 0 deletions src/adapters/non-verbal/glossarist-augment.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Local module augmentation for glossarist 0.4.2.
//
// Upstream's published src/models/index.d.ts declares ZERO classes for the
// non-verbal hierarchy (Figure, Table, Formula, FigureImage, NonVerbalEntity,
// SharedNonVerbalEntity, NonVerbalReference + subclasses, BibliographyEntry,
// BibliographyData) plus the localized-string helpers. The top-level
// index.d.ts re-exports the names, so TypeScript silently resolves every
// consumer import to `any`.
//
// This file declares the runtime shape so consumer code can be type-checked.
// DELETE this file when upstream ships proper declarations — tracked by
// PR glossarist/glossarist-js#31 (targets v0.4.3+).

import type { ConceptSource, GlossaristModel } from 'glossarist';

declare module 'glossarist' {
class RegistrableModel extends GlossaristModel {
static register(type: string, cls: typeof RegistrableModel): void;
static fromData(data: Record<string, unknown>): RegistrableModel;
}

class FigureImage extends GlossaristModel {
constructor(data?: {
src?: string | null;
format?: string | null;
role?: string | null;
width?: number | null;
height?: number | null;
scale?: number | null;
});
readonly src: string | null;
readonly format: string | null;
readonly role: string | null;
readonly width: number | null;
readonly height: number | null;
readonly scale: number | null;
static fromJSON(data: Record<string, unknown>): FigureImage;
}

class NonVerbalEntity extends RegistrableModel {
constructor(data?: Record<string, unknown>);
readonly caption: Record<string, string> | null;
readonly description: Record<string, string> | null;
readonly alt: Record<string, string> | null;
readonly sources: ConceptSource[];
findById(targetId: string): NonVerbalEntity | null;
allIds(): string[];
static fromJSON(data: Record<string, unknown>): NonVerbalEntity;
}

class SharedNonVerbalEntity extends NonVerbalEntity {
constructor(data?: Record<string, unknown>);
readonly id: string | null;
readonly identifier: string | null;
findById(targetId: string): SharedNonVerbalEntity | null;
allIds(): string[];
static fromJSON(data: Record<string, unknown>): SharedNonVerbalEntity;
}

class Figure extends SharedNonVerbalEntity {
constructor(data?: Record<string, unknown>);
readonly images: FigureImage[];
readonly subfigures: Figure[];
findById(targetId: string): Figure | null;
allIds(): string[];
static fromJSON(data: Record<string, unknown>): Figure;
}

class Table extends SharedNonVerbalEntity {
constructor(data?: Record<string, unknown>);
readonly content: Record<string, unknown> | null;
readonly format: string | null;
static fromJSON(data: Record<string, unknown>): Table;
}

class Formula extends SharedNonVerbalEntity {
constructor(data?: Record<string, unknown>);
readonly expression: Record<string, string> | null;
readonly notation: string | null;
static fromJSON(data: Record<string, unknown>): Formula;
}

const NON_VERBAL_TYPES: readonly string[];

class NonVerbalReference extends RegistrableModel {
constructor(data?: Record<string, unknown>);
readonly entityId: string | null;
readonly display: string | null;
readonly dedupKey: readonly [string, string | null];
static fromJSON(data: Record<string, unknown> | string): NonVerbalReference;
static register(type: string, cls: typeof NonVerbalReference): void;
}

class FigureReference extends NonVerbalReference {
static fromJSON(data: Record<string, unknown> | string): FigureReference;
}

class TableReference extends NonVerbalReference {
static fromJSON(data: Record<string, unknown> | string): TableReference;
}

class FormulaReference extends NonVerbalReference {
static fromJSON(data: Record<string, unknown> | string): FormulaReference;
}

class BibliographyEntry extends GlossaristModel {
constructor(data?: Record<string, unknown>);
readonly id: string | null;
readonly reference: string | null;
readonly title: string | null;
readonly link: string | null;
readonly type: string | null;
static fromJSON(data: Record<string, unknown>): BibliographyEntry;
}

class BibliographyData extends GlossaristModel {
constructor(data?: Record<string, unknown>);
readonly entries: BibliographyEntry[];
find(id: string): BibliographyEntry | null;
readonly keys: string[];
toYAML(): string;
toJSON(): { bibliography: BibliographyEntry[] };
static fromYAML(yamlString: string): BibliographyData;
static fromJSON(data: Record<string, unknown>): BibliographyData;
}
}
21 changes: 12 additions & 9 deletions src/adapters/non-verbal/index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
/**
* Public API for the non-verbal entity model layer.
*
* Re-exports the types, bridges, and dispatch table. Components and
* composables import from herenever from individual files — so the
* internal layout can evolve without breaking the public surface.
* Model classes (Figure, Table, Formula, FigureImage, NonVerbalEntity) are
* re-exported from `glossarist`upstream is the SSOT for the model.
* Consumer-owned types live in `./types`.
*/

export type {
LocalizedString,
NonVerbalKind,
FigureImage,
FigureImageFormat,
FigureImageRole,
NonVerbalSource,
NonVerbalSourceOrigin,
NonVerbalSourceRef,
NonVerbalSourceLocality,
NonVerbalEntityBase,
Figure,
Table,
TableContent,
TableFormat,
Formula,
FormulaNotation,
NonVerbalEntity,
NonVerbRepV3,
NonVerbalReference,
} from './types';

export type {
Figure,
FigureImage,
Table,
Formula,
NonVerbalEntity,
SharedNonVerbalEntity,
} from 'glossarist';

export { figureFromJsonLd } from './figure-bridge';
export { tableFromJsonLd } from './table-bridge';
export { formulaFromJsonLd } from './formula-bridge';
Expand Down
3 changes: 2 additions & 1 deletion src/adapters/non-verbal/kind.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NonVerbalEntity, NonVerbalKind } from './types';
import type { NonVerbalKind } from './types';
import type { NonVerbalEntity } from 'glossarist';
import {
ENTITY_DIRECTORIES,
ENTITY_TYPES,
Expand Down
22 changes: 12 additions & 10 deletions src/adapters/non-verbal/table-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
* }
*/

import type { Table, TableContent, TableFormat } from './types';
import { Table } from 'glossarist';
import type { TableContent } from './types';
import { isType, pickField, localized } from './prefix';
import { sourcesFromJsonLd } from './source-bridge';

Expand Down Expand Up @@ -83,16 +84,17 @@ export function tableFromJsonLd(doc: Record<string, unknown>): Table | null {
if (!content) return null;

const formatRaw = (pickField<string>(doc, 'format') ?? '').toLowerCase();
const format = FORMAT_SET.has(formatRaw) ? (formatRaw as TableFormat) : undefined;
const format = FORMAT_SET.has(formatRaw) ? formatRaw : undefined;

const sources = sourcesFromJsonLd(pickField(doc, 'source'));

const t: Table = { kind: 'table', id, content };
if (identifier) t.identifier = identifier;
if (caption) t.caption = caption;
if (description) t.description = description;
if (format) t.format = format;
if (sources.length) t.sources = sources;

return t;
return new Table({
id,
content: content as Record<string, unknown>,
...(identifier && { identifier }),
...(caption && { caption }),
...(description && { description }),
...(format && { format }),
...(sources.length && { sources }),
});
}
Loading