@vectojs/markdown 0.16.1 → 0.17.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.
@@ -0,0 +1,73 @@
1
+ import type { TokenizerAndRendererExtension, Token } from 'marked';
2
+ /**
3
+ * `markdown-it-abbr`-style abbreviations: `*[HTML]: HyperText Markup Language`
4
+ * defines a term, and every later whole-word occurrence of `HTML` in the
5
+ * document's prose gets a dotted-underline visual treatment.
6
+ *
7
+ * This is architecturally unlike every other `PX-0524` span construct. Every
8
+ * other one (`sub`, `sup`, `ins`, `mark`, `emoji`) is a token-level
9
+ * substitution: the tokenizer sees the construct's own delimiters and emits a
10
+ * styled span right there. An abbreviation definition carries no delimiters at
11
+ * its use site at all — `HTML` in `The HTML spec` is indistinguishable from
12
+ * any other word until the WHOLE document's definitions are known. So this
13
+ * module only collects the dictionary; applying it to prose is
14
+ * `markdown-inline.ts`'s `emitProse` job, which every `collectSpans` leaf now
15
+ * routes through instead of pushing a bare span.
16
+ *
17
+ * ## Why this forces a full-document re-render on change
18
+ *
19
+ * A definition can be written anywhere — GFM allows it inline, but the
20
+ * convention (and this module's only shape) is one line, and streamed
21
+ * documents commonly put them at the end, same as footnotes. If a definition
22
+ * arrives in the incremental-lex TAIL while the term it defines already
23
+ * rendered in the STABLE PREFIX, the prefix's paragraph entities were built by
24
+ * `collectSpans` before the dictionary contained that term, and `Markdown.ts`'s
25
+ * `updateTokens` reuses an entity whenever its token's `raw` is byte-identical
26
+ * — which it is, since the term's OWN paragraph did not change, only a
27
+ * definition elsewhere did. This is exactly `hasLinkDefinitions`'s own
28
+ * problem (a reference definition arriving late must retroactively change
29
+ * already-rendered inline tokens) and gets the same fix at the same layer:
30
+ * `Markdown.ts` tracks the resolved dictionary across renders and forces a full
31
+ * rebuild — not merely a full re-LEX, which `incrementalLex.ts` already gets
32
+ * right regardless — whenever it changes. See `Markdown.ts`'s
33
+ * `abbreviationsChanged` check next to `updateTokens`.
34
+ */
35
+ /** `*[TERM]: definition` — collected metadata, not rendered content. */
36
+ export interface AbbrDefToken {
37
+ type: 'abbrDef';
38
+ raw: string;
39
+ /** The term exactly as written between `*[` and `]`, e.g. `HTML`. */
40
+ term: string;
41
+ /** The definition text, shown as the term's tooltip title in prose. */
42
+ definition: string;
43
+ }
44
+ /**
45
+ * The single `marked.use` block extension, shared between `Markdown.ts` and
46
+ * `MarkdownWorker.ts` — same lockstep requirement `markdown-footnote.ts`
47
+ * documents at length: the two lexers must agree exactly.
48
+ *
49
+ * No `start()`, for the same reason `footnoteDef` supplies none: a block
50
+ * `start()` retroactively re-groups paragraphs already emitted (see
51
+ * `markdown-footnote.ts`'s measured evidence). Its absence costs the same one
52
+ * spec-adjacent behaviour footnotes already accept — a definition on the line
53
+ * directly after a paragraph with no blank line between is absorbed into that
54
+ * paragraph rather than recognised.
55
+ */
56
+ export declare const ABBR_EXTENSIONS: TokenizerAndRendererExtension[];
57
+ /**
58
+ * Whether `text` contains an abbreviation-definition line at all.
59
+ *
60
+ * Cheap reject first (`*[` is rare outside this construct), matching every
61
+ * other opener-detector in this package (`hasBlockMathOpener`,
62
+ * `hasContainerOpener`).
63
+ */
64
+ export declare function hasAbbrDef(text: string): boolean;
65
+ /**
66
+ * Scan top-level tokens for `abbrDef` entries and build the term dictionary.
67
+ *
68
+ * Later definitions of the same term win — the same "last write wins" rule
69
+ * `marked` itself applies to duplicate link reference definitions — rather
70
+ * than throwing or silently keeping the first, since a streamed document has
71
+ * no way to reject a correction after the fact.
72
+ */
73
+ export declare function collectAbbreviations(tokens: readonly Token[]): ReadonlyMap<string, string>;
@@ -1,6 +1,7 @@
1
1
  import { type ContentProjection, type ContentProjectionHint, GlyphRasterAtlas, type GlyphRasterAtlasStats, IRenderer } from '@vectojs/core';
