@vectojs/markdown 0.22.0 → 0.23.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.
- package/README.md +53 -225
- package/dist/Markdown.d.ts +91 -60
- package/dist/MarkdownWorkerSource.d.ts +1 -1
- package/dist/index.js +189 -90
- package/dist/index.mjs +189 -90
- package/dist/markdown-abbr.d.ts +0 -8
- package/dist/markdown-image.d.ts +22 -22
- package/package.json +5 -5
- package/dist/markdown-typography.d.ts +0 -72
package/README.md
CHANGED
|
@@ -1,243 +1,71 @@
|
|
|
1
1
|
# @vectojs/markdown
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
(`RichText`, `Stack`, `Table`, `Text`, `Image`). It also exports `CodeBlock`.
|
|
9
|
-
|
|
10
|
-
This package was split out of `@vectojs/ui` so that the heavy `marked` +
|
|
11
|
-
`@vectojs/tex` dependencies are only pulled in by apps that actually render
|
|
12
|
-
Markdown. Because it depends on `@vectojs/ui` components, it sits **above** `ui`
|
|
13
|
-
in the dependency graph — install it alongside `@vectojs/ui` and `@vectojs/core`.
|
|
3
|
+
`@vectojs/markdown` renders Markdown (with TeX math) as a canvas-native entity tree: the
|
|
4
|
+
`Markdown` entity parses with `marked`, typesets math through `@vectojs/tex`, and lays the result
|
|
5
|
+
out using `@vectojs/ui` components. It was split out of `@vectojs/ui` precisely so the heavy
|
|
6
|
+
`marked` + `@vectojs/tex` dependencies load only for apps that render documents — it sits **above**
|
|
7
|
+
`ui` in the dependency graph and takes `@vectojs/core` and `@vectojs/ui` as peer dependencies.
|
|
14
8
|
|
|
15
9
|
## Install
|
|
16
10
|
|
|
17
|
-
```
|
|
11
|
+
```bash
|
|
18
12
|
bun add @vectojs/markdown @vectojs/ui @vectojs/core
|
|
19
13
|
```
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
import { Markdown } from "@vectojs/markdown";
|
|
25
|
-
|
|
26
|
-
const md = new Markdown("# Hello\n\nInline math $E = mc^2$.");
|
|
27
|
-
scene.add(md);
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Streaming
|
|
15
|
+
Peers: `@vectojs/core >=1.34.0 <2`, `@vectojs/ui >=2.6.0 <3`.
|
|
31
16
|
|
|
32
|
-
|
|
33
|
-
parse/layout commit per animation frame. `write()` applies backpressure when its
|
|
34
|
-
bounded buffer is full; await it when consuming an async token source.
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
const md = new Markdown("");
|
|
38
|
-
scene.add(md);
|
|
39
|
-
|
|
40
|
-
const stream = md.createStream();
|
|
41
|
-
for await (const token of tokens) {
|
|
42
|
-
await stream.write(token);
|
|
43
|
-
}
|
|
44
|
-
// Commits the final text, then waits for the parse to be applied — once this
|
|
45
|
-
// resolves, the rendered document reflects everything written.
|
|
46
|
-
await stream.close();
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Add fixed-rate typewriter pacing without changing producer code:
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
const stream = md.createStream({
|
|
53
|
-
pacing: { graphemesPerSecond: 48 },
|
|
54
|
-
maxBufferedChars: 64 * 1024,
|
|
55
|
-
signal: abortController.signal,
|
|
56
|
-
});
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Pacing slices by grapheme cluster, so combining marks, emoji ZWJ sequences,
|
|
60
|
-
flags, and surrogate pairs stay intact across ordinary chunk/frame boundaries.
|
|
61
|
-
`abort()` discards uncommitted text; `Markdown.destroy()` does the same cleanup
|
|
62
|
-
automatically. The existing `appendMarkdown()` API remains synchronous and
|
|
63
|
-
flushes submitted controller text before a direct append.
|
|
64
|
-
|
|
65
|
-
### Incomplete Markdown while streaming
|
|
66
|
-
|
|
67
|
-
Mid-stream, the trailing text of a paragraph is often an unclosed inline
|
|
68
|
-
construct — `**bo`, `` `cod ``, `[text](url`. `marked` has no signal that more
|
|
69
|
-
characters are coming, so it lexes those as plain text. `incompleteMode` chooses
|
|
70
|
-
what you show in the meantime:
|
|
17
|
+
## Usage
|
|
71
18
|
|
|
72
19
|
```ts
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
| Mode | Unclosed `**bo` renders as |
|
|
77
|
-
| --------------------- | ------------------------------------ |
|
|
78
|
-
| `'literal'` (default) | `**bo` — the literal characters |
|
|
79
|
-
| `'optimistic'` | **bo** — guessed bold, syntax hidden |
|
|
80
|
-
|
|
81
|
-
`'optimistic'` covers strong, emphasis, and inline code. An unclosed link shows
|
|
82
|
-
its label as plain text: with no closing `)` there is no URL yet, so nothing is
|
|
83
|
-
made clickable. Only the document's **last paragraph** is ever guessed at, and
|
|
84
|
-
only while the stream is open — headings, list items, table cells, and any
|
|
85
|
-
earlier paragraph are always literal.
|
|
20
|
+
import { Scene } from '@vectojs/core';
|
|
21
|
+
import { Markdown } from '@vectojs/markdown';
|
|
86
22
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
at an identical document. `'literal'` remains the default: it is what every prior
|
|
90
|
-
release rendered.
|
|
23
|
+
const scene = new Scene(document.querySelector<HTMLCanvasElement>('canvas')!);
|
|
24
|
+
scene.renderMode = 'onDemand';
|
|
91
25
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
const stream = md.createStream({
|
|
100
|
-
onStable: (blocks) => {
|
|
101
|
-
for (const block of blocks) fadeIn(block);
|
|
26
|
+
const md = new Markdown('# Hello\n\nInline math $E = mc^2$.', {
|
|
27
|
+
maxWidth: 640,
|
|
28
|
+
theme: 'githubDark', // or a full MarkdownTheme object
|
|
29
|
+
onLinkClick(href) {
|
|
30
|
+
console.log('navigate', href);
|
|
102
31
|
},
|
|
103
32
|
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
It never fires on `flush()`, `abort()`, or `destroy()` — none of those mean the
|
|
107
|
-
content stopped changing. It receives a snapshot array, not a live reference.
|
|
108
|
-
Calling `appendMarkdown()` or `setContent()` from inside the callback throws; a
|
|
109
|
-
throw from the callback rejects the `close()` promise. `onStable` is independent
|
|
110
|
-
of `incompleteMode` and works with the `'literal'` default.
|
|
111
|
-
|
|
112
|
-
### Streamed TeX math
|
|
113
|
-
|
|
114
|
-
A fenced math block is typeset only once its closing fence arrives:
|
|
33
|
+
scene.add(md.setPosition(24, 24));
|
|
115
34
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
35
|
+
// Stream LLM output: chunks coalesce into one parse per frame.
|
|
36
|
+
const stream = md.createStream({ incompleteMode: 'optimistic' });
|
|
37
|
+
stream.write('# Ti');
|
|
38
|
+
await stream.close(); // resolves after the final parse is applied
|
|
119
39
|
```
|
|
120
|
-
````
|
|
121
|
-
|
|
122
|
-
While the fence is still open the block renders as an ordinary code block showing
|
|
123
|
-
the TeX source, then becomes the formula on the chunk that closes it. This is
|
|
124
|
-
deliberate. `marked` lexes an unterminated fence as a _complete_ token as soon as
|
|
125
|
-
it reads the info string, so a formula streamed a few characters at a time
|
|
126
|
-
arrives as a long run of whole tokens, nearly all of them invalid TeX — typesetting
|
|
127
|
-
each one spends the most expensive call in this package rendering an error glyph
|
|
128
|
-
that is replaced by the next chunk. Showing the source is both cheaper and more
|
|
129
|
-
honest: the formula genuinely is not finished.
|
|
130
|
-
|
|
131
|
-
Converted formulas are cached (bounded, process-wide), so a repeated formula is
|
|
132
|
-
converted once no matter how many documents or instances render it.
|
|
133
|
-
|
|
134
|
-
Inline `$...$` math is a separate path: it is currently shown as styled source
|
|
135
|
-
text, not typeset.
|
|
136
|
-
|
|
137
|
-
#### The math engine is loaded on demand
|
|
138
|
-
|
|
139
|
-
TeX math is typeset by `@vectojs/tex`, which is imported dynamically the first
|
|
140
|
-
time a document actually has a formula. It is by far the heaviest thing this
|
|
141
|
-
package can pull in — measured against a browser bundle of a consumer that renders
|
|
142
|
-
only prose, built with code splitting and minification:
|
|
143
|
-
|
|
144
|
-
| prose-only consumer | raw | gzip | chunks |
|
|
145
|
-
| ---------------------- | --------: | ------: | -----: |
|
|
146
|
-
| `mathjax-full` | 2,199,869 | 748,713 | 19 |
|
|
147
|
-
| `@vectojs/tex` (now) | 758,249 | 273,754 | 3 |
|
|
148
|
-
| no math at all (floor) | 379,224 | 118,670 | 3 |
|
|
149
|
-
|
|
150
|
-
Against that floor the math path itself is 630,043 gzip under `mathjax-full` and
|
|
151
|
-
155,033 under `@vectojs/tex` — **4.06x smaller**. The eagerly-downloaded entry
|
|
152
|
-
chunk a prose-only consumer actually pays for is 117,889 gzip, within 1 KB of the
|
|
153
|
-
no-math floor.
|
|
154
|
-
|
|
155
|
-
Your bundler needs code splitting enabled to see this; without it the bytes are
|
|
156
|
-
still in the output, just not evaluated until first use.
|
|
157
|
-
|
|
158
|
-
The tradeoff is that **the first formula on a page cannot be typeset
|
|
159
|
-
synchronously.** It renders as a code block of TeX source — the same state an
|
|
160
|
-
unclosed fence already shows — and is replaced once the module resolves. Every
|
|
161
|
-
formula after that is synchronous again. (The engine itself is synchronous; the
|
|
162
|
-
lazy import is what defers it, and it is kept for the bundle size above.)
|
|
163
|
-
|
|
164
|
-
While streaming this is invisible: the load starts as soon as an _opening_ math
|
|
165
|
-
fence appears, several chunks before the closing one, so the formula is typeset on
|
|
166
|
-
the chunk that closes it. `await stream.close()` and `onStable` also wait for a
|
|
167
|
-
pending load, so a "final" document never hands you an untypeset formula.
|
|
168
|
-
|
|
169
|
-
If you need the very first formula typeset in the same tick — measuring layout
|
|
170
|
-
immediately after construction, for instance — preload it:
|
|
171
|
-
|
|
172
|
-
````ts
|
|
173
|
-
import { Markdown, preloadMathJax } from "@vectojs/markdown";
|
|
174
|
-
|
|
175
|
-
await preloadMathJax();
|
|
176
|
-
const md = new Markdown("```math\n\\int_0^1 x\\,dx\n```"); // typeset synchronously
|
|
177
|
-
````
|
|
178
|
-
|
|
179
|
-
`preloadMathJax()` is idempotent and shared across every document, so calling it
|
|
180
|
-
from several places starts one load. `isMathJaxReady()` reports whether formulas
|
|
181
|
-
currently typeset without waiting. If the load fails, formulas keep rendering as
|
|
182
|
-
TeX source rather than throwing.
|
|
183
|
-
|
|
184
|
-
Both names are historical: they date from when `mathjax-full` was the engine and
|
|
185
|
-
mean "the math engine", whichever one that is. They keep those names because they
|
|
186
|
-
are public API and a rename would break every consumer for cosmetics.
|
|
187
|
-
|
|
188
|
-
A formula containing a symbol outside the engine's shipped glyph corpus also
|
|
189
|
-
renders as TeX source rather than being drawn with that symbol missing.
|
|
190
|
-
|
|
191
|
-
## Images
|
|
192
|
-
|
|
193
|
-
An image renders in one of two ways, decided by where it is written.
|
|
194
|
-
|
|
195
|
-
**On its own, or in a paragraph, blockquote or list item**, the paragraph splits
|
|
196
|
-
into blocks and the image becomes an `Image` entity at its natural size, capped
|
|
197
|
-
to the available width. This is the ordinary `` case.
|
|
198
|
-
|
|
199
|
-
**On a line it shares with text** — in a heading, or in a table cell — it renders
|
|
200
|
-
as an inline box in the text run, so the prose flows around it and selection and
|
|
201
|
-
the accessible name still work. Its height is a multiple of the run's font size
|
|
202
|
-
(`theme.inlineImageScale`, default `1.15`) and its width follows the image's
|
|
203
|
-
natural aspect ratio, so a badge stays wide and a square icon stays square:
|
|
204
|
-
|
|
205
|
-
```ts
|
|
206
|
-
const md = new Markdown("# Build ");
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
This is a deliberate departure from HTML, which would render an inline image at
|
|
210
|
-
its intrinsic size. A 512px logo written into an `h1` would otherwise tower over
|
|
211
|
-
its own heading, and an inline box has to be sized before the image has decoded.
|
|
212
|
-
The height is fixed up front for the same reason: the line box never moves, and
|
|
213
|
-
only the width settles once the aspect ratio is known.
|
|
214
|
-
|
|
215
|
-
The `alt` text is the accessible name and the copied text — never painted as
|
|
216
|
-
visible prose. If the image fails to load, the box is replaced by the alt text
|
|
217
|
-
rather than left as an invisible gap.
|
|
218
|
-
|
|
219
|
-
## Syntax coverage
|
|
220
|
-
|
|
221
|
-
Everything in [CommonMark](https://spec.commonmark.org/) plus the
|
|
222
|
-
[GFM](https://github.github.com/gfm/) extensions this renderer draws: tables,
|
|
223
|
-
strikethrough, task lists, autolinks, plus `$…$` / `$$…$$` TeX math and
|
|
224
|
-
` ```math ` fences.
|
|
225
|
-
|
|
226
|
-
Two constructs are deliberately **not** supported, and both are pinned by tests
|
|
227
|
-
so the behaviour cannot drift silently:
|
|
228
|
-
|
|
229
|
-
- **Definition lists.** `Term` then `: definition` renders as the two literal
|
|
230
|
-
lines the source contains, colon included.
|
|
231
|
-
- **Raw HTML blocks.** `<details>`, `<div>`, `<iframe>` and HTML comments render
|
|
232
|
-
nothing at all.
|
|
233
|
-
|
|
234
|
-
Definition lists are neither CommonMark nor GFM; when they arrive it will be
|
|
235
|
-
through the same syntax-extension mechanism footnotes need. Raw HTML blocks
|
|
236
|
-
cannot work in a zero-DOM renderer — there is no DOM to hand markup to. `<svg>`
|
|
237
|
-
is the one exception, because a self-contained SVG document can be rasterized.
|
|
238
|
-
|
|
239
|
-
Footnotes (`[^1]`) are **not yet parsed** and currently render as literal source.
|
|
240
40
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
41
|
+
## Highlights
|
|
42
|
+
|
|
43
|
+
- CommonMark plus GFM tables, strikethrough, task lists, and autolinks; paragraphs become
|
|
44
|
+
`RichText`, fences become `CodeBlock`, GFM tables become `Table`.
|
|
45
|
+
- Frame-coalesced streaming via `createStream()`: at most one parse/layout commit per animation
|
|
46
|
+
frame, grapheme-cluster-safe typewriter pacing (`graphemesPerSecond`), bounded-buffer
|
|
47
|
+
backpressure on `write()`, `onStable` firing once when the document is final, and an
|
|
48
|
+
`incompleteMode` of `'literal'` (default) or `'optimistic'` for unclosed markers mid-stream.
|
|
49
|
+
- TeX math through `@vectojs/tex`, dynamically imported on the first formula so prose-only bundles
|
|
50
|
+
stay small; `preloadMathJax()` / `isMathJaxReady()` control the lazy load, converted formulas are
|
|
51
|
+
cached process-wide, and fenced math blocks typeset only once their closing fence arrives.
|
|
52
|
+
- Themes: constructor accepts a preset name (`'githubDark' | 'githubLight' | 'dracula' |
|
|
53
|
+
'solarizedDark' | 'solarizedLight'`) or a full theme object; post-construction changes go through
|
|
54
|
+
`setTheme()` — `theme` itself is getter-only (#657).
|
|
55
|
+
- Selectable content: rendered text projects browser-native drag selection, copy, and find-in-page;
|
|
56
|
+
toggle at runtime with `setSelectable(false)`.
|
|
57
|
+
- Block affordances: opt-in copy/download controls on code blocks and tables with injectable
|
|
58
|
+
`writeClipboard` / `saveFile`, per-kind overrides, and CSV export helpers (`tableToCsv`,
|
|
59
|
+
`tableToMarkdown`).
|
|
60
|
+
- Front matter: `scanFrontMatter` / `parseFrontMatterFields` expose YAML-ish metadata before the
|
|
61
|
+
document body renders.
|
|
62
|
+
- Large-document virtualization: pass `virtualize` and drive `setVisibleRange(scrollY, viewportHeight)`
|
|
63
|
+
to materialize only top-level blocks near the viewport.
|
|
64
|
+
|
|
65
|
+
> Documents @vectojs/markdown@0.23.0.
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
- [Markdown reference](https://vectojs.org/reference/ui-markdown/)
|
|
70
|
+
- [CodeBlock reference](https://vectojs.org/reference/ui-codeblock/)
|
|
71
|
+
- [Streaming guide](https://vectojs.org/learn/streaming/)
|
package/dist/Markdown.d.ts
CHANGED
|
@@ -150,7 +150,38 @@ export interface MarkdownOptions {
|
|
|
150
150
|
export declare class Markdown extends UIComponent {
|
|
151
151
|
content: Stack;
|
|
152
152
|
maxWidth: number;
|
|
153
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Resolved once in the constructor and never re-applied: entities capture
|
|
155
|
+
* colors, fonts and sizes at build time, so assigning this after construction
|
|
156
|
+
* would paint blocks built afterwards in the new palette while everything
|
|
157
|
+
* earlier kept the old one. Exposed read-only to keep that trap unreachable
|
|
158
|
+
* (#657); the supported post-construction path is {@link setTheme}, which
|
|
159
|
+
* re-renders instead of leaving blocks half-migrated.
|
|
160
|
+
*
|
|
161
|
+
* Backed by {@link currentTheme} rather than a `readonly` field because the
|
|
162
|
+
* blockquote arm legally swaps the render-time theme for its own subtree and
|
|
163
|
+
* restores it afterwards — an internal, exception-safe scope, not a re-theme.
|
|
164
|
+
*/
|
|
165
|
+
get theme(): Required<MarkdownTheme>;
|
|
166
|
+
private currentTheme;
|
|
167
|
+
/**
|
|
168
|
+
* Swap the document theme and rebuild every rendered block.
|
|
169
|
+
*
|
|
170
|
+
* Accepts the same shapes as {@link MarkdownOptions.theme}: a preset name or
|
|
171
|
+
* a full/partial {@link MarkdownTheme}, resolved through
|
|
172
|
+
* {@link resolvePresetTheme}. This is the only supported way to change theme
|
|
173
|
+
* after construction — {@link theme} is a read-only view (#657) precisely so
|
|
174
|
+
* tokens cannot be swapped underneath already-built blocks without the
|
|
175
|
+
* rebuild performed here.
|
|
176
|
+
*
|
|
177
|
+
* The rebuild goes through {@link setContent} rather than an in-place
|
|
178
|
+
* repaint: entities capture colors and fonts at build time, so nothing short
|
|
179
|
+
* of re-rendering applies a palette, and an active stream needs the same
|
|
180
|
+
* teardown an explicit content replacement gets.
|
|
181
|
+
*
|
|
182
|
+
* @returns `this` for chaining.
|
|
183
|
+
*/
|
|
184
|
+
setTheme(theme: MarkdownThemePresetName | MarkdownTheme): this;
|
|
154
185
|
onLinkClick?: (url: string) => void;
|
|
155
186
|
selectable: boolean;
|
|
156
187
|
/**
|
|
@@ -535,12 +566,6 @@ export declare class Markdown extends UIComponent {
|
|
|
535
566
|
private refitParagraphImage;
|
|
536
567
|
/** Replace all markdown content (full rebuild). */
|
|
537
568
|
setContent(markdown: string): this;
|
|
538
|
-
/**
|
|
539
|
-
* Tear down this Markdown block: drop any in-flight worker callbacks (each
|
|
540
|
-
* pins `this` via its closure, so a mid-stream destroy would otherwise keep
|
|
541
|
-
* the whole subtree alive until the worker replied), then recurse into the
|
|
542
|
-
* content subtree via `super.destroy()` so every block's resources are freed.
|
|
543
|
-
*/
|
|
544
569
|
/**
|
|
545
570
|
* Repaint this document when an inline formula's raster finishes decoding.
|
|
546
571
|
*
|
|
@@ -591,6 +616,12 @@ export declare class Markdown extends UIComponent {
|
|
|
591
616
|
* this predicate.
|
|
592
617
|
*/
|
|
593
618
|
private inlineImageBoxesStale;
|
|
619
|
+
/**
|
|
620
|
+
* Tear down this Markdown block: drop any in-flight worker callbacks (each
|
|
621
|
+
* pins `this` via its closure, so a mid-stream destroy would otherwise keep
|
|
622
|
+
* the whole subtree alive until the worker replied), then recurse into the
|
|
623
|
+
* content subtree via `super.destroy()` so every block's resources are freed.
|
|
624
|
+
*/
|
|
594
625
|
destroy(): void;
|
|
595
626
|
/**
|
|
596
627
|
* Streaming and parse state — the markdown streaming inspector.
|
|
@@ -643,30 +674,6 @@ export declare class Markdown extends UIComponent {
|
|
|
643
674
|
* guess is unwound back to.
|
|
644
675
|
*/
|
|
645
676
|
private literalParagraphSpans;
|
|
646
|
-
/**
|
|
647
|
-
* Update a reused blockquote's tail child in place, or report that it cannot be.
|
|
648
|
-
*
|
|
649
|
-
* The render arm builds `container[border, innerStack]` where every inner block
|
|
650
|
-
* sits in its own single-child `wrapper`, so the tail entity is
|
|
651
|
-
* `innerStack.children.at(-1).children[0]`. Only the LAST inner block may be
|
|
652
|
-
* updated: the inner token list is prefix-stable exactly like the top level (a
|
|
653
|
-
* growing quote keeps its earlier blocks byte-identical), so anything before the
|
|
654
|
-
* tail is untouched and anything more complicated than a changed tail falls back
|
|
655
|
-
* to the caller's rebuild.
|
|
656
|
-
*
|
|
657
|
-
* Returns `false` without mutating anything when the shape is not the simple
|
|
658
|
-
* grow-the-tail case, which is the signal for the caller to rebuild. Every early
|
|
659
|
-
* return has to leave the entity untouched, or a rejected reuse would leave a
|
|
660
|
-
* half-updated quote on screen.
|
|
661
|
-
*/
|
|
662
|
-
/**
|
|
663
|
-
* Build one list item's spans: inline content plus its marker.
|
|
664
|
-
*
|
|
665
|
-
* Shared by the `list` render arm and the streamed-reuse path below, because
|
|
666
|
-
* the two must produce byte-identical spans — a reused list that disagreed with
|
|
667
|
-
* a rebuilt one about its marker or its entity decoding would make a streamed
|
|
668
|
-
* document differ from the same source pasted at once.
|
|
669
|
-
*/
|
|
670
677
|
/**
|
|
671
678
|
* Inline spans for one table cell.
|
|
672
679
|
*
|
|
@@ -697,6 +704,28 @@ export declare class Markdown extends UIComponent {
|
|
|
697
704
|
private inlineRunSpans;
|
|
698
705
|
/** One text run of an image-bearing paragraph, as both paths build it. */
|
|
699
706
|
private inlineRunRichText;
|
|
707
|
+
/**
|
|
708
|
+
* Wraps a block in its copy / download controls, or returns it untouched.
|
|
709
|
+
*
|
|
710
|
+
* The controls are built lazily through `make` so a document with
|
|
711
|
+
* `blockAffordances` off pays nothing — not the closures, not the measurement
|
|
712
|
+
* `BlockAffordanceButton` does in its constructor.
|
|
713
|
+
*/
|
|
714
|
+
private withBlockAffordances;
|
|
715
|
+
/** Copy and download controls for one fenced code block, per {@link affordanceConfig}. */
|
|
716
|
+
private codeBlockAffordances;
|
|
717
|
+
/** Copy (as Markdown) and download (as CSV) controls for one table, per {@link affordanceConfig}. */
|
|
718
|
+
private tableAffordances;
|
|
719
|
+
/**
|
|
720
|
+
* Button styling for the affordances, derived from the document theme.
|
|
721
|
+
*
|
|
722
|
+
* Themed rather than hardcoded so a light-theme document does not get the dark
|
|
723
|
+
* default palette. `focusColor` is set explicitly from the theme's accent
|
|
724
|
+
* because `Button`'s default cyan is tuned for the dark palette and reads as
|
|
725
|
+
* off-brand elsewhere — while a focus ring is the one affordance a keyboard
|
|
726
|
+
* user cannot do without.
|
|
727
|
+
*/
|
|
728
|
+
private affordanceButtonOptions;
|
|
700
729
|
/**
|
|
701
730
|
* One image inside a paragraph, sized by a guess until its bitmap decodes.
|
|
702
731
|
*
|
|
@@ -722,28 +751,6 @@ export declare class Markdown extends UIComponent {
|
|
|
722
751
|
* policy for a zero-dimension source is a separate decision from notifying
|
|
723
752
|
* the scene, which is the actual defect here.
|
|
724
753
|
*/
|
|
725
|
-
/**
|
|
726
|
-
* Wraps a block in its copy / download controls, or returns it untouched.
|
|
727
|
-
*
|
|
728
|
-
* The controls are built lazily through `make` so a document with
|
|
729
|
-
* `blockAffordances` off pays nothing — not the closures, not the measurement
|
|
730
|
-
* `BlockAffordanceButton` does in its constructor.
|
|
731
|
-
*/
|
|
732
|
-
private withBlockAffordances;
|
|
733
|
-
/** Copy and download controls for one fenced code block, per {@link affordanceConfig}. */
|
|
734
|
-
private codeBlockAffordances;
|
|
735
|
-
/** Copy (as Markdown) and download (as CSV) controls for one table, per {@link affordanceConfig}. */
|
|
736
|
-
private tableAffordances;
|
|
737
|
-
/**
|
|
738
|
-
* Button styling for the affordances, derived from the document theme.
|
|
739
|
-
*
|
|
740
|
-
* Themed rather than hardcoded so a light-theme document does not get the dark
|
|
741
|
-
* default palette. `focusColor` is set explicitly from the theme's accent
|
|
742
|
-
* because `Button`'s default cyan is tuned for the dark palette and reads as
|
|
743
|
-
* off-brand elsewhere — while a focus ring is the one affordance a keyboard
|
|
744
|
-
* user cannot do without.
|
|
745
|
-
*/
|
|
746
|
-
private affordanceButtonOptions;
|
|
747
754
|
private paragraphImage;
|
|
748
755
|
/**
|
|
749
756
|
* Re-position every block after an image whose decode corrected its box.
|
|
@@ -859,6 +866,14 @@ export declare class Markdown extends UIComponent {
|
|
|
859
866
|
* bullet or ordinal.
|
|
860
867
|
*/
|
|
861
868
|
private listItemBlockStack;
|
|
869
|
+
/**
|
|
870
|
+
* Build one list item's spans: inline content plus its marker.
|
|
871
|
+
*
|
|
872
|
+
* Shared by the `list` render arm and the streamed-reuse path below, because
|
|
873
|
+
* the two must produce byte-identical spans — a reused list that disagreed with
|
|
874
|
+
* a rebuilt one about its marker or its entity decoding would make a streamed
|
|
875
|
+
* document differ from the same source pasted at once.
|
|
876
|
+
*/
|
|
862
877
|
private listItemSpans;
|
|
863
878
|
/** Construct the `RichText` for one list item. */
|
|
864
879
|
private listItemRichText;
|
|
@@ -950,6 +965,22 @@ export declare class Markdown extends UIComponent {
|
|
|
950
965
|
* (its keys are `text`/`tokens`/`header`/`align`).
|
|
951
966
|
*/
|
|
952
967
|
private updateStreamedTable;
|
|
968
|
+
/**
|
|
969
|
+
* Update a reused blockquote's tail child in place, or report that it cannot be.
|
|
970
|
+
*
|
|
971
|
+
* The render arm builds `container[border, innerStack]` where every inner block
|
|
972
|
+
* sits in its own single-child `wrapper`, so the tail entity is
|
|
973
|
+
* `innerStack.children.at(-1).children[0]`. Only the LAST inner block may be
|
|
974
|
+
* updated: the inner token list is prefix-stable exactly like the top level (a
|
|
975
|
+
* growing quote keeps its earlier blocks byte-identical), so anything before the
|
|
976
|
+
* tail is untouched and anything more complicated than a changed tail falls back
|
|
977
|
+
* to the caller's rebuild.
|
|
978
|
+
*
|
|
979
|
+
* Returns `false` without mutating anything when the shape is not the simple
|
|
980
|
+
* grow-the-tail case, which is the signal for the caller to rebuild. Every early
|
|
981
|
+
* return has to leave the entity untouched, or a rejected reuse would leave a
|
|
982
|
+
* half-updated quote on screen.
|
|
983
|
+
*/
|
|
953
984
|
private updateBlockquoteTail;
|
|
954
985
|
/**
|
|
955
986
|
* Spans for a heading being updated in place.
|
|
@@ -979,13 +1010,6 @@ export declare class Markdown extends UIComponent {
|
|
|
979
1010
|
private optimisticParagraphSpans;
|
|
980
1011
|
/** Display style for a guessed-closed construct. */
|
|
981
1012
|
private optimisticStyle;
|
|
982
|
-
/**
|
|
983
|
-
* Re-render the paragraph currently showing a guess from its own tokens, with
|
|
984
|
-
* no overlay, and forget it.
|
|
985
|
-
*
|
|
986
|
-
* Idempotent and free when no guess is live, which is what lets `close()`,
|
|
987
|
-
* `abort()`, and a mid-stream staleness check all call it unconditionally.
|
|
988
|
-
*/
|
|
989
1013
|
/**
|
|
990
1014
|
* Start the MathJax load, and re-typeset this document once it resolves.
|
|
991
1015
|
*
|
|
@@ -1023,6 +1047,13 @@ export declare class Markdown extends UIComponent {
|
|
|
1023
1047
|
* stream is still open the next chunk re-applies a guess.
|
|
1024
1048
|
*/
|
|
1025
1049
|
private retypesetFromTokens;
|
|
1050
|
+
/**
|
|
1051
|
+
* Re-render the paragraph currently showing a guess from its own tokens, with
|
|
1052
|
+
* no overlay, and forget it.
|
|
1053
|
+
*
|
|
1054
|
+
* Idempotent and free when no guess is live, which is what lets `close()`,
|
|
1055
|
+
* `abort()`, and a mid-stream staleness check all call it unconditionally.
|
|
1056
|
+
*/
|
|
1026
1057
|
private unwindOptimisticTail;
|
|
1027
1058
|
/**
|
|
1028
1059
|
* Drop a guess that is no longer on the document's trailing paragraph.
|