@vectojs/markdown 0.16.0 → 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.
- package/dist/Markdown.d.ts +24 -1
- package/dist/MarkdownWorkerSource.d.ts +1 -1
- package/dist/incrementalLex.d.ts +18 -1
- package/dist/index.js +955 -67
- package/dist/index.mjs +952 -67
- package/dist/markdown-abbr.d.ts +73 -0
- package/dist/markdown-code.d.ts +12 -8
- package/dist/markdown-container.d.ts +79 -0
- package/dist/markdown-emoji.d.ts +39 -0
- package/dist/markdown-entities.d.ts +18 -0
- package/dist/markdown-footnote.d.ts +32 -3
- package/dist/markdown-inline.d.ts +40 -2
- package/dist/markdown-ins-mark.d.ts +33 -0
- package/dist/markdown-presets.d.ts +59 -0
- package/dist/markdown-superscript.d.ts +36 -0
- package/dist/markdown-typography.d.ts +72 -0
- package/dist/theme.d.ts +127 -0
- package/package.json +2 -2
|
@@ -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>;
|
package/dist/markdown-code.d.ts
CHANGED
|
@@ -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
|
|
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}
|
|
32
|
-
*
|
|
33
|
-
* written against an earlier, smaller
|
|
34
|
-
* is public API, and a hand-built
|
|
35
|
-
*
|
|
36
|
-
* size key
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
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;
|