2
2
  import { UIComponent } from '@vectojs/ui';
3
- import { type MarkdownTheme } from './theme';
3
+ import { type MarkdownThemePresetName } from './markdown-presets';
4
+ import type { MarkdownTheme } from './theme';
4
5
  /**
5
6
  * A single self-rendering entity for fenced code blocks.
6
7
  *
@@ -28,14 +29,17 @@ export declare class CodeBlock extends UIComponent {
28
29
  private codeFont;
29
30
  selectable: boolean;
30
31
  /**
31
- * @param theme Any subset of {@link MarkdownTheme}; missing keys fall back to
32
- * `DEFAULT_THEME` in `./theme`. Accepting a partial theme keeps callers that were
33
- * written against an earlier, smaller `MarkdownTheme` working — this class
34
- * is public API, and a hand-built theme literal would otherwise start
35
- * throwing `lineHeight must be a positive finite number` the moment a new
36
- * size key was added.
32
+ * @param theme Any subset of {@link MarkdownTheme}, or the name of a built-in
33
+ * preset (see {@link MarkdownThemePresetName}). Accepting a partial theme
34
+ * keeps callers that were written against an earlier, smaller
35
+ * `MarkdownTheme` working — this class is public API, and a hand-built
36
+ * theme literal would otherwise start throwing
37
+ * `lineHeight must be a positive finite number` the moment a new size key
38
+ * was added. Resolved through {@link resolvePresetTheme} so `CodeBlock` can
39
+ * be constructed directly with a preset name without going through
40
+ * `Markdown`.
37
41
  */
38
- constructor(code: string, lang: string, maxWidth: number, theme: MarkdownTheme, selectable?: boolean);
42
+ constructor(code: string, lang: string, maxWidth: number, theme: MarkdownThemePresetName | MarkdownTheme, selectable?: boolean);
39
43
  /** Re-parse code content (e.g. for live editing). */
40
44
  setCode(code: string, lang?: string): this;
41
45
  /** Enable or disable browser-native selection for this code block. */
