@copilotkit/markdown 2.0.0-rc.19 → 2.0.0-rc.21
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 +24 -7
- package/dist/dom/index.cjs +255 -0
- package/dist/dom/index.cjs.map +1 -0
- package/dist/dom/index.d.cts +93 -0
- package/dist/dom/index.d.cts.map +1 -0
- package/dist/dom/index.d.mts +93 -0
- package/dist/dom/index.d.mts.map +1 -0
- package/dist/dom/index.mjs +254 -0
- package/dist/dom/index.mjs.map +1 -0
- package/dist/index.cjs +522 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +271 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +271 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +512 -12
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.cjs +66 -458
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +13 -22
- package/dist/react/index.d.cts.map +1 -1
- package/dist/react/index.d.mts +13 -22
- package/dist/react/index.d.mts.map +1 -1
- package/dist/react/index.mjs +67 -459
- package/dist/react/index.mjs.map +1 -1
- package/dist/react-native/index.cjs.map +1 -1
- package/dist/react-native/index.d.cts.map +1 -1
- package/dist/react-native/index.d.mts.map +1 -1
- package/dist/react-native/index.mjs.map +1 -1
- package/dist/vue/index.cjs +86 -199
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.d.cts +46 -16
- package/dist/vue/index.d.cts.map +1 -1
- package/dist/vue/index.d.mts +46 -16
- package/dist/vue/index.d.mts.map +1 -1
- package/dist/vue/index.mjs +88 -201
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +6 -2
package/dist/index.cjs
CHANGED
|
@@ -7,7 +7,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
7
7
|
const DEFAULT_OPTIONS = {
|
|
8
8
|
segmenter: true,
|
|
9
9
|
enableTables: true,
|
|
10
|
-
enableAutolinks: true
|
|
10
|
+
enableAutolinks: true,
|
|
11
|
+
enableMath: false
|
|
11
12
|
};
|
|
12
13
|
/**
|
|
13
14
|
* Merges user-provided parser options with defaults.
|
|
@@ -19,7 +20,8 @@ function normalizeOptions(options) {
|
|
|
19
20
|
return {
|
|
20
21
|
segmenter: options?.segmenter ?? DEFAULT_OPTIONS.segmenter,
|
|
21
22
|
enableTables: options?.enableTables ?? DEFAULT_OPTIONS.enableTables,
|
|
22
|
-
enableAutolinks: options?.enableAutolinks ?? DEFAULT_OPTIONS.enableAutolinks
|
|
23
|
+
enableAutolinks: options?.enableAutolinks ?? DEFAULT_OPTIONS.enableAutolinks,
|
|
24
|
+
enableMath: options?.enableMath ?? DEFAULT_OPTIONS.enableMath
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
@@ -665,6 +667,13 @@ function parseInline(text, absoluteStart, path, context) {
|
|
|
665
667
|
i = code.next;
|
|
666
668
|
continue;
|
|
667
669
|
}
|
|
670
|
+
const math = context.options.enableMath ? parseMathInline(text, i, absoluteStart, path, nodes.length) : null;
|
|
671
|
+
if (math) {
|
|
672
|
+
flushTextBuffer();
|
|
673
|
+
nodes.push(math.node);
|
|
674
|
+
i = math.next;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
668
677
|
const strong = parseDelimitedInline(text, i, "**", "strong", absoluteStart, path, nodes.length, context, citations, warnings, hasWarnedSegmenterUnavailable) ?? parseDelimitedInline(text, i, "__", "strong", absoluteStart, path, nodes.length, context, citations, warnings, hasWarnedSegmenterUnavailable);
|
|
669
678
|
if (strong) {
|
|
670
679
|
flushTextBuffer();
|
|
@@ -1016,6 +1025,32 @@ function parseInlineCode(text, at, start, path, index) {
|
|
|
1016
1025
|
next: close + len
|
|
1017
1026
|
};
|
|
1018
1027
|
}
|
|
1028
|
+
const DISPLAY_MATH = /^\$\$([\s\S]+?)\$\$/;
|
|
1029
|
+
const INLINE_MATH = /^\$(?!\s)([^$\n]*?\S)\$(?!\d)/;
|
|
1030
|
+
function parseMathInline(text, at, start, path, index) {
|
|
1031
|
+
if (text[at] !== "$") return null;
|
|
1032
|
+
const rest = text.slice(at);
|
|
1033
|
+
const display = DISPLAY_MATH.exec(rest);
|
|
1034
|
+
const match = display ?? INLINE_MATH.exec(rest);
|
|
1035
|
+
if (!match) return null;
|
|
1036
|
+
return {
|
|
1037
|
+
node: {
|
|
1038
|
+
path: `${path}.${index}`,
|
|
1039
|
+
type: "math",
|
|
1040
|
+
range: {
|
|
1041
|
+
start: start + at,
|
|
1042
|
+
end: start + at + match[0].length
|
|
1043
|
+
},
|
|
1044
|
+
closed: true,
|
|
1045
|
+
props: {
|
|
1046
|
+
text: match[1],
|
|
1047
|
+
display: display !== null
|
|
1048
|
+
},
|
|
1049
|
+
children: []
|
|
1050
|
+
},
|
|
1051
|
+
next: at + match[0].length
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1019
1054
|
function parseDelimitedInline(text, at, delimiter, type, start, path, index, context, citations, warnings, hasWarnedSegmenterUnavailable) {
|
|
1020
1055
|
if (!text.startsWith(delimiter, at)) return null;
|
|
1021
1056
|
const close = text.indexOf(delimiter, at + delimiter.length);
|
|
@@ -1072,6 +1107,11 @@ function parseDelimitedInline(text, at, delimiter, type, start, path, index, con
|
|
|
1072
1107
|
//#endregion
|
|
1073
1108
|
//#region src/parser/block-parser.ts
|
|
1074
1109
|
/**
|
|
1110
|
+
* GFM task-list marker at the start of a list item: `[ ]`, `[x]` or `[X]`,
|
|
1111
|
+
* followed by whitespace and then content.
|
|
1112
|
+
*/
|
|
1113
|
+
const TASK_MARKER = /^\[([ xX])\][ \t]+(?=\S)/;
|
|
1114
|
+
/**
|
|
1075
1115
|
* Parses a contiguous range of source lines into block-level draft nodes.
|
|
1076
1116
|
*
|
|
1077
1117
|
* @param lines - Line-split source with offsets.
|
|
@@ -1344,9 +1384,10 @@ function parseList(lines, from, to, path, context, ordered) {
|
|
|
1344
1384
|
const marker = matchListItem(lines[i].text);
|
|
1345
1385
|
if (!marker || marker.ordered !== ordered) break;
|
|
1346
1386
|
if (ordered && listStartNumber === null) listStartNumber = marker.start;
|
|
1387
|
+
const task = TASK_MARKER.exec(marker.content);
|
|
1347
1388
|
const contentLines = [{
|
|
1348
1389
|
...lines[i],
|
|
1349
|
-
text: marker.content
|
|
1390
|
+
text: task ? marker.content.slice(task[0].length) : marker.content
|
|
1350
1391
|
}];
|
|
1351
1392
|
const contentIndent = marker.contentIndent;
|
|
1352
1393
|
i += 1;
|
|
@@ -1401,7 +1442,7 @@ function parseList(lines, from, to, path, context, ordered) {
|
|
|
1401
1442
|
end: contentLines[contentLines.length - 1].end
|
|
1402
1443
|
},
|
|
1403
1444
|
closed: context.isComplete || contentLines[contentLines.length - 1].hasNewline,
|
|
1404
|
-
props: {},
|
|
1445
|
+
props: task ? { checked: task[1] !== " " } : {},
|
|
1405
1446
|
children: [paragraph]
|
|
1406
1447
|
});
|
|
1407
1448
|
citations = parsedInline.citations;
|
|
@@ -1676,11 +1717,19 @@ function opToAstNode(op, id, parentId, children) {
|
|
|
1676
1717
|
tight: op.props["tight"],
|
|
1677
1718
|
children
|
|
1678
1719
|
};
|
|
1679
|
-
case "list-item":
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1720
|
+
case "list-item": {
|
|
1721
|
+
const checked = op.props["checked"];
|
|
1722
|
+
return checked === void 0 ? {
|
|
1723
|
+
...base,
|
|
1724
|
+
type: "list-item",
|
|
1725
|
+
children
|
|
1726
|
+
} : {
|
|
1727
|
+
...base,
|
|
1728
|
+
type: "list-item",
|
|
1729
|
+
checked,
|
|
1730
|
+
children
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1684
1733
|
case "code-block": return {
|
|
1685
1734
|
...base,
|
|
1686
1735
|
type: "code-block",
|
|
@@ -1764,6 +1813,12 @@ function opToAstNode(op, id, parentId, children) {
|
|
|
1764
1813
|
url: op.props["url"],
|
|
1765
1814
|
text: op.props["text"]
|
|
1766
1815
|
};
|
|
1816
|
+
case "math": return {
|
|
1817
|
+
...base,
|
|
1818
|
+
type: "math",
|
|
1819
|
+
text: op.props["text"],
|
|
1820
|
+
display: op.props["display"]
|
|
1821
|
+
};
|
|
1767
1822
|
case "citation": return {
|
|
1768
1823
|
...base,
|
|
1769
1824
|
type: "citation",
|
|
@@ -2002,12 +2057,14 @@ function isProduction() {
|
|
|
2002
2057
|
*
|
|
2003
2058
|
* No-op in production and after the first warning. Only the built-in default
|
|
2004
2059
|
* renderers call this — a custom `markdownRenderer` never triggers it.
|
|
2060
|
+
* Pass `{ rendersMath: true }` from a default renderer that typesets math, so
|
|
2061
|
+
* only code blocks are flagged.
|
|
2005
2062
|
*
|
|
2006
2063
|
* @internal
|
|
2007
2064
|
*/
|
|
2008
|
-
function warnUnsupportedRichSyntaxOnce(content) {
|
|
2065
|
+
function warnUnsupportedRichSyntaxOnce(content, { rendersMath = false } = {}) {
|
|
2009
2066
|
if (warned || !content || isProduction()) return;
|
|
2010
|
-
const hasMath = BLOCK_MATH.test(content) || BRACKET_MATH.test(content);
|
|
2067
|
+
const hasMath = !rendersMath && (BLOCK_MATH.test(content) || BRACKET_MATH.test(content));
|
|
2011
2068
|
const hasHighlightableCode = HIGHLIGHTABLE_CODE.test(content);
|
|
2012
2069
|
if (!hasMath && !hasHighlightableCode) return;
|
|
2013
2070
|
warned = true;
|
|
@@ -2016,8 +2073,462 @@ function warnUnsupportedRichSyntaxOnce(content) {
|
|
|
2016
2073
|
}
|
|
2017
2074
|
|
|
2018
2075
|
//#endregion
|
|
2076
|
+
//#region src/render/parser-session.ts
|
|
2077
|
+
function getSegmenterKey(segmenter) {
|
|
2078
|
+
if (segmenter === true || segmenter === false) return String(segmenter);
|
|
2079
|
+
return `object:${segmenter.locale ?? ""}:${segmenter.granularity ?? "word"}`;
|
|
2080
|
+
}
|
|
2081
|
+
function getOptionsKey(options) {
|
|
2082
|
+
return `${getSegmenterKey(options.segmenter)}|tables:${String(options.enableTables)}|autolinks:${String(options.enableAutolinks)}|math:${String(options.enableMath)}`;
|
|
2083
|
+
}
|
|
2084
|
+
function parseFullText(text, options, isCompleteInput) {
|
|
2085
|
+
const initialState = createStreamingMarkdownParserState(options);
|
|
2086
|
+
const parsedState = text.length > 0 ? parseStreamingMarkdownChunk(initialState, text) : initialState;
|
|
2087
|
+
return isCompleteInput ? finalizeStreamingMarkdown(parsedState) : parsedState;
|
|
2088
|
+
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Returns the parser session for `text`, reusing `previous` when it can.
|
|
2091
|
+
*
|
|
2092
|
+
* - Same inputs: returns `previous` unchanged.
|
|
2093
|
+
* - Text grew by a suffix: parses only the suffix, so unchanged AST nodes keep
|
|
2094
|
+
* their identity (renderers rely on this to keep DOM and animations stable).
|
|
2095
|
+
* - Text was replaced or options changed: parses from scratch.
|
|
2096
|
+
* - `isComplete` toggled: finalizes (or re-opens) the current state.
|
|
2097
|
+
*
|
|
2098
|
+
* @param previous - The session from the last update, or `null` on first use.
|
|
2099
|
+
* @param text - Full markdown source, which usually grows over time.
|
|
2100
|
+
* @param options - Parser option overrides.
|
|
2101
|
+
* @param isCompleteInput - `true` once the source is final.
|
|
2102
|
+
* @public
|
|
2103
|
+
*/
|
|
2104
|
+
function updateStreamingMarkdownParserSession(previous, text, options, isCompleteInput) {
|
|
2105
|
+
const normalized = normalizeOptions(options);
|
|
2106
|
+
const optionsKey = getOptionsKey(normalized);
|
|
2107
|
+
if (previous === null || previous.optionsKey !== optionsKey) return {
|
|
2108
|
+
parserState: parseFullText(text, normalized, isCompleteInput),
|
|
2109
|
+
text,
|
|
2110
|
+
optionsKey,
|
|
2111
|
+
isCompleteInput
|
|
2112
|
+
};
|
|
2113
|
+
const textChanged = text !== previous.text;
|
|
2114
|
+
const completionChanged = previous.isCompleteInput !== isCompleteInput;
|
|
2115
|
+
if (!textChanged && !completionChanged) return previous;
|
|
2116
|
+
if (!textChanged) {
|
|
2117
|
+
const parserState = isCompleteInput ? finalizeStreamingMarkdown(previous.parserState) : parseStreamingMarkdownChunk(previous.parserState, "");
|
|
2118
|
+
return {
|
|
2119
|
+
...previous,
|
|
2120
|
+
parserState,
|
|
2121
|
+
isCompleteInput
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
const suffix = text.startsWith(previous.text) ? text.slice(previous.text.length) : null;
|
|
2125
|
+
const parsed = suffix === null ? parseFullText(text, normalized, false) : parseStreamingMarkdownChunk(previous.parserState, suffix);
|
|
2126
|
+
return {
|
|
2127
|
+
parserState: isCompleteInput ? finalizeStreamingMarkdown(parsed) : parsed,
|
|
2128
|
+
text,
|
|
2129
|
+
optionsKey,
|
|
2130
|
+
isCompleteInput
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
//#endregion
|
|
2135
|
+
//#region src/render/render-tree.ts
|
|
2136
|
+
/**
|
|
2137
|
+
* Class on the root element every renderer emits.
|
|
2138
|
+
* @public
|
|
2139
|
+
*/
|
|
2140
|
+
const STREAMING_MARKDOWN_ROOT_CLASS = "cpk-streaming-markdown-root";
|
|
2141
|
+
/**
|
|
2142
|
+
* Node classes CopilotKit's built-in chat markdown renderers (React, Vue,
|
|
2143
|
+
* Angular) apply, so all three emit the same themed markup.
|
|
2144
|
+
* @public
|
|
2145
|
+
*/
|
|
2146
|
+
const CHAT_MARKDOWN_NODE_CLASS_NAMES = { codeBlock: "cpk-markdown-code-block" };
|
|
2147
|
+
const SEGMENT_CLASS = "cpk-streaming-markdown-segment";
|
|
2148
|
+
const CITATION_CLASS = "cpk-streaming-markdown-citation";
|
|
2149
|
+
const CITATION_LABEL_CLASS = "cpk-streaming-markdown-citation-label";
|
|
2150
|
+
const WORD_JOINER = "";
|
|
2151
|
+
/**
|
|
2152
|
+
* Attributes of the default caret element.
|
|
2153
|
+
* @public
|
|
2154
|
+
*/
|
|
2155
|
+
const STREAMING_MARKDOWN_CARET_ATTRS = {
|
|
2156
|
+
"aria-hidden": "true",
|
|
2157
|
+
class: "cpk-streaming-markdown-caret",
|
|
2158
|
+
"data-streaming-markdown-caret": "true"
|
|
2159
|
+
};
|
|
2160
|
+
/**
|
|
2161
|
+
* Inline style of the default caret element (camelCase CSS properties).
|
|
2162
|
+
* @public
|
|
2163
|
+
*/
|
|
2164
|
+
const STREAMING_MARKDOWN_CARET_STYLE = {
|
|
2165
|
+
display: "inline-block",
|
|
2166
|
+
width: "0.48em",
|
|
2167
|
+
height: "0.48em",
|
|
2168
|
+
marginInlineStart: "0.08em",
|
|
2169
|
+
verticalAlign: "-0.08em",
|
|
2170
|
+
borderRadius: "999px",
|
|
2171
|
+
backgroundColor: "currentColor",
|
|
2172
|
+
opacity: "0.55"
|
|
2173
|
+
};
|
|
2174
|
+
const CITATION_STYLES = `
|
|
2175
|
+
.${STREAMING_MARKDOWN_ROOT_CLASS} .${CITATION_CLASS} {
|
|
2176
|
+
vertical-align: baseline;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
.${STREAMING_MARKDOWN_ROOT_CLASS} .${CITATION_LABEL_CLASS} {
|
|
2180
|
+
display: inline-flex;
|
|
2181
|
+
align-items: center;
|
|
2182
|
+
justify-content: center;
|
|
2183
|
+
inline-size: 1.4em;
|
|
2184
|
+
block-size: 1.4em;
|
|
2185
|
+
border-radius: 999px;
|
|
2186
|
+
border: 1px solid hsl(0 0% 50% / 0.35);
|
|
2187
|
+
background-color: hsl(0 0% 50% / 0.16);
|
|
2188
|
+
color: inherit;
|
|
2189
|
+
font-size: 0.7em;
|
|
2190
|
+
line-height: 1;
|
|
2191
|
+
font-variant-numeric: tabular-nums;
|
|
2192
|
+
text-decoration: none;
|
|
2193
|
+
transform: translateY(-0.15em);
|
|
2194
|
+
}
|
|
2195
|
+
`;
|
|
2196
|
+
const SEGMENT_FADE_IN_STYLES = `
|
|
2197
|
+
.${STREAMING_MARKDOWN_ROOT_CLASS} .${SEGMENT_CLASS} {
|
|
2198
|
+
opacity: 1;
|
|
2199
|
+
transition: opacity 400ms ease-out;
|
|
2200
|
+
@starting-style {
|
|
2201
|
+
opacity: 0;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
`;
|
|
2205
|
+
const SEGMENT_FALLBACK_STYLES = `
|
|
2206
|
+
.${STREAMING_MARKDOWN_ROOT_CLASS} .${SEGMENT_CLASS} {
|
|
2207
|
+
opacity: 1;
|
|
2208
|
+
transition: opacity 400ms ease-out;
|
|
2209
|
+
}
|
|
2210
|
+
`;
|
|
2211
|
+
/**
|
|
2212
|
+
* Stylesheet every renderer injects next to its root: the per-token fade-in
|
|
2213
|
+
* for text segments and the citation pill. Read at render time, so it is safe
|
|
2214
|
+
* to call during server rendering.
|
|
2215
|
+
*
|
|
2216
|
+
* @public
|
|
2217
|
+
*/
|
|
2218
|
+
function getStreamingMarkdownStyles() {
|
|
2219
|
+
return (typeof navigator !== "undefined" && /jsdom/i.test(navigator.userAgent) ? SEGMENT_FALLBACK_STYLES : SEGMENT_FADE_IN_STYLES) + CITATION_STYLES;
|
|
2220
|
+
}
|
|
2221
|
+
const SAFE_HREF = /^(https?:|mailto:|tel:|#|\/|\.\/|\.\.\/)/i;
|
|
2222
|
+
const SAFE_IMG_SRC = /^(https?:|data:image\/(?!svg)|\/|\.\/|\.\.\/)/i;
|
|
2223
|
+
/**
|
|
2224
|
+
* Returns `href` when its scheme is allowed for links, else `undefined`.
|
|
2225
|
+
* @public
|
|
2226
|
+
*/
|
|
2227
|
+
function sanitizeStreamingMarkdownHref(href) {
|
|
2228
|
+
if (!href) return void 0;
|
|
2229
|
+
return SAFE_HREF.test(href.trim()) ? href : void 0;
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Returns `src` when its scheme is allowed for images, else `undefined`.
|
|
2233
|
+
* @public
|
|
2234
|
+
*/
|
|
2235
|
+
function sanitizeStreamingMarkdownImageSrc(src) {
|
|
2236
|
+
if (!src) return void 0;
|
|
2237
|
+
return SAFE_IMG_SRC.test(src.trim()) ? src : void 0;
|
|
2238
|
+
}
|
|
2239
|
+
const RENDERER_KEY_BY_NODE_TYPE = {
|
|
2240
|
+
document: "document",
|
|
2241
|
+
paragraph: "paragraph",
|
|
2242
|
+
heading: "heading",
|
|
2243
|
+
blockquote: "blockquote",
|
|
2244
|
+
list: "list",
|
|
2245
|
+
"list-item": "listItem",
|
|
2246
|
+
"code-block": "codeBlock",
|
|
2247
|
+
table: "table",
|
|
2248
|
+
"table-row": "tableRow",
|
|
2249
|
+
"table-cell": "tableCell",
|
|
2250
|
+
"thematic-break": "thematicBreak",
|
|
2251
|
+
text: "text",
|
|
2252
|
+
em: "em",
|
|
2253
|
+
strong: "strong",
|
|
2254
|
+
strikethrough: "strikethrough",
|
|
2255
|
+
"inline-code": "inlineCode",
|
|
2256
|
+
"soft-break": "softBreak",
|
|
2257
|
+
"hard-break": "hardBreak",
|
|
2258
|
+
image: "image",
|
|
2259
|
+
link: "link",
|
|
2260
|
+
autolink: "autolink",
|
|
2261
|
+
citation: "citation",
|
|
2262
|
+
math: "math"
|
|
2263
|
+
};
|
|
2264
|
+
const SLOT = {
|
|
2265
|
+
kind: "slot",
|
|
2266
|
+
key: "slot"
|
|
2267
|
+
};
|
|
2268
|
+
function indexNodes(state) {
|
|
2269
|
+
return new Map(state.nodes.map((node) => [node.id, node]));
|
|
2270
|
+
}
|
|
2271
|
+
function findOpenNode(stack, nodeById) {
|
|
2272
|
+
for (let index = stack.length - 1; index >= 0; index -= 1) {
|
|
2273
|
+
const candidate = nodeById.get(stack[index]);
|
|
2274
|
+
if (candidate && candidate.type !== "document") return candidate;
|
|
2275
|
+
}
|
|
2276
|
+
return null;
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* The deepest open (still streaming) node other than the document, or `null`.
|
|
2280
|
+
* This is where the caret goes.
|
|
2281
|
+
*
|
|
2282
|
+
* @public
|
|
2283
|
+
*/
|
|
2284
|
+
function findStreamingMarkdownOpenNode(state) {
|
|
2285
|
+
return findOpenNode(state.stack, indexNodes(state));
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Citation number and definition for a citation node.
|
|
2289
|
+
*
|
|
2290
|
+
* @public
|
|
2291
|
+
*/
|
|
2292
|
+
function getStreamingMarkdownCitationData(node, citations) {
|
|
2293
|
+
return {
|
|
2294
|
+
id: node.idRef,
|
|
2295
|
+
number: node.number ?? citations.numbers[node.idRef] ?? node.idRef,
|
|
2296
|
+
definition: citations.definitions[node.idRef]
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
function nodeAttrs(node, context, extra = {}) {
|
|
2300
|
+
const attrs = {};
|
|
2301
|
+
const className = context.nodeClassNames?.[RENDERER_KEY_BY_NODE_TYPE[node.type]];
|
|
2302
|
+
if (className) attrs.class = className;
|
|
2303
|
+
const entries = Object.entries(extra);
|
|
2304
|
+
for (const [name, value] of entries) if (value !== void 0) attrs[name] = value;
|
|
2305
|
+
attrs["data-streaming-markdown-node"] = node.type;
|
|
2306
|
+
attrs["data-node-open"] = String(!node.closed);
|
|
2307
|
+
return attrs;
|
|
2308
|
+
}
|
|
2309
|
+
function element(key, tag, attrs, children, action) {
|
|
2310
|
+
return action ? {
|
|
2311
|
+
kind: "element",
|
|
2312
|
+
key,
|
|
2313
|
+
tag,
|
|
2314
|
+
attrs,
|
|
2315
|
+
children,
|
|
2316
|
+
action
|
|
2317
|
+
} : {
|
|
2318
|
+
kind: "element",
|
|
2319
|
+
key,
|
|
2320
|
+
tag,
|
|
2321
|
+
attrs,
|
|
2322
|
+
children
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
function text(key, value) {
|
|
2326
|
+
return {
|
|
2327
|
+
kind: "text",
|
|
2328
|
+
key,
|
|
2329
|
+
text: value
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
function caretFor(nodeId, context) {
|
|
2333
|
+
return context.caretTargetNodeId === nodeId ? [{
|
|
2334
|
+
kind: "caret",
|
|
2335
|
+
key: `caret-${nodeId}`
|
|
2336
|
+
}] : [];
|
|
2337
|
+
}
|
|
2338
|
+
function renderNode(node, context, children, output, citation) {
|
|
2339
|
+
const base = {
|
|
2340
|
+
kind: "node",
|
|
2341
|
+
key: String(node.id),
|
|
2342
|
+
node,
|
|
2343
|
+
rendererKey: RENDERER_KEY_BY_NODE_TYPE[node.type],
|
|
2344
|
+
children,
|
|
2345
|
+
output,
|
|
2346
|
+
containsCaret: context.caretPath.has(node.id)
|
|
2347
|
+
};
|
|
2348
|
+
return citation ? {
|
|
2349
|
+
...base,
|
|
2350
|
+
citation
|
|
2351
|
+
} : base;
|
|
2352
|
+
}
|
|
2353
|
+
function containerChildren(node, context) {
|
|
2354
|
+
return [...node.children.flatMap((childId) => {
|
|
2355
|
+
const child = context.nodeById.get(childId);
|
|
2356
|
+
return child ? [buildNode(child, context)] : [];
|
|
2357
|
+
}), ...caretFor(node.id, context)];
|
|
2358
|
+
}
|
|
2359
|
+
/** A container whose default output is one element wrapping its children. */
|
|
2360
|
+
function wrapped(node, context, tag, extra = {}, action) {
|
|
2361
|
+
const attrs = nodeAttrs(node, context, extra);
|
|
2362
|
+
return renderNode(node, context, containerChildren(node, context), [element(String(node.id), tag, attrs, [SLOT], action)]);
|
|
2363
|
+
}
|
|
2364
|
+
function textSegments(node) {
|
|
2365
|
+
if (node.text.length === 0) return [];
|
|
2366
|
+
if (node.segments.length === 0) return [element(`segment-${node.id}-full`, "span", {
|
|
2367
|
+
class: SEGMENT_CLASS,
|
|
2368
|
+
"data-streaming-markdown-segment-kind": "full",
|
|
2369
|
+
"data-streaming-markdown-whitespace": "false"
|
|
2370
|
+
}, [text("text", node.text)])];
|
|
2371
|
+
return node.segments.map((segment) => element(`segment-${node.id}-${segment.start}-${segment.kind}`, "span", {
|
|
2372
|
+
class: SEGMENT_CLASS,
|
|
2373
|
+
"data-streaming-markdown-segment-kind": segment.kind,
|
|
2374
|
+
"data-streaming-markdown-whitespace": String(segment.isWhitespace)
|
|
2375
|
+
}, [text("text", segment.noBreakBefore ? `${WORD_JOINER}${segment.text}` : segment.text)]));
|
|
2376
|
+
}
|
|
2377
|
+
function taskCheckbox(node, context) {
|
|
2378
|
+
const parent = node.parentId == null ? void 0 : context.nodeById.get(node.parentId);
|
|
2379
|
+
if (!(parent?.type === "list-item" && parent.checked !== void 0 && parent.children[0] === node.id)) return [];
|
|
2380
|
+
const attrs = {
|
|
2381
|
+
type: "checkbox",
|
|
2382
|
+
disabled: true,
|
|
2383
|
+
"data-streaming-markdown-task": "true"
|
|
2384
|
+
};
|
|
2385
|
+
if (parent.checked) attrs.checked = true;
|
|
2386
|
+
return [element(`task-${parent.id}`, "input", attrs, []), text("task-space", " ")];
|
|
2387
|
+
}
|
|
2388
|
+
function buildCitation(node, context) {
|
|
2389
|
+
const citation = getStreamingMarkdownCitationData(node, context.citations);
|
|
2390
|
+
const label = [text("label", String(citation.number))];
|
|
2391
|
+
const href = sanitizeStreamingMarkdownHref(citation.definition?.url);
|
|
2392
|
+
const inner = href ? element("label", "a", {
|
|
2393
|
+
href,
|
|
2394
|
+
target: "_blank",
|
|
2395
|
+
rel: "noopener noreferrer",
|
|
2396
|
+
role: "doc-noteref",
|
|
2397
|
+
class: CITATION_LABEL_CLASS
|
|
2398
|
+
}, label, {
|
|
2399
|
+
type: "citation",
|
|
2400
|
+
citation,
|
|
2401
|
+
node
|
|
2402
|
+
}) : element("label", "span", {
|
|
2403
|
+
role: "doc-noteref",
|
|
2404
|
+
class: CITATION_LABEL_CLASS
|
|
2405
|
+
}, label);
|
|
2406
|
+
const attrs = nodeAttrs(node, context);
|
|
2407
|
+
const supClass = attrs.class ? `${CITATION_CLASS} ${attrs.class}` : CITATION_CLASS;
|
|
2408
|
+
return renderNode(node, context, null, [element(String(node.id), "sup", {
|
|
2409
|
+
...attrs,
|
|
2410
|
+
class: supClass
|
|
2411
|
+
}, [inner])], citation);
|
|
2412
|
+
}
|
|
2413
|
+
function buildNode(node, context) {
|
|
2414
|
+
const key = String(node.id);
|
|
2415
|
+
switch (node.type) {
|
|
2416
|
+
case "document": return renderNode(node, context, containerChildren(node, context), [SLOT]);
|
|
2417
|
+
case "paragraph": {
|
|
2418
|
+
const attrs = nodeAttrs(node, context);
|
|
2419
|
+
return renderNode(node, context, containerChildren(node, context), [element(key, "p", attrs, [...taskCheckbox(node, context), SLOT])]);
|
|
2420
|
+
}
|
|
2421
|
+
case "heading": return wrapped(node, context, `h${node.level}`);
|
|
2422
|
+
case "blockquote": return wrapped(node, context, "blockquote");
|
|
2423
|
+
case "list": return wrapped(node, context, node.ordered ? "ol" : "ul", {
|
|
2424
|
+
start: node.ordered && node.start != null ? String(node.start) : void 0,
|
|
2425
|
+
"data-list-tight": String(node.tight)
|
|
2426
|
+
});
|
|
2427
|
+
case "list-item": return wrapped(node, context, "li");
|
|
2428
|
+
case "table": {
|
|
2429
|
+
const attrs = nodeAttrs(node, context);
|
|
2430
|
+
return renderNode(node, context, containerChildren(node, context), [element(key, "table", attrs, [element("tbody", "tbody", {}, [SLOT])])]);
|
|
2431
|
+
}
|
|
2432
|
+
case "table-row": return wrapped(node, context, "tr");
|
|
2433
|
+
case "table-cell": {
|
|
2434
|
+
const parent = node.parentId == null ? void 0 : context.nodeById.get(node.parentId);
|
|
2435
|
+
return wrapped(node, context, parent?.type === "table-row" && parent.isHeader ? "th" : "td");
|
|
2436
|
+
}
|
|
2437
|
+
case "em": return wrapped(node, context, "em");
|
|
2438
|
+
case "strong": return wrapped(node, context, "strong");
|
|
2439
|
+
case "strikethrough": return wrapped(node, context, "s");
|
|
2440
|
+
case "link": {
|
|
2441
|
+
const href = sanitizeStreamingMarkdownHref(node.url);
|
|
2442
|
+
return wrapped(node, context, "a", {
|
|
2443
|
+
href,
|
|
2444
|
+
target: "_blank",
|
|
2445
|
+
rel: "noopener noreferrer",
|
|
2446
|
+
title: node.title
|
|
2447
|
+
}, href ? {
|
|
2448
|
+
type: "link",
|
|
2449
|
+
url: href,
|
|
2450
|
+
node
|
|
2451
|
+
} : void 0);
|
|
2452
|
+
}
|
|
2453
|
+
case "code-block": return renderNode(node, context, null, [element(key, "pre", nodeAttrs(node, context), [element("code", "code", node.info ? { "data-code-info": node.info } : {}, [text("text", node.text), ...caretFor(node.id, context)])])]);
|
|
2454
|
+
case "thematic-break": return renderNode(node, context, null, [element(key, "hr", nodeAttrs(node, context), [])]);
|
|
2455
|
+
case "hard-break": return renderNode(node, context, null, [element(key, "br", nodeAttrs(node, context), [])]);
|
|
2456
|
+
case "text": return renderNode(node, context, textSegments(node), [SLOT]);
|
|
2457
|
+
case "soft-break": return renderNode(node, context, [text("text", "\n")], [SLOT]);
|
|
2458
|
+
case "inline-code": return renderNode(node, context, [text("text", node.text)], [element(key, "code", nodeAttrs(node, context), [SLOT])]);
|
|
2459
|
+
case "image": return renderNode(node, context, null, [element(key, "img", nodeAttrs(node, context, {
|
|
2460
|
+
src: sanitizeStreamingMarkdownImageSrc(node.url),
|
|
2461
|
+
alt: node.alt,
|
|
2462
|
+
title: node.title
|
|
2463
|
+
}), [])]);
|
|
2464
|
+
case "autolink": {
|
|
2465
|
+
const href = sanitizeStreamingMarkdownHref(node.url);
|
|
2466
|
+
return renderNode(node, context, [text("text", node.text)], [element(key, "a", nodeAttrs(node, context, {
|
|
2467
|
+
href,
|
|
2468
|
+
target: "_blank",
|
|
2469
|
+
rel: "noopener noreferrer"
|
|
2470
|
+
}), [SLOT], href ? {
|
|
2471
|
+
type: "link",
|
|
2472
|
+
url: href,
|
|
2473
|
+
node
|
|
2474
|
+
} : void 0)]);
|
|
2475
|
+
}
|
|
2476
|
+
case "math": return renderNode(node, context, [text("text", node.text)], [element(key, "span", nodeAttrs(node, context, { "data-math-display": String(node.display) }), [SLOT])]);
|
|
2477
|
+
case "citation": return buildCitation(node, context);
|
|
2478
|
+
default: return node;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Turns parser state into a framework-neutral render tree: which elements to
|
|
2483
|
+
* create, their sanitized attributes, click actions, text segments for the
|
|
2484
|
+
* per-token animation, and where the caret goes. The React, Vue, and DOM
|
|
2485
|
+
* renderers all render this tree, so markup and sanitization live here only.
|
|
2486
|
+
*
|
|
2487
|
+
* @param state - Parser state from the streaming markdown parser.
|
|
2488
|
+
* @param options - Whether to place a caret, and optional per-node classes.
|
|
2489
|
+
* @returns The root render node (`null` when the state has no root) and the
|
|
2490
|
+
* open node the caret targets.
|
|
2491
|
+
* @public
|
|
2492
|
+
*/
|
|
2493
|
+
function buildStreamingMarkdownRenderTree(state, options) {
|
|
2494
|
+
const nodeById = indexNodes(state);
|
|
2495
|
+
const openNode = findOpenNode(state.stack, nodeById);
|
|
2496
|
+
const caretTargetNodeId = options.caret && !state.isComplete && openNode ? openNode.id : null;
|
|
2497
|
+
const caretPath = /* @__PURE__ */ new Set();
|
|
2498
|
+
let cursor = caretTargetNodeId == null ? void 0 : nodeById.get(caretTargetNodeId);
|
|
2499
|
+
while (cursor) {
|
|
2500
|
+
caretPath.add(cursor.id);
|
|
2501
|
+
cursor = cursor.parentId == null ? void 0 : nodeById.get(cursor.parentId);
|
|
2502
|
+
}
|
|
2503
|
+
const context = {
|
|
2504
|
+
nodeById,
|
|
2505
|
+
citations: state.citations,
|
|
2506
|
+
caretTargetNodeId,
|
|
2507
|
+
caretPath,
|
|
2508
|
+
nodeClassNames: options.nodeClassNames
|
|
2509
|
+
};
|
|
2510
|
+
const rootNode = state.rootId == null ? void 0 : nodeById.get(state.rootId);
|
|
2511
|
+
return {
|
|
2512
|
+
root: rootNode ? buildNode(rootNode, context) : null,
|
|
2513
|
+
openNode,
|
|
2514
|
+
isComplete: state.isComplete
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
//#endregion
|
|
2519
|
+
exports.CHAT_MARKDOWN_NODE_CLASS_NAMES = CHAT_MARKDOWN_NODE_CLASS_NAMES;
|
|
2520
|
+
exports.STREAMING_MARKDOWN_CARET_ATTRS = STREAMING_MARKDOWN_CARET_ATTRS;
|
|
2521
|
+
exports.STREAMING_MARKDOWN_CARET_STYLE = STREAMING_MARKDOWN_CARET_STYLE;
|
|
2522
|
+
exports.STREAMING_MARKDOWN_ROOT_CLASS = STREAMING_MARKDOWN_ROOT_CLASS;
|
|
2523
|
+
exports.buildStreamingMarkdownRenderTree = buildStreamingMarkdownRenderTree;
|
|
2019
2524
|
exports.createStreamingMarkdownParserState = createStreamingMarkdownParserState;
|
|
2020
2525
|
exports.finalizeStreamingMarkdown = finalizeStreamingMarkdown;
|
|
2526
|
+
exports.findStreamingMarkdownOpenNode = findStreamingMarkdownOpenNode;
|
|
2527
|
+
exports.getStreamingMarkdownCitationData = getStreamingMarkdownCitationData;
|
|
2528
|
+
exports.getStreamingMarkdownStyles = getStreamingMarkdownStyles;
|
|
2021
2529
|
exports.parseStreamingMarkdownChunk = parseStreamingMarkdownChunk;
|
|
2530
|
+
exports.sanitizeStreamingMarkdownHref = sanitizeStreamingMarkdownHref;
|
|
2531
|
+
exports.sanitizeStreamingMarkdownImageSrc = sanitizeStreamingMarkdownImageSrc;
|
|
2532
|
+
exports.updateStreamingMarkdownParserSession = updateStreamingMarkdownParserSession;
|
|
2022
2533
|
exports.warnUnsupportedRichSyntaxOnce = warnUnsupportedRichSyntaxOnce;
|
|
2023
2534
|
//# sourceMappingURL=index.cjs.map
|