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