@@ -0,0 +1,79 @@
1
+ import type { Token, TokenizerAndRendererExtension } from 'marked';
2
+ /**
3
+ * `:::` fenced containers (`:::note … :::`, `:::warning … :::`), the
4
+ * `remark-directive`/`markdown-it-container` construct.
5
+ *
6
+ * One new `marked.use` **block** extension. Nothing in `marked`'s grammar,
7
+ * including GFM, tokenizes a `:::` fence at all — verified against
8
+ * marked@18.0.7: it falls through to plain `paragraph`/`text` (`PX-0524`).
9
+ *
10
+ * Registered in `Markdown.ts` and `MarkdownWorker.ts` from this single shared
11
+ * array, for the reason `markdown-footnote.ts` gives at length: the two
12
+ * lexers must agree exactly, or the worker emits tokens the renderer has no
13
+ * arm for.
14
+ *
15
+ * Deliberately holds no entity, theme or `@vectojs/*` import, so this module
16
+ * stays safe to inline into the worker bundle (`scripts/build-worker.js`,
17
+ * `bundle: true`).
18
+ */
19
+ /** A `:::kind\n…body…\n:::` block, its body block-lexed like a blockquote's. */
20
+ export interface ContainerToken {
21
+ type: 'container';
22
+ raw: string;
23
+ /**
24
+ * The word after `:::` on the opening line, e.g. `'note'`. `undefined` for
25
+ * a bare `:::` with nothing after it — a container with no declared kind
26
+ * still fences its content, it just has no theme-mapped colour or label.
27
+ */
28
+ kind?: string;
29
+ /** Nested block tokens, exactly as `blockquote`'s `tokens` field. */
30
+ tokens: Token[];
31
+ }
32
+ /**
33
+ * The two extensions, in the order they are registered.
34
+ *
35
+ * ## No `start()`, for the same reason `footnoteDef` has none
36
+ *
37
+ * A block `start()` clips the text handed to the paragraph tokenizer and
38
+ * retroactively re-groups an already-emitted paragraph the moment a `:::`
39
+ * appears anywhere ahead — `markdown-footnote.ts`'s doc comment measures this
40
+ * exhaustively for `[^`, and the same `Lexer.blockTokens` mechanism applies
41
+ * here verbatim. Omitting it costs the same one CommonMark-adjacent
42
+ * behaviour footnotes already give up: a fence written directly after a
43
+ * paragraph line, with no blank line between, is absorbed into that
44
+ * paragraph instead of opening a container. Verified empirically against
45
+ * marked@18.0.7.
46
+ *
47
+ * ## The forward-reach hazard this construct DOES have
48
+ *
49
+ * Unlike `footnoteDef` (single-line, cannot reach past its own token) but
50
+ * LIKE `blockMath`, an unterminated `:::` opener can absorb arbitrarily much
51
+ * of the document once its closing fence eventually arrives — the tokenizer
52
+ * below scans forward for a depth-balanced close with no bound on how far. Measured: a
53
+ * prefix ending in an open `:::note\nHello` block-lexes to
54
+ * `[paragraph, space, paragraph]` (an ordinary unterminated-fence-reads-as-
55
+ * text fallback), and appending `\n\nWorld\n:::\n` collapses ALL of it into a
56
+ * single `container` token whose nested tokens are `[paragraph('Hello'),
57
+ * space, paragraph('World')]` — the same shape `blockMath`'s doc comment
58
+ * documents for `$$`. `incrementalLex.ts`'s `hasContainerOpener()` therefore
59
+ * degrades an instance the same way `hasBlockMathOpener()` does, using the
60
+ * same `OPEN_RE` this module exports for that check to share the exact
61
+ * definition of "a fence is open" with the tokenizer.
62
+ *
63
+ * ## Why an extension is enough
64
+ *
65
+ * `marked.use` extensions run BEFORE the built-in tokenizers (`Lexer.use`
66
+ * inserts with `unshift`), so `container` claims the `:::` line ahead of the
67
+ * built-in `paragraph`/`text` rules — the same ordering `footnoteRef`/
68
+ * `footnoteDef` rely on, contradicting the earlier (wrong) assumption
69
+ * recorded in `PX-0517`/`DEC-01KZDGBE`.
70
+ *
71
+ * ## `renderer` is required but unreachable
72
+ *
73
+ * `marked.use` demands one for a custom token, and `@vectojs/markdown` never
74
+ * calls `marked.parse` — it renders from the token tree. Returning `raw`
75
+ * matches every other extension in this package.
76
+ */
77
+ export declare const CONTAINER_EXTENSIONS: TokenizerAndRendererExtension[];
78
+ /** Whether `text` contains a `:::` opener that {@link OPEN_RE} would match. */
79
+ export declare function hasContainerOpener(text: string): boolean;
@@ -0,0 +1,39 @@
1
+ import type { TokenizerAndRendererExtension } from 'marked';
2
+ /**
3
+ * GitHub-style emoji shortcodes: `:wink:` -> 😉.
4
+ *
5
+ * A new `marked.use` inline extension, the same shape as
6
+ * `markdown-superscript.ts`'s `sup`: `:name:` is not tokenized by marked's
7
+ * built-in grammar at all (verified against marked@18.0.7 — falls through to
8
+ * plain `text`, `PX-0524`), so it needs its own tokenizer.
9
+ *
10
+ * Unlike `sup`/`ins`/`mark`, the token carries no NEW rendering behaviour —
11
+ * once resolved, a shortcode is exactly a run of plain text (the emoji
12
+ * character itself), which the browser's font stack already shapes and colors
13
+ * via `fillText` like any other codepoint. So this module's only real content
14
+ * is the lookup table; `collectSpans`' `emoji` arm just pushes the resolved
15
+ * character with the inherited style, unchanged.
16
+ *
17
+ * Registered in `Markdown.ts` and `MarkdownWorker.ts` from this single shared
18
+ * array, for the reason `markdown-footnote.ts` gives at length: the two lexers
19
+ * must agree exactly, or the worker emits tokens the renderer has no arm for.
20
+ *
21
+ * Deliberately holds no entity, theme or `@vectojs/*` import, so this module
22
+ * stays safe to inline into the worker bundle (`scripts/build-worker.js`,
23
+ * `bundle: true`).
24
+ */
25
+ /** `:name:`, as it appears mid-sentence, already resolved to its character. */
26
+ export interface EmojiToken {
27
+ type: 'emoji';
28
+ raw: string;
29
+ /** The resolved emoji character(s), e.g. `'😉'` — never the shortcode text. */
30
+ text: string;
31
+ }
32
+ /**
33
+ * Shortcode -> emoji character, a representative common subset of GitHub's
34
+ * table (github/gemoji), not the full ~1800-entry set. Extending this table
35
+ * only ever adds a lookup entry; it never touches the tokenizer or
36
+ * `collectSpans`, so growing it later is a one-line-per-emoji change.
37
+ */
38
+ export declare const EMOJI_MAP: Readonly<Record<string, string>>;
39
+ export declare const EMOJI_EXTENSIONS: TokenizerAndRendererExtension[];
@@ -31,3 +31,21 @@ export declare class MarkdownContainer extends Entity {
31
31
  isPointInside(_globalX: number, _globalY: number): boolean;
32
32
  render(_r: any): void;
33
33
  }
