@vectojs/markdown 0.17.0 → 0.18.1

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,159 @@
1
+ import type { Entity } from '@vectojs/core';
2
+ import type { MarkdownTheme } from './theme';
3
+ /**
4
+ * Fenced block renderer registry: pluggable rendering for code fences keyed by
5
+ * info string.
6
+ *
7
+ * Each language (code, math, mermaid, graphviz, …) is a plugin rather than a
8
+ * branch in `Markdown.renderToken`. Renderers lazy-load on demand and cache
9
+ * conversions, reusing the pattern math already established.
10
+ *
11
+ * ## Lifecycle
12
+ *
13
+ * A renderer may be in one of three states:
14
+ * - **incomplete**: The renderer is registered but its module/assets are not
15
+ * loaded yet. The first call to `render()` triggers the load.
16
+ * - **ready**: The renderer is loaded and can render synchronously.
17
+ * - **error**: The renderer failed to load or encountered an unrecoverable error.
18
+ * Falls back to default code block rendering.
19
+ *
20
+ * ## Fallback
21
+ *
22
+ * When no renderer is registered for a language, or when a renderer returns
23
+ * `null`, the registry falls back to the default code block renderer. This
24
+ * preserves backward compatibility and degrades gracefully.
25
+ *
26
+ * ## Example
27
+ *
28
+ * ```typescript
29
+ * import { FencedBlockRegistry } from '@vectojs/markdown';
30
+ *
31
+ * // Register a custom renderer
32
+ * FencedBlockRegistry.register('mermaid', {
33
+ * async load() {
34
+ * const mermaid = await import('mermaid');
35
+ * return (source, lang, options) => {
36
+ * // ... render logic
37
+ * return entity;
38
+ * };
39
+ * }
40
+ * });
41
+ *
42
+ * // Unregister (for testing or cleanup)
43
+ * FencedBlockRegistry.unregister('mermaid');
44
+ * ```
45
+ */
46
+ /**
47
+ * Options passed to a fenced block renderer.
48
+ *
49
+ * Includes the theme, available width, and whether text should be selectable.
50
+ * Renderers may ignore options that don't apply to their output format.
51
+ */
52
+ export interface FencedBlockRenderOptions {
53
+ /** The current Markdown theme (colors, fonts, sizes). */
54
+ theme: MarkdownTheme;
55
+ /** Available horizontal space in pixels. */
56
+ availableWidth: number;
57
+ /** Whether text content should be selectable. */
58
+ selectable: boolean;
59
+ }
60
+ /**
61
+ * A fenced block renderer: converts source code in a specific language to an Entity.
62
+ *
63
+ * Returns `null` when rendering fails or the source is invalid. The caller falls
64
+ * back to a default code block.
65
+ */
66
+ export type FencedBlockRenderer = (source: string, lang: string, options: FencedBlockRenderOptions) => Entity | null;
67
+ /**
68
+ * A lazy-loadable renderer specification.
69
+ *
70
+ * The `load()` method is called once, the first time a fence with this language
71
+ * appears. It should return a synchronous renderer function, or `null` if loading
72
+ * fails. Failures are logged but swallowed — the fence renders as a code block.
73
+ */
74
+ export interface FencedBlockRendererSpec {
75
+ /**
76
+ * Load the renderer asynchronously.
77
+ *
78
+ * Called exactly once, the first time a fence with this language is encountered.
79
+ * Returns a synchronous renderer, or `null` on failure. The promise rejection
80
+ * is caught and logged; renderers must not leave unhandled rejections.
81
+ */
82
+ load: () => Promise<FencedBlockRenderer | null>;
83
+ }
84
+ /**
85
+ * Register a lazy-loadable fenced block renderer for a language.
86
+ *
87
+ * @param lang - The language identifier (case-insensitive). Normalized to lowercase.
88
+ * @param spec - The renderer specification, with a `load()` method.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * FencedBlockRegistry.register('mermaid', {
93
+ * async load() {
94
+ * const mermaid = await import('mermaid');
95
+ * return (source, lang, options) => {
96
+ * // ... render Mermaid diagram
97
+ * return entity;
98
+ * };
99
+ * }
100
+ * });
101
+ * ```
102
+ */
103
+ export declare function registerFencedBlockRenderer(lang: string, spec: FencedBlockRendererSpec): void;
104
+ /**
105
+ * Unregister a fenced block renderer.
106
+ *
107
+ * Used for testing (sabotage tests) and cleanup. After unregistering, fences with
108
+ * this language fall back to the default code block renderer.
109
+ *
110
+ * @param lang - The language identifier (case-insensitive).
111
+ */
112
+ export declare function unregisterFencedBlockRenderer(lang: string): void;
113
+ /**
114
+ * Check if a renderer is registered for a language.
115
+ *
116
+ * Returns `true` if a renderer is registered, regardless of whether it has loaded yet.
117
+ *
118
+ * @param lang - The language identifier (case-insensitive).
119
+ */
120
+ export declare function hasFencedBlockRenderer(lang: string): boolean;
121
+ /**
122
+ * Check if a renderer is ready (loaded and available for synchronous rendering).
123
+ *
124
+ * Returns `false` if the renderer is not registered, not loaded yet, or failed to load.
125
+ *
126
+ * @param lang - The language identifier (case-insensitive).
127
+ */
128
+ export declare function isFencedBlockRendererReady(lang: string): boolean;
129
+ /**
130
+ * Begin (or join) loading a fenced block renderer.
131
+ *
132
+ * Idempotent: the load promise is cached, so multiple callers share one load.
133
+ * Failures are swallowed — the renderer simply stays unavailable.
134
+ *
135
+ * Call this when a fence with this language first appears, even while it is still
136
+ * open (incomplete). This prefetches the module so the closing fence can render
137
+ * synchronously, hiding the lazy load during a stream.
138
+ *
139
+ * @param lang - The language identifier (case-insensitive).
140
+ * @returns A promise that resolves when the renderer is ready, or immediately if
141
+ * already loaded.
142
+ */
143
+ export declare function ensureFencedBlockRenderer(lang: string): Promise<void>;
144
+ /**
145
+ * Render a fenced code block using the registered renderer for its language.
146
+ *
147
+ * Returns `null` if:
148
+ * - No renderer is registered for this language
149
+ * - The renderer is not loaded yet
150
+ * - The renderer returned `null` (rendering failed)
151
+ *
152
+ * The caller falls back to the default code block renderer in all three cases.
153
+ *
154
+ * @param source - The source code inside the fence.
155
+ * @param lang - The language identifier (case-insensitive).
156
+ * @param options - Rendering options (theme, width, selectable).
157
+ * @returns The rendered entity, or `null` to fall back to default code block.
158
+ */
159
+ export declare function renderFencedBlock(source: string, lang: string, options: FencedBlockRenderOptions): Entity | null;
@@ -59,14 +59,15 @@ export interface FootnoteDefToken {
59
59
  * {@link consumeContinuation} can still be scanning forward (an open
60
60
  * continuation) when more text arrives.
61
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
62
+ * Used by `incrementalLex.ts`'s degrade check, mirroring `hasContainerOpener`:
63
+ * a footnote definition's continuation-consuming tokenizer has the exact same
64
+ * forward-reach hazard a `:::` fence does, now that it can span a blank line.
65
+ * (`blockMath` used to be the third member of this set; its tokenizer now stops
66
+ * at a blank line, so it no longer degrades — see `paragraphPairCap`.) Deliberately does not try to determine
66
67
  * whether a SPECIFIC header's continuation is still open — that would need to
67
68
  * replicate the tokenizer's own scan — and instead degrades on the mere
68
69
  * presence of any header, which is safe (if conservative) the same way
69
- * `hasBlockMathOpener` accepts matching inside a fenced code block it would
70
+ * `hasContainerOpener` accepts matching inside a fenced code block it would
70
71
  * never actually reach.
71
72
  */
72
73
  export declare function hasFootnoteDefOpener(text: string): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/markdown",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },