@vectojs/markdown 0.11.0 → 0.13.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 +102 -0
- package/dist/index.js +175 -19
- package/dist/index.mjs +174 -19
- package/package.json +1 -1
package/dist/Markdown.d.ts
CHANGED
|
@@ -32,6 +32,42 @@ export interface MarkdownTheme {
|
|
|
32
32
|
/** Base font size in px. */
|
|
33
33
|
fontSize?: number;
|
|
34
34
|
}
|
|
35
|
+
/** A simple concrete container entity for nested layouts. */
|
|
36
|
+
declare class MarkdownContainer extends Entity {
|
|
37
|
+
isPointInside(_globalX: number, _globalY: number): boolean;
|
|
38
|
+
render(_r: any): void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One display formula: a `$$..$$` block or a closed ```` ```math ```` fence.
|
|
42
|
+
*
|
|
43
|
+
* A named class rather than a bare {@link MarkdownContainer} because the formula
|
|
44
|
+
* needs a stable handle, and after the switch to an inline object it has none:
|
|
45
|
+
* the typeset raster lives in a `paint` closure captured by the span, so removing
|
|
46
|
+
* the `Image` entity left nothing exposing either the source or the SVG bytes.
|
|
47
|
+
* Devtools, tests, and anything auditing what a formula actually rendered all
|
|
48
|
+
* want that. `markstream-vue` reaches the same conclusion from the DOM side and
|
|
49
|
+
* publishes `data-markstream-mode` on its math node for the same reason.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately carries no typeset-vs-source flag. A formula MathJax has not
|
|
52
|
+
* converted yet renders as a bare {@link CodeBlock} of its TeX, which this class
|
|
53
|
+
* does not wrap — wrapping it would put a container between `content` and a
|
|
54
|
+
* `CodeBlock` that the streamed `setCode` path locates by type. So a flag would
|
|
55
|
+
* have exactly one reachable value, which is the dead-API trap that cost CTX-0208
|
|
56
|
+
* a debugging pass. Add it together with wrapping the fallback, or not at all.
|
|
57
|
+
*/
|
|
58
|
+
export declare class MathBlock extends MarkdownContainer {
|
|
59
|
+
/**
|
|
60
|
+
* The TeX source, exactly as written between the delimiters.
|
|
61
|
+
*
|
|
62
|
+
* Also the projected text and the accessible name, so this is the one string a
|
|
63
|
+
* reader can find, select, and copy.
|
|
64
|
+
*/
|
|
65
|
+
readonly formula: string;
|
|
66
|
+
/** The `data:image/svg+xml` URI of the typeset glyphs. */
|
|
67
|
+
readonly svgUri: string;
|
|
68
|
+
constructor(formula: string, svgUri: string);
|
|
69
|
+
getDevtoolsDescriptor(): DevtoolsDescriptor;
|
|
70
|
+
}
|
|
35
71
|
/**
|
|
36
72
|
* A single self-rendering entity for fenced code blocks.
|
|
37
73
|
*
|
|
@@ -545,6 +581,71 @@ export declare class Markdown extends UIComponent {
|
|
|
545
581
|
private paragraphImage;
|
|
546
582
|
/** One table cell entity, shared by the render arm and the streamed-table path. */
|
|
547
583
|
private tableCellRichText;
|
|
584
|
+
/**
|
|
585
|
+
* Token types a list item's fast path can render as one `RichText`.
|
|
586
|
+
*
|
|
587
|
+
* An ALLOWLIST, deliberately, following `markstream-vue`'s
|
|
588
|
+
* `SIMPLE_INLINE_TYPES` (`SimpleInlineRenderer/simpleInline.ts:15-31`): a block
|
|
589
|
+
* type is excluded by OMISSION, so a token this renderer has never heard of
|
|
590
|
+
* falls out of the fast path automatically instead of being silently flattened
|
|
591
|
+
* to its raw text. A denylist fails the other way, and the failure is quiet —
|
|
592
|
+
* a formula painted as literal TeX rather than an error — which is why this
|
|
593
|
+
* defect survived so long.
|
|
594
|
+
*
|
|
595
|
+
* Deliberately small, because a list item's DIRECT children are far less varied
|
|
596
|
+
* than they look. Probed against marked 18.0.7: every inline construct
|
|
597
|
+
* (`strong`, `em`, `del`, `codespan`, `link`, `image`, `br`, `escape`, `html`,
|
|
598
|
+
* `inlineMath`) arrives nested one level DEEPER, inside a container whose own
|
|
599
|
+
* type is `text` — so a tight item's direct child list is `text` and nothing
|
|
600
|
+
* else. Listing those inline types here would be dead code.
|
|
601
|
+
*
|
|
602
|
+
* `space` and `checkbox` are included because both are inert here. A blank line
|
|
603
|
+
* between an item's paragraph and its block sibling produces a `space`, and
|
|
604
|
+
* marked unshifts a `checkbox` into every TIGHT GFM task item — the box itself
|
|
605
|
+
* is drawn from `item.task`/`item.checked` by `listItemSpans`, so the token
|
|
606
|
+
* renders nothing on its own. Omitting `checkbox` sent every task item down the
|
|
607
|
+
* block path and moved its marker into a nested entity, which broke four
|
|
608
|
+
* task-list assertions in `Markdown.test.ts`.
|
|
609
|
+
*/
|
|
610
|
+
private static readonly INLINE_ITEM_TOKENS;
|
|
611
|
+
/**
|
|
612
|
+
* Does this item consist purely of inline content?
|
|
613
|
+
*
|
|
614
|
+
* True keeps the single-`RichText` fast path, which is not merely an
|
|
615
|
+
* optimization: `updateStreamedList` reuses `stack.children[i]` by calling
|
|
616
|
+
* `setSpans` on it, so an item that becomes a `Stack` forfeits streamed reuse
|
|
617
|
+
* for its entire list. Only pay for a block container when an item holds a
|
|
618
|
+
* block.
|
|
619
|
+
*
|
|
620
|
+
* A lone `paragraph` counts as inline. A LOOSE list re-lexes every item's
|
|
621
|
+
* inline content from `text` to `paragraph` — adding one blank line anywhere
|
|
622
|
+
* flips `token.loose` for the whole list — so treating a single paragraph as a
|
|
623
|
+
* block would drop the fast path for every item of every loose list, the common
|
|
624
|
+
* shape in real prose, for no rendering benefit.
|
|
625
|
+
*/
|
|
626
|
+
private itemIsInlineOnly;
|
|
627
|
+
/**
|
|
628
|
+
* Build a list item that holds block-level children.
|
|
629
|
+
*
|
|
630
|
+
* The item becomes a vertical `Stack`: its leading inline run (carrying the
|
|
631
|
+
* marker) first, then every remaining child rendered through the same
|
|
632
|
+
* `renderToken` the document level uses, indented to clear the marker.
|
|
633
|
+
*
|
|
634
|
+
* Recursing rather than special-casing the types we know about is the point — a
|
|
635
|
+
* display formula, a fence, a table, a blockquote, a nested list, an `hr` and a
|
|
636
|
+
* second paragraph all render exactly as they would at indent 0, and a block
|
|
637
|
+
* type added later works here for free.
|
|
638
|
+
*
|
|
639
|
+
* Only the FIRST child can be the lead. Everything after it becomes a block,
|
|
640
|
+
* including a second `paragraph`: an item's two paragraphs are two blocks, and
|
|
641
|
+
* folding them into the lead run would concatenate them into one line with no
|
|
642
|
+
* separation.
|
|
643
|
+
*
|
|
644
|
+
* The lead `RichText` is emitted even when the item has no inline text, because
|
|
645
|
+
* it carries the marker — an item that is nothing but a formula still shows its
|
|
646
|
+
* bullet or ordinal.
|
|
647
|
+
*/
|
|
648
|
+
private listItemBlockStack;
|
|
548
649
|
private listItemSpans;
|
|
549
650
|
/** Construct the `RichText` for one list item. */
|
|
550
651
|
private listItemRichText;
|
|
@@ -786,3 +887,4 @@ export declare class Markdown extends UIComponent {
|
|
|
786
887
|
/** Structural — children draw themselves. */
|
|
787
888
|
render(_r: IRenderer): void;
|
|
788
889
|
}
|
|
890
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
CodeBlock: () => CodeBlock,
|
|
34
34
|
Markdown: () => Markdown,
|
|
35
|
+
MathBlock: () => MathBlock,
|
|
35
36
|
codeAtlas: () => codeAtlas,
|
|
36
37
|
codeAtlasStats: () => codeAtlasStats,
|
|
37
38
|
isMathJaxReady: () => isMathJaxReady,
|
|
@@ -871,6 +872,33 @@ var MarkdownContainer = class extends import_core.Entity {
|
|
|
871
872
|
render(_r) {
|
|
872
873
|
}
|
|
873
874
|
};
|
|
875
|
+
var MathBlock = class extends MarkdownContainer {
|
|
876
|
+
/**
|
|
877
|
+
* The TeX source, exactly as written between the delimiters.
|
|
878
|
+
*
|
|
879
|
+
* Also the projected text and the accessible name, so this is the one string a
|
|
880
|
+
* reader can find, select, and copy.
|
|
881
|
+
*/
|
|
882
|
+
formula;
|
|
883
|
+
/** The `data:image/svg+xml` URI of the typeset glyphs. */
|
|
884
|
+
svgUri;
|
|
885
|
+
constructor(formula, svgUri) {
|
|
886
|
+
super();
|
|
887
|
+
this.formula = formula;
|
|
888
|
+
this.svgUri = svgUri;
|
|
889
|
+
}
|
|
890
|
+
getDevtoolsDescriptor() {
|
|
891
|
+
return {
|
|
892
|
+
kind: "MathBlock",
|
|
893
|
+
groups: [
|
|
894
|
+
{
|
|
895
|
+
label: "Math",
|
|
896
|
+
fields: [{ label: "formula", value: this.formula, readOnly: true }]
|
|
897
|
+
}
|
|
898
|
+
]
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
};
|
|
874
902
|
var KEYWORD_SETS = {
|
|
875
903
|
js: /* @__PURE__ */ new Set([
|
|
876
904
|
"const",
|
|
@@ -1531,7 +1559,7 @@ function renderInlineToRichText(tokens, fallbackText, font, color, maxWidth, the
|
|
|
1531
1559
|
onLinkClick
|
|
1532
1560
|
});
|
|
1533
1561
|
}
|
|
1534
|
-
var Markdown = class extends import_ui.UIComponent {
|
|
1562
|
+
var Markdown = class _Markdown extends import_ui.UIComponent {
|
|
1535
1563
|
content;
|
|
1536
1564
|
maxWidth;
|
|
1537
1565
|
theme;
|
|
@@ -2580,6 +2608,109 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
2580
2608
|
onLinkClick: this.onLinkClick
|
|
2581
2609
|
});
|
|
2582
2610
|
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Token types a list item's fast path can render as one `RichText`.
|
|
2613
|
+
*
|
|
2614
|
+
* An ALLOWLIST, deliberately, following `markstream-vue`'s
|
|
2615
|
+
* `SIMPLE_INLINE_TYPES` (`SimpleInlineRenderer/simpleInline.ts:15-31`): a block
|
|
2616
|
+
* type is excluded by OMISSION, so a token this renderer has never heard of
|
|
2617
|
+
* falls out of the fast path automatically instead of being silently flattened
|
|
2618
|
+
* to its raw text. A denylist fails the other way, and the failure is quiet —
|
|
2619
|
+
* a formula painted as literal TeX rather than an error — which is why this
|
|
2620
|
+
* defect survived so long.
|
|
2621
|
+
*
|
|
2622
|
+
* Deliberately small, because a list item's DIRECT children are far less varied
|
|
2623
|
+
* than they look. Probed against marked 18.0.7: every inline construct
|
|
2624
|
+
* (`strong`, `em`, `del`, `codespan`, `link`, `image`, `br`, `escape`, `html`,
|
|
2625
|
+
* `inlineMath`) arrives nested one level DEEPER, inside a container whose own
|
|
2626
|
+
* type is `text` — so a tight item's direct child list is `text` and nothing
|
|
2627
|
+
* else. Listing those inline types here would be dead code.
|
|
2628
|
+
*
|
|
2629
|
+
* `space` and `checkbox` are included because both are inert here. A blank line
|
|
2630
|
+
* between an item's paragraph and its block sibling produces a `space`, and
|
|
2631
|
+
* marked unshifts a `checkbox` into every TIGHT GFM task item — the box itself
|
|
2632
|
+
* is drawn from `item.task`/`item.checked` by `listItemSpans`, so the token
|
|
2633
|
+
* renders nothing on its own. Omitting `checkbox` sent every task item down the
|
|
2634
|
+
* block path and moved its marker into a nested entity, which broke four
|
|
2635
|
+
* task-list assertions in `Markdown.test.ts`.
|
|
2636
|
+
*/
|
|
2637
|
+
static INLINE_ITEM_TOKENS = /* @__PURE__ */ new Set([
|
|
2638
|
+
"text",
|
|
2639
|
+
"space",
|
|
2640
|
+
"checkbox"
|
|
2641
|
+
]);
|
|
2642
|
+
/**
|
|
2643
|
+
* Does this item consist purely of inline content?
|
|
2644
|
+
*
|
|
2645
|
+
* True keeps the single-`RichText` fast path, which is not merely an
|
|
2646
|
+
* optimization: `updateStreamedList` reuses `stack.children[i]` by calling
|
|
2647
|
+
* `setSpans` on it, so an item that becomes a `Stack` forfeits streamed reuse
|
|
2648
|
+
* for its entire list. Only pay for a block container when an item holds a
|
|
2649
|
+
* block.
|
|
2650
|
+
*
|
|
2651
|
+
* A lone `paragraph` counts as inline. A LOOSE list re-lexes every item's
|
|
2652
|
+
* inline content from `text` to `paragraph` — adding one blank line anywhere
|
|
2653
|
+
* flips `token.loose` for the whole list — so treating a single paragraph as a
|
|
2654
|
+
* block would drop the fast path for every item of every loose list, the common
|
|
2655
|
+
* shape in real prose, for no rendering benefit.
|
|
2656
|
+
*/
|
|
2657
|
+
itemIsInlineOnly(item) {
|
|
2658
|
+
const children = item.tokens;
|
|
2659
|
+
if (!children || children.length === 0) return true;
|
|
2660
|
+
if (children.length === 1 && children[0].type === "paragraph") return true;
|
|
2661
|
+
return children.every((child) => _Markdown.INLINE_ITEM_TOKENS.has(child.type));
|
|
2662
|
+
}
|
|
2663
|
+
/**
|
|
2664
|
+
* Build a list item that holds block-level children.
|
|
2665
|
+
*
|
|
2666
|
+
* The item becomes a vertical `Stack`: its leading inline run (carrying the
|
|
2667
|
+
* marker) first, then every remaining child rendered through the same
|
|
2668
|
+
* `renderToken` the document level uses, indented to clear the marker.
|
|
2669
|
+
*
|
|
2670
|
+
* Recursing rather than special-casing the types we know about is the point — a
|
|
2671
|
+
* display formula, a fence, a table, a blockquote, a nested list, an `hr` and a
|
|
2672
|
+
* second paragraph all render exactly as they would at indent 0, and a block
|
|
2673
|
+
* type added later works here for free.
|
|
2674
|
+
*
|
|
2675
|
+
* Only the FIRST child can be the lead. Everything after it becomes a block,
|
|
2676
|
+
* including a second `paragraph`: an item's two paragraphs are two blocks, and
|
|
2677
|
+
* folding them into the lead run would concatenate them into one line with no
|
|
2678
|
+
* separation.
|
|
2679
|
+
*
|
|
2680
|
+
* The lead `RichText` is emitted even when the item has no inline text, because
|
|
2681
|
+
* it carries the marker — an item that is nothing but a formula still shows its
|
|
2682
|
+
* bullet or ordinal.
|
|
2683
|
+
*/
|
|
2684
|
+
listItemBlockStack(token, index, availableWidth, t) {
|
|
2685
|
+
const item = token.items[index];
|
|
2686
|
+
const children = item.tokens ?? [];
|
|
2687
|
+
const stack = new import_ui.Stack({ direction: "vertical", gap: 4 });
|
|
2688
|
+
const first = children[0];
|
|
2689
|
+
const leadChildren = first && (first.type === "text" || first.type === "paragraph") ? [first] : [];
|
|
2690
|
+
const leadToken = {
|
|
2691
|
+
...token,
|
|
2692
|
+
items: token.items.map((it, i) => i === index ? { ...it, tokens: leadChildren } : it)
|
|
2693
|
+
};
|
|
2694
|
+
stack.add(this.listItemRichText(leadToken, index, availableWidth, t));
|
|
2695
|
+
const indent = Math.round(t.fontSize);
|
|
2696
|
+
const childMetrics = {
|
|
2697
|
+
marginBefore: 0,
|
|
2698
|
+
marginAfter: 0,
|
|
2699
|
+
indentStart: indent,
|
|
2700
|
+
availableWidth: Math.max(1, availableWidth - indent)
|
|
2701
|
+
};
|
|
2702
|
+
for (let i = leadChildren.length; i < children.length; i++) {
|
|
2703
|
+
const el = this.renderTokenWithMetrics(children[i], childMetrics);
|
|
2704
|
+
if (!el) continue;
|
|
2705
|
+
const wrapper = new MarkdownContainer();
|
|
2706
|
+
el.x = indent;
|
|
2707
|
+
wrapper.add(el);
|
|
2708
|
+
wrapper.width = el.width + indent;
|
|
2709
|
+
wrapper.height = el.height;
|
|
2710
|
+
stack.add(wrapper);
|
|
2711
|
+
}
|
|
2712
|
+
return stack;
|
|
2713
|
+
}
|
|
2583
2714
|
listItemSpans(token, index) {
|
|
2584
2715
|
const item = token.items[index];
|
|
2585
2716
|
const num = Number(token.start ?? 1) + index;
|
|
@@ -2659,18 +2790,23 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
2659
2790
|
const lastRetained = oldToken.items.length - 1;
|
|
2660
2791
|
for (let i = 0; i < lastRetained; i++) {
|
|
2661
2792
|
if (oldToken.items[i].text !== newToken.items[i].text) return false;
|
|
2793
|
+
const isStack = stack.children[i] instanceof import_ui.Stack;
|
|
2794
|
+
if (isStack !== !this.itemIsInlineOnly(newToken.items[i])) return false;
|
|
2662
2795
|
}
|
|
2663
2796
|
const availableWidth = this.activeBlockMetrics?.availableWidth ?? this.maxWidth;
|
|
2664
2797
|
const t = this.theme;
|
|
2665
2798
|
const tailEntity = stack.children[lastRetained];
|
|
2666
2799
|
if (oldToken.items[lastRetained].text !== newToken.items[lastRetained].text) {
|
|
2800
|
+
if (!this.itemIsInlineOnly(newToken.items[lastRetained])) return false;
|
|
2667
2801
|
if (!("setSpans" in tailEntity)) return false;
|
|
2668
2802
|
tailEntity.setSpans(
|
|
2669
2803
|
this.listItemSpans(newToken, lastRetained)
|
|
2670
2804
|
);
|
|
2671
2805
|
}
|
|
2672
2806
|
for (let i = oldToken.items.length; i < newToken.items.length; i++) {
|
|
2673
|
-
stack.add(
|
|
2807
|
+
stack.add(
|
|
2808
|
+
this.itemIsInlineOnly(newToken.items[i]) ? this.listItemRichText(newToken, i, availableWidth, t) : this.listItemBlockStack(newToken, i, availableWidth, t)
|
|
2809
|
+
);
|
|
2674
2810
|
}
|
|
2675
2811
|
const last = stack.children.at(-1);
|
|
2676
2812
|
if (last) stack.resizeLastChild(last);
|
|
@@ -3285,23 +3421,40 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
3285
3421
|
if (!mathData) return null;
|
|
3286
3422
|
const intrinsicW = exToPx(mathData.widthEx, t.fontSize);
|
|
3287
3423
|
const intrinsicH = exToPx(mathData.heightEx, t.fontSize);
|
|
3288
|
-
const
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3424
|
+
const scale = Math.min(1, availableWidth / intrinsicW);
|
|
3425
|
+
const width = intrinsicW * scale;
|
|
3426
|
+
const height = intrinsicH * scale;
|
|
3427
|
+
const uri = mathData.uri;
|
|
3428
|
+
const math = new import_ui.RichText(
|
|
3429
|
+
[
|
|
3430
|
+
{
|
|
3431
|
+
text: import_core.OBJECT_REPLACEMENT,
|
|
3432
|
+
object: {
|
|
3433
|
+
width,
|
|
3434
|
+
height,
|
|
3435
|
+
// The TeX source is what a reader copies and what a screen reader
|
|
3436
|
+
// announces. KaTeX's dual-layer contract carries the same string in an
|
|
3437
|
+
// `<annotation encoding="application/x-tex">`; here the projection is
|
|
3438
|
+
// the semantic layer, so one copy of the source serves both.
|
|
3439
|
+
alt: formula,
|
|
3440
|
+
paint: (surface, box) => paintInlineMath(uri, surface, box)
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
],
|
|
3444
|
+
{
|
|
3445
|
+
font: `${t.fontSize}px ${t.bodyFont}`,
|
|
3446
|
+
color: t.textColor,
|
|
3447
|
+
maxWidth: availableWidth,
|
|
3448
|
+
selectable: this.selectable
|
|
3297
3449
|
}
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
wrapper.
|
|
3304
|
-
wrapper.
|
|
3450
|
+
);
|
|
3451
|
+
this.subscribeInlineMathRepaint();
|
|
3452
|
+
const wrapper = new MathBlock(formula, uri);
|
|
3453
|
+
math.x = 16;
|
|
3454
|
+
math.y = 8;
|
|
3455
|
+
wrapper.add(math);
|
|
3456
|
+
wrapper.width = width + 16;
|
|
3457
|
+
wrapper.height = height + 16;
|
|
3305
3458
|
return wrapper;
|
|
3306
3459
|
}
|
|
3307
3460
|
renderToken(token) {
|
|
@@ -3434,7 +3587,9 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
3434
3587
|
const listToken = token;
|
|
3435
3588
|
const listStack = new import_ui.Stack({ direction: "vertical", gap: 6 });
|
|
3436
3589
|
for (let i = 0; i < listToken.items.length; i++) {
|
|
3437
|
-
listStack.add(
|
|
3590
|
+
listStack.add(
|
|
3591
|
+
this.itemIsInlineOnly(listToken.items[i]) ? this.listItemRichText(listToken, i, availableWidth, t) : this.listItemBlockStack(listToken, i, availableWidth, t)
|
|
3592
|
+
);
|
|
3438
3593
|
}
|
|
3439
3594
|
return listStack;
|
|
3440
3595
|
}
|
|
@@ -3497,6 +3652,7 @@ var Markdown = class extends import_ui.UIComponent {
|
|
|
3497
3652
|
0 && (module.exports = {
|
|
3498
3653
|
CodeBlock,
|
|
3499
3654
|
Markdown,
|
|
3655
|
+
MathBlock,
|
|
3500
3656
|
codeAtlas,
|
|
3501
3657
|
codeAtlasStats,
|
|
3502
3658
|
isMathJaxReady,
|
package/dist/index.mjs
CHANGED
|
@@ -839,6 +839,33 @@ var MarkdownContainer = class extends Entity {
|
|
|
839
839
|
render(_r) {
|
|
840
840
|
}
|
|
841
841
|
};
|
|
842
|
+
var MathBlock = class extends MarkdownContainer {
|
|
843
|
+
/**
|
|
844
|
+
* The TeX source, exactly as written between the delimiters.
|
|
845
|
+
*
|
|
846
|
+
* Also the projected text and the accessible name, so this is the one string a
|
|
847
|
+
* reader can find, select, and copy.
|
|
848
|
+
*/
|
|
849
|
+
formula;
|
|
850
|
+
/** The `data:image/svg+xml` URI of the typeset glyphs. */
|
|
851
|
+
svgUri;
|
|
852
|
+
constructor(formula, svgUri) {
|
|
853
|
+
super();
|
|
854
|
+
this.formula = formula;
|
|
855
|
+
this.svgUri = svgUri;
|
|
856
|
+
}
|
|
857
|
+
getDevtoolsDescriptor() {
|
|
858
|
+
return {
|
|
859
|
+
kind: "MathBlock",
|
|
860
|
+
groups: [
|
|
861
|
+
{
|
|
862
|
+
label: "Math",
|
|
863
|
+
fields: [{ label: "formula", value: this.formula, readOnly: true }]
|
|
864
|
+
}
|
|
865
|
+
]
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
};
|
|
842
869
|
var KEYWORD_SETS = {
|
|
843
870
|
js: /* @__PURE__ */ new Set([
|
|
844
871
|
"const",
|
|
@@ -1499,7 +1526,7 @@ function renderInlineToRichText(tokens, fallbackText, font, color, maxWidth, the
|
|
|
1499
1526
|
onLinkClick
|
|
1500
1527
|
});
|
|
1501
1528
|
}
|
|
1502
|
-
var Markdown = class extends UIComponent {
|
|
1529
|
+
var Markdown = class _Markdown extends UIComponent {
|
|
1503
1530
|
content;
|
|
1504
1531
|
maxWidth;
|
|
1505
1532
|
theme;
|
|
@@ -2548,6 +2575,109 @@ var Markdown = class extends UIComponent {
|
|
|
2548
2575
|
onLinkClick: this.onLinkClick
|
|
2549
2576
|
});
|
|
2550
2577
|
}
|
|
2578
|
+
/**
|
|
2579
|
+
* Token types a list item's fast path can render as one `RichText`.
|
|
2580
|
+
*
|
|
2581
|
+
* An ALLOWLIST, deliberately, following `markstream-vue`'s
|
|
2582
|
+
* `SIMPLE_INLINE_TYPES` (`SimpleInlineRenderer/simpleInline.ts:15-31`): a block
|
|
2583
|
+
* type is excluded by OMISSION, so a token this renderer has never heard of
|
|
2584
|
+
* falls out of the fast path automatically instead of being silently flattened
|
|
2585
|
+
* to its raw text. A denylist fails the other way, and the failure is quiet —
|
|
2586
|
+
* a formula painted as literal TeX rather than an error — which is why this
|
|
2587
|
+
* defect survived so long.
|
|
2588
|
+
*
|
|
2589
|
+
* Deliberately small, because a list item's DIRECT children are far less varied
|
|
2590
|
+
* than they look. Probed against marked 18.0.7: every inline construct
|
|
2591
|
+
* (`strong`, `em`, `del`, `codespan`, `link`, `image`, `br`, `escape`, `html`,
|
|
2592
|
+
* `inlineMath`) arrives nested one level DEEPER, inside a container whose own
|
|
2593
|
+
* type is `text` — so a tight item's direct child list is `text` and nothing
|
|
2594
|
+
* else. Listing those inline types here would be dead code.
|
|
2595
|
+
*
|
|
2596
|
+
* `space` and `checkbox` are included because both are inert here. A blank line
|
|
2597
|
+
* between an item's paragraph and its block sibling produces a `space`, and
|
|
2598
|
+
* marked unshifts a `checkbox` into every TIGHT GFM task item — the box itself
|
|
2599
|
+
* is drawn from `item.task`/`item.checked` by `listItemSpans`, so the token
|
|
2600
|
+
* renders nothing on its own. Omitting `checkbox` sent every task item down the
|
|
2601
|
+
* block path and moved its marker into a nested entity, which broke four
|
|
2602
|
+
* task-list assertions in `Markdown.test.ts`.
|
|
2603
|
+
*/
|
|
2604
|
+
static INLINE_ITEM_TOKENS = /* @__PURE__ */ new Set([
|
|
2605
|
+
"text",
|
|
2606
|
+
"space",
|
|
2607
|
+
"checkbox"
|
|
2608
|
+
]);
|
|
2609
|
+
/**
|
|
2610
|
+
* Does this item consist purely of inline content?
|
|
2611
|
+
*
|
|
2612
|
+
* True keeps the single-`RichText` fast path, which is not merely an
|
|
2613
|
+
* optimization: `updateStreamedList` reuses `stack.children[i]` by calling
|
|
2614
|
+
* `setSpans` on it, so an item that becomes a `Stack` forfeits streamed reuse
|
|
2615
|
+
* for its entire list. Only pay for a block container when an item holds a
|
|
2616
|
+
* block.
|
|
2617
|
+
*
|
|
2618
|
+
* A lone `paragraph` counts as inline. A LOOSE list re-lexes every item's
|
|
2619
|
+
* inline content from `text` to `paragraph` — adding one blank line anywhere
|
|
2620
|
+
* flips `token.loose` for the whole list — so treating a single paragraph as a
|
|
2621
|
+
* block would drop the fast path for every item of every loose list, the common
|
|
2622
|
+
* shape in real prose, for no rendering benefit.
|
|
2623
|
+
*/
|
|
2624
|
+
itemIsInlineOnly(item) {
|
|
2625
|
+
const children = item.tokens;
|
|
2626
|
+
if (!children || children.length === 0) return true;
|
|
2627
|
+
if (children.length === 1 && children[0].type === "paragraph") return true;
|
|
2628
|
+
return children.every((child) => _Markdown.INLINE_ITEM_TOKENS.has(child.type));
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* Build a list item that holds block-level children.
|
|
2632
|
+
*
|
|
2633
|
+
* The item becomes a vertical `Stack`: its leading inline run (carrying the
|
|
2634
|
+
* marker) first, then every remaining child rendered through the same
|
|
2635
|
+
* `renderToken` the document level uses, indented to clear the marker.
|
|
2636
|
+
*
|
|
2637
|
+
* Recursing rather than special-casing the types we know about is the point — a
|
|
2638
|
+
* display formula, a fence, a table, a blockquote, a nested list, an `hr` and a
|
|
2639
|
+
* second paragraph all render exactly as they would at indent 0, and a block
|
|
2640
|
+
* type added later works here for free.
|
|
2641
|
+
*
|
|
2642
|
+
* Only the FIRST child can be the lead. Everything after it becomes a block,
|
|
2643
|
+
* including a second `paragraph`: an item's two paragraphs are two blocks, and
|
|
2644
|
+
* folding them into the lead run would concatenate them into one line with no
|
|
2645
|
+
* separation.
|
|
2646
|
+
*
|
|
2647
|
+
* The lead `RichText` is emitted even when the item has no inline text, because
|
|
2648
|
+
* it carries the marker — an item that is nothing but a formula still shows its
|
|
2649
|
+
* bullet or ordinal.
|
|
2650
|
+
*/
|
|
2651
|
+
listItemBlockStack(token, index, availableWidth, t) {
|
|
2652
|
+
const item = token.items[index];
|
|
2653
|
+
const children = item.tokens ?? [];
|
|
2654
|
+
const stack = new Stack({ direction: "vertical", gap: 4 });
|
|
2655
|
+
const first = children[0];
|
|
2656
|
+
const leadChildren = first && (first.type === "text" || first.type === "paragraph") ? [first] : [];
|
|
2657
|
+
const leadToken = {
|
|
2658
|
+
...token,
|
|
2659
|
+
items: token.items.map((it, i) => i === index ? { ...it, tokens: leadChildren } : it)
|
|
2660
|
+
};
|
|
2661
|
+
stack.add(this.listItemRichText(leadToken, index, availableWidth, t));
|
|
2662
|
+
const indent = Math.round(t.fontSize);
|
|
2663
|
+
const childMetrics = {
|
|
2664
|
+
marginBefore: 0,
|
|
2665
|
+
marginAfter: 0,
|
|
2666
|
+
indentStart: indent,
|
|
2667
|
+
availableWidth: Math.max(1, availableWidth - indent)
|
|
2668
|
+
};
|
|
2669
|
+
for (let i = leadChildren.length; i < children.length; i++) {
|
|
2670
|
+
const el = this.renderTokenWithMetrics(children[i], childMetrics);
|
|
2671
|
+
if (!el) continue;
|
|
2672
|
+
const wrapper = new MarkdownContainer();
|
|
2673
|
+
el.x = indent;
|
|
2674
|
+
wrapper.add(el);
|
|
2675
|
+
wrapper.width = el.width + indent;
|
|
2676
|
+
wrapper.height = el.height;
|
|
2677
|
+
stack.add(wrapper);
|
|
2678
|
+
}
|
|
2679
|
+
return stack;
|
|
2680
|
+
}
|
|
2551
2681
|
listItemSpans(token, index) {
|
|
2552
2682
|
const item = token.items[index];
|
|
2553
2683
|
const num = Number(token.start ?? 1) + index;
|
|
@@ -2627,18 +2757,23 @@ var Markdown = class extends UIComponent {
|
|
|
2627
2757
|
const lastRetained = oldToken.items.length - 1;
|
|
2628
2758
|
for (let i = 0; i < lastRetained; i++) {
|
|
2629
2759
|
if (oldToken.items[i].text !== newToken.items[i].text) return false;
|
|
2760
|
+
const isStack = stack.children[i] instanceof Stack;
|
|
2761
|
+
if (isStack !== !this.itemIsInlineOnly(newToken.items[i])) return false;
|
|
2630
2762
|
}
|
|
2631
2763
|
const availableWidth = this.activeBlockMetrics?.availableWidth ?? this.maxWidth;
|
|
2632
2764
|
const t = this.theme;
|
|
2633
2765
|
const tailEntity = stack.children[lastRetained];
|
|
2634
2766
|
if (oldToken.items[lastRetained].text !== newToken.items[lastRetained].text) {
|
|
2767
|
+
if (!this.itemIsInlineOnly(newToken.items[lastRetained])) return false;
|
|
2635
2768
|
if (!("setSpans" in tailEntity)) return false;
|
|
2636
2769
|
tailEntity.setSpans(
|
|
2637
2770
|
this.listItemSpans(newToken, lastRetained)
|
|
2638
2771
|
);
|
|
2639
2772
|
}
|
|
2640
2773
|
for (let i = oldToken.items.length; i < newToken.items.length; i++) {
|
|
2641
|
-
stack.add(
|
|
2774
|
+
stack.add(
|
|
2775
|
+
this.itemIsInlineOnly(newToken.items[i]) ? this.listItemRichText(newToken, i, availableWidth, t) : this.listItemBlockStack(newToken, i, availableWidth, t)
|
|
2776
|
+
);
|
|
2642
2777
|
}
|
|
2643
2778
|
const last = stack.children.at(-1);
|
|
2644
2779
|
if (last) stack.resizeLastChild(last);
|
|
@@ -3253,23 +3388,40 @@ var Markdown = class extends UIComponent {
|
|
|
3253
3388
|
if (!mathData) return null;
|
|
3254
3389
|
const intrinsicW = exToPx(mathData.widthEx, t.fontSize);
|
|
3255
3390
|
const intrinsicH = exToPx(mathData.heightEx, t.fontSize);
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3391
|
+
const scale = Math.min(1, availableWidth / intrinsicW);
|
|
3392
|
+
const width = intrinsicW * scale;
|
|
3393
|
+
const height = intrinsicH * scale;
|
|
3394
|
+
const uri = mathData.uri;
|
|
3395
|
+
const math = new RichText(
|
|
3396
|
+
[
|
|
3397
|
+
{
|
|
3398
|
+
text: OBJECT_REPLACEMENT,
|
|
3399
|
+
object: {
|
|
3400
|
+
width,
|
|
3401
|
+
height,
|
|
3402
|
+
// The TeX source is what a reader copies and what a screen reader
|
|
3403
|
+
// announces. KaTeX's dual-layer contract carries the same string in an
|
|
3404
|
+
// `<annotation encoding="application/x-tex">`; here the projection is
|
|
3405
|
+
// the semantic layer, so one copy of the source serves both.
|
|
3406
|
+
alt: formula,
|
|
3407
|
+
paint: (surface, box) => paintInlineMath(uri, surface, box)
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
],
|
|
3411
|
+
{
|
|
3412
|
+
font: `${t.fontSize}px ${t.bodyFont}`,
|
|
3413
|
+
color: t.textColor,
|
|
3414
|
+
maxWidth: availableWidth,
|
|
3415
|
+
selectable: this.selectable
|
|
3265
3416
|
}
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
wrapper.
|
|
3272
|
-
wrapper.
|
|
3417
|
+
);
|
|
3418
|
+
this.subscribeInlineMathRepaint();
|
|
3419
|
+
const wrapper = new MathBlock(formula, uri);
|
|
3420
|
+
math.x = 16;
|
|
3421
|
+
math.y = 8;
|
|
3422
|
+
wrapper.add(math);
|
|
3423
|
+
wrapper.width = width + 16;
|
|
3424
|
+
wrapper.height = height + 16;
|
|
3273
3425
|
return wrapper;
|
|
3274
3426
|
}
|
|
3275
3427
|
renderToken(token) {
|
|
@@ -3402,7 +3554,9 @@ var Markdown = class extends UIComponent {
|
|
|
3402
3554
|
const listToken = token;
|
|
3403
3555
|
const listStack = new Stack({ direction: "vertical", gap: 6 });
|
|
3404
3556
|
for (let i = 0; i < listToken.items.length; i++) {
|
|
3405
|
-
listStack.add(
|
|
3557
|
+
listStack.add(
|
|
3558
|
+
this.itemIsInlineOnly(listToken.items[i]) ? this.listItemRichText(listToken, i, availableWidth, t) : this.listItemBlockStack(listToken, i, availableWidth, t)
|
|
3559
|
+
);
|
|
3406
3560
|
}
|
|
3407
3561
|
return listStack;
|
|
3408
3562
|
}
|
|
@@ -3464,6 +3618,7 @@ var Markdown = class extends UIComponent {
|
|
|
3464
3618
|
export {
|
|
3465
3619
|
CodeBlock,
|
|
3466
3620
|
Markdown,
|
|
3621
|
+
MathBlock,
|
|
3467
3622
|
codeAtlas,
|
|
3468
3623
|
codeAtlasStats,
|
|
3469
3624
|
isMathJaxReady,
|