34
+ /**
35
+ * A rounded-rect background fill for a `:::` container's full content area.
36
+ *
37
+ * A separate leaf rather than folding the fill into {@link QuoteBorder}: a
38
+ * blockquote's accent bar is the ONLY visual besides its text, but a container
39
+ * additionally has a background — real callout components (Docusaurus
40
+ * admonitions, mdBook, GitHub's `[!NOTE]`) all paint one — and the two need
41
+ * independent geometry (the fill spans the full width, the bar is a narrow
42
+ * strip at `x=0`), so one entity cannot own both a `width`/`height` box and a
43
+ * bar `width`.
44
+ */
45
+ export declare class ContainerBackground extends Entity {
46
+ color: string;
47
+ radius: number;
48
+ constructor(w: number, h: number, color: string, radius: number);
49
+ isPointInside(): boolean;
50
+ render(r: IRenderer): void;
51
+ }
@@ -1,4 +1,4 @@
1
- import type { TokenizerAndRendererExtension } from 'marked';
1
+ import type { Token, TokenizerAndRendererExtension } from 'marked';
2
2
  /**
3
3
  * GFM footnotes: the two `marked` tokenizer extensions, their token types, and
4
4
  * the marker text a reference renders as.
@@ -32,15 +32,44 @@ export interface FootnoteRefToken {
32
32
  /** The label exactly as written between `[^` and `]`, e.g. `1` or `note`. */
33
33
  label: string;
34
34
  }
35
- /** A footnote definition line — `[^1]: The note.` — as its own block. */
35
+ /**
36
+ * A footnote definition — `[^1]: The note.` — as its own block, optionally
37
+ * followed by indented continuation lines that extend the body across
38
+ * multiple paragraphs (`markdown-it`'s and GFM's own shape for this).
39
+ */
36
40
  export interface FootnoteDefToken {
37
41
  type: 'footnoteDef';
38
42
  raw: string;
39
43
  /** The label exactly as written, matching a {@link FootnoteRefToken}'s. */
40
44
  label: string;
41
- /** The note body, source text with inline markup left unparsed. */
45
+ /** The header line's body text, source text with inline markup unparsed. */
42
46
  body: string;
47
+ /**
48
+ * Block tokens for any indented continuation content after the header line
49
+ * (further paragraphs, lists, code, …), block-lexed exactly like a
50
+ * blockquote's `tokens`. Empty when the definition is single-line, which is
51
+ * the overwhelming majority — a definition is only ever multi-paragraph when
52
+ * the source actually indents a second block under it.
53
+ */
54
+ tokens: Token[];
43
55
  }
56
+ /**
57
+ * Whether `text` contains a `[^label]:` header that {@link HEADER_RE} would
58
+ * match, ANYWHERE in the document — the exact condition under which
59
+ * {@link consumeContinuation} can still be scanning forward (an open
60
+ * continuation) when more text arrives.
61
+ *
62
+ * Used by `incrementalLex.ts`'s degrade check, mirroring `hasBlockMathOpener`
63
+ * and `hasContainerOpener`: a footnote definition's continuation-consuming
64
+ * tokenizer has the exact same forward-reach hazard those two document, now
65
+ * that it can span a blank line. Deliberately does not try to determine
66
+ * whether a SPECIFIC header's continuation is still open — that would need to
67
+ * replicate the tokenizer's own scan — and instead degrades on the mere
68
+ * presence of any header, which is safe (if conservative) the same way
69
+ * `hasBlockMathOpener` accepts matching inside a fenced code block it would
70
+ * never actually reach.
71
+ */
72
+ export declare function hasFootnoteDefOpener(text: string): boolean;
44
73
  /**
45
74
  * The two extensions, in the order they are registered.
46
75
  *
@@ -13,6 +13,35 @@ import type { MarkdownTheme } from './theme';
13
13
  */
14
14
  /** Decode basic HTML entities that `marked` emits in token text. */
15
15
  export declare function decodeEntities(text: string): string;
16
+ /**
17
+ * `markdown-it`'s `typographer` substitutions: dashes, ellipsis, trademark
18
+ * symbols, and quote pairs contained within one run of prose.
19
+ *
20
+ * Off by default (`theme.typographer`), matching markdown-it's own default —
21
+ * these are characters the author did not literally type, so applying them
22
+ * unconditionally would silently rewrite a document's source.
23
+ *
24
+ * ## Quote pairing is INTRA-RUN only
25
+ *
26
+ * `"quoted"` becomes curly only when both its opening and closing `"` are in
27
+ * the SAME decoded text run. A quote that spans an inline-markup boundary
28
+ * (`"quoted *emphasis* text"` splits into three text tokens around the `em`)
29
+ * is not paired across that boundary and stays straight. `collectSpans`
30
+ * recurses per-token with no shared mutable state across siblings, and
31
+ * markdown-it's own quote rule needs exactly that — a per-paragraph
32
+ * open/close stack — to pair nested and cross-boundary quotes. Threading that
33
+ * state through this module's recursion is a materially larger change,
34
+ * deferred until a real document exercises the gap.
35
+ *
36
+ * ## Apostrophes vs. quote delimiters
37
+ *
38
+ * A `'` between two letters (`it's`, `y'all`) is a contraction, not a quote
39
+ * delimiter, and is replaced with a closing curly quote BEFORE quote pairing
40
+ * runs — otherwise `it's fine, 'nice' day` would pair the apostrophe in
41
+ * `it's` with the opening `'` of `'nice'`, consuming both instead of matching
42
+ * `'nice'` on its own.
43
+ */
44
+ export declare function applyTypography(text: string): string;
16
45
  /**
17
46
  * Recursively walk the inline token tree, accumulating {@link StyledSpan}s
18
47
  * with inherited style overrides (bold, italic, etc.).
@@ -26,7 +55,14 @@ export declare function collectSpans(tokens: Token[], inherited: TextStyle, them
26
55
  * its size in its `font` string rather than in any span style, so it cannot be
27
56
  * recovered from `inherited`.
28
57
  */
29
- blockFontSize?: number): void;
58
+ blockFontSize?: number,
59
+ /**
60
+ * The document's `*[TERM]: definition` dictionary, applied to every prose
61
+ * leaf via {@link emitProse}. Defaults to {@link NO_ABBREVIATIONS} so every
62
+ * call site that has none to thread — nested recursive calls, and callers
63
+ * predating this feature — costs nothing extra.
64
+ */
65
+ abbr?: ReadonlyMap<string, string>): void;
30
66
  /**
31
67
  * One trailing inline construct that has opened but not closed yet.
32
68
  *
@@ -55,4 +91,6 @@ export interface UnclosedInline {
55
91
  */
56
92
  export declare function findUnclosedInline(text: string): UnclosedInline | null;
57
93
  /** Parse inline markdown tokens and produce a {@link RichText} entity. */
58
- export declare function renderInlineToRichText(tokens: Token[] | undefined, fallbackText: string, font: string, color: string, maxWidth: number, theme: Required<MarkdownTheme>, selectable: boolean, onLinkClick?: (url: string) => void): RichText;
94
+ export declare function renderInlineToRichText(tokens: Token[] | undefined, fallbackText: string, font: string, color: string, maxWidth: number, theme: Required<MarkdownTheme>, selectable: boolean, onLinkClick?: (url: string) => void,
95
+ /** The document's `*[TERM]: definition` dictionary — see {@link emitProse}. */
96
+ abbr?: ReadonlyMap<string, string>): RichText;
@@ -0,0 +1,33 @@
1
+ import type { TokenizerAndRendererExtension } from 'marked';
2
+ /**
3
+ * `markdown-it`-style insert and highlight: `++inserted++`, `==marked==`.
4
+ *
5
+ * Two new `marked.use` inline extensions, the same shape as
6
+ * `markdown-superscript.ts`'s `sup`: neither `+` nor `==` is tokenized by
7
+ * marked's built-in grammar at all (verified against marked@18.0.7 — both fall
8
+ * through to plain `text`, `PX-0524`), so each needs its own tokenizer rather
9
+ * than reusing an existing token type the way single-tilde subscript reuses
10
+ * `del`.
11
+ *
12
+ * Registered in `Markdown.ts` and `MarkdownWorker.ts` from this single shared
13
+ * array, for the reason `markdown-footnote.ts` gives at length: the two lexers
14
+ * must agree exactly, or the worker emits tokens the renderer has no arm for.
15
+ *
16
+ * Deliberately holds no entity, theme or `@vectojs/*` import, so this module
17
+ * stays safe to inline into the worker bundle (`scripts/build-worker.js`,
18
+ * `bundle: true`).
19
+ */
20
+ /** `++content++`, as it appears mid-sentence. See `SuperscriptToken` for why
21
+ * this carries `text` rather than a distinct field name. */
22
+ export interface InsToken {
23
+ type: 'ins';
24
+ raw: string;
25
+ text: string;
26
+ }
27
+ /** `==content==`, as it appears mid-sentence. */
28
+ export interface MarkToken {
29
+ type: 'mark';
30
+ raw: string;
31
+ text: string;
32
+ }
33
+ export declare const INS_MARK_EXTENSIONS: TokenizerAndRendererExtension[];
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Named theme presets for `@vectojs/markdown`.
3
+ *
4
+ * Each preset is a full {@link MarkdownTheme} partial whose color tokens are
5
+ * sourced verbatim from the palette's upstream specification:
6
+ *
7
+ * - **`githubDark`** — GitHub Dark Default (Primer dark palette).
8
+ * Source: `primer/github-vscode-theme` / `rouge-ruby/rouge` Primer primitives.
9
+ * - **`githubLight`** — GitHub Light Default (Primer light palette).
10
+ * Source: same — P_RED_5, P_BLUE_6/P_BLUE_8, P_GRAY_5, P_GRAY_9, canvas tokens.
11
+ * - **`dracula`** — Dracula Classic.
12
+ * Source: https://draculatheme.com/spec and https://github.com/dracula/dracula-theme
13
+ * - **`solarizedDark`** — Solarized dark mode.
14
+ * Source: https://ethanschoonover.com/solarized/ (L*a*b canonical, sRGB values).
15
+ * - **`solarizedLight`** — Solarized light mode.
16
+ * Source: same palette, base-pair swapped per Schoonover's own CSS snippet
17
+ * (light: `base3:base0` background/foreground, `base2` background highlight,
18
+ * `base1` comments; dark: `base03:base0`, `base02`, `base01`).
19
+ *
20
+ * Spacing and typography keys are NOT set in presets: they remain at
21
+ * {@link DEFAULT_THEME} defaults so a caller who passes `theme: 'dracula'`
22
+ * gets Dracula colors on a correctly-spaced layout without having to restate
23
+ * every dimension.
24
+ *
25
+ * Light presets apply a CONTRAST PASS on translucent overlays
26
+ * (`codeBgColor`, `tableHeaderBgColor`, `containerBgColor`, `markHighlightColor`,
27
+ * `hrColor`): the stock theme is dark and its translucent values composite
28
+ * against a near-black surface. On a white/cream background the same RGBA
29
+ * values produce the wrong visual weight, so light presets use opaque or
30
+ * differently-composited colors derived from the palette instead.
31
+ */
32
+ import { type MarkdownTheme } from './theme';
33
+ /** Names of the built-in theme presets. */
34
+ export type MarkdownThemePresetName = 'githubDark' | 'githubLight' | 'dracula' | 'solarizedDark' | 'solarizedLight';
35
+ /** Look-up table from preset name to its partial {@link MarkdownTheme}. */
36
+ export declare const PRESET_THEMES: Readonly<Record<MarkdownThemePresetName, MarkdownTheme>>;
37
+ /**
38
+ * Return `true` if `value` is a recognised {@link MarkdownThemePresetName}.
39
+ *
40
+ * Use this to branch on `theme?: MarkdownThemePresetName | MarkdownTheme`
41
+ * before calling {@link resolvePresetTheme}.
42
+ */
43
+ export declare function isPresetName(value: unknown): value is MarkdownThemePresetName;
44
+ /**
45
+ * Resolve a `theme` option that may be a preset name, a full/partial
46
+ * {@link MarkdownTheme}, or `undefined`, into a `Required<MarkdownTheme>`.
47
+ *
48
+ * Always calls {@link resolveTheme} rather than spreading {@link PRESET_THEMES}
49
+ * or `DEFAULT_THEME` directly, so the derived keys (`tableFontSize` from
50
+ * `fontSize`, `quoteTextColor`/`footnoteColor` from `textColor`/`linkColor`)
51
+ * still apply on top of a preset exactly as they do for a hand-written theme —
52
+ * a preset that sets `linkColor` but not `footnoteColor` should still get a
53
+ * footnote marker in the preset's link color, not the stock accent.
54
+ *
55
+ * A plain `MarkdownTheme` object is passed straight to `resolveTheme` unchanged
56
+ * (this is the pre-existing, non-preset path); a preset name is looked up in
57
+ * {@link PRESET_THEMES} first.
58
+ */
59
+ export declare function resolvePresetTheme(theme?: MarkdownThemePresetName | MarkdownTheme): Required<MarkdownTheme>;
@@ -0,0 +1,36 @@
1
+ import type { TokenizerAndRendererExtension } from 'marked';
2
+ /**
3
+ * `markdown-it`-style superscript: `19^th^`, `x^2^`.
4
+ *
5
+ * A new `marked.use` inline extension, unlike subscript — single-tilde already
6
+ * lexed to a real token (`del`) that `collectSpans` merely had to recognise by
7
+ * `raw`. Nothing in `marked`'s built-in grammar produces a token for `^…^` at
8
+ * all: it falls through to plain `text`, verified against marked@18.0.7 in
9
+ * `PX-0524`. So superscript needs its own tokenizer, the same shape as
10
+ * `markdown-footnote.ts`'s `footnoteRef`.
11
+ *
12
+ * Registered in `Markdown.ts` and `MarkdownWorker.ts` from this single shared
13
+ * array, for the reason `markdown-footnote.ts` gives at length: the two lexers
14
+ * must agree exactly, or the worker emits tokens the renderer has no arm for.
15
+ *
16
+ * Deliberately holds no entity, theme or `@vectojs/*` import, so this module
17
+ * stays safe to inline into the worker bundle (`scripts/build-worker.js`,
18
+ * `bundle: true`).
19
+ */
20
+ /**
21
+ * `^content^`, as it appears mid-sentence.
22
+ *
23
+ * Carries `text` rather than a distinct field name — unlike
24
+ * {@link import('./markdown-footnote').FootnoteRefToken}, which avoids `text`
25
+ * so an unhandled token falls through to nothing rather than to a wrong
26
+ * render. A superscript token IS mostly text, just raised: `producesEntity`'s
27
+ * `'text' in token` fallback rendering it as an ordinary block if this arm were
28
+ * ever missing is a reasonable enough degradation (plain, unraised text) that
29
+ * the distinct-name defence footnotes need does not apply here.
30
+ */
31
+ export interface SuperscriptToken {
32
+ type: 'sup';
33
+ raw: string;
34
+ text: string;
35
+ }
36
+ export declare const SUPERSCRIPT_EXTENSIONS: TokenizerAndRendererExtension[];
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Typographic replacements: `--`/`---` to en/em dash, `...` to `…`, `(c)`/`(r)`/
3
+ * `(tm)` to `©`/`®`/`™`, `+-` to `±`, and repeated `!`/`,` collapsing —
4
+ * `markdown-it`'s `typographer: true` set, minus smart quotes (see below).
5
+ *
6
+ * Ported as a **pure text transform** rather than a new tokenizer or `marked`
7
+ * extension, because that is what it is upstream too: `markdown-it`'s own
8
+ * `replacements` rule runs as a `core` pass over already-tokenized `text`
9
+ * content, not a grammar rule, and every substitution here is describable the
10
+ * same way — a regex over a string that changes no token boundaries. Applying
11
+ * it as the last step before a `text`-bearing span is pushed keeps this
12
+ * completely orthogonal to every other inline arm: a bold/italic/code run's
13
+ * content transforms identically to a plain paragraph's, because it is the same
14
+ * function call, not a special case duplicated per arm.
15
+ *
16
+ * ## Off by default, unlike every other construct in this package
17
+ *
18
+ * `markdown-it` itself defaults `typographer` to `false`, and this mirrors
19
+ * that rather than picking a different default for parity's own sake: the
20
+ * transform is lossy (a real `--` a caller wanted to keep literal, e.g. a CLI
21
+ * flag or a code-adjacent range, becomes an en dash with no way back short of
22
+ * disabling the whole feature) and every other construct in this package
23
+ * (subscript, superscript, ins/mark, emoji, footnotes) is instead
24
+ * unconditionally-on syntax recognition with no lossy default to weigh.
25
+ * `theme.typographer` (default `false`) is the gate, checked once per paragraph
26
+ * of text collection rather than per span, so a caller who never opts in pays
27
+ * nothing beyond the one boolean check `collectSpans` already does today for
28
+ * `inherited`.
29
+ *
30
+ * ## Smart quotes are deliberately NOT ported
31
+ *
32
+ * `markdown-it`'s `smartquotes` rule curls straight quotes into `‘’“”` by
33
+ * walking the FULL inline-token sequence of one block with a stack that
34
+ * matches an opening quote to its closing partner, consulting the previous and
35
+ * next token's own trailing/leading character across token boundaries (see
36
+ * `references/markdown-it/lib/rules_core/smartquotes.mjs`'s `process_inlines`).
37
+ * That is not a per-span text transform — it is a second pass over
38
+ * `collectSpans`' entire OUTPUT array, with cross-span state (the open-quote
39
+ * stack) and lookback/lookahead into neighboring spans' text. Porting it here
40
+ * would mean walking `out` after the fact, which every call site of
41
+ * `collectSpans`/`applyTypography` would have to remember to do, and getting it
42
+ * wrong reads as a rendering defect (mismatched curly quotes) rather than a
43
+ * missing feature. Left unimplemented; a caller wanting curled quotes still
44
+ * gets straight ones, the same honest fallback every unsupported construct in
45
+ * this package gets.
46
+ */
47
+ /**
48
+ * Apply typographic substitutions to one run of plain text.
49
+ *
50
+ * Order matters and mirrors `markdown-it`'s own rule (`replace_scoped` before
51
+ * `replace_rare`, and within the latter: `+-`, then `...`, then the
52
+ * `?`/`!`-adjacent ellipsis correction, then `!!!!`/`,,`, then em-dash, then
53
+ * en-dash):
54
+ *
55
+ * - `+-` before `...`: neither can produce the other's trigger character, so
56
+ * order between them is actually inert, but matching upstream's order keeps
57
+ * this auditable against it rather than needing its own independent proof.
58
+ * - The em-dash pass runs before the en-dash pass. `---` is three hyphens; the
59
+ * en-dash patterns below both require a NON-hyphen on the dash-adjacent side
60
+ * (`(?=[^-]|$)` / preceded by whitespace or a non-hyphen-non-space), so `---`
61
+ * itself never matches either en-dash pattern regardless of order — but a
62
+ * FOUR-hyphen run (`----`) would: the em-dash regex only consumes exactly
63
+ * three of the four hyphens (`(^|[^-])---(?=[^-]|$)` requires its OWN
64
+ * boundary hyphens to be non-hyphen), leaving a leftover single hyphens on
65
+ * either side that the leftover isn't itself `--`. Verified empirically
66
+ * against `markdown-it`'s reference implementation that `----` and `---`
67
+ * both resolve to one em dash: `----` is not decomposed into em+en, and
68
+ * running en-dash first would have changed that (the outer pair of the four
69
+ * hyphens would each independently look like the START of a `--` run before
70
+ * the em-dash pass ever saw the middle two as a `---`).
71
+ */
72
+ export declare function applyTypography(text: string): string;