react-native-nitro-markdown 0.7.1 → 0.8.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/README.md +179 -27
- package/ios/HybridMarkdownSession.swift +11 -4
- package/lib/commonjs/MarkdownContext.js +1 -3
- package/lib/commonjs/MarkdownContext.js.map +1 -1
- package/lib/commonjs/index.js +6 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/markdown-stream.js +113 -29
- package/lib/commonjs/markdown-stream.js.map +1 -1
- package/lib/commonjs/markdown.js +96 -38
- package/lib/commonjs/markdown.js.map +1 -1
- package/lib/commonjs/renderers/image.js +11 -2
- package/lib/commonjs/renderers/image.js.map +1 -1
- package/lib/commonjs/renderers/math.js +22 -2
- package/lib/commonjs/renderers/math.js.map +1 -1
- package/lib/commonjs/renderers/table/index.js +4 -4
- package/lib/commonjs/renderers/table/index.js.map +1 -1
- package/lib/commonjs/renderers/table/table-utils.js +1 -1
- package/lib/commonjs/renderers/table/table-utils.js.map +1 -1
- package/lib/commonjs/use-markdown-stream.js +2 -6
- package/lib/commonjs/use-markdown-stream.js.map +1 -1
- package/lib/commonjs/utils/code-highlight.js +1 -1
- package/lib/commonjs/utils/code-highlight.js.map +1 -1
- package/lib/commonjs/utils/incremental-ast.js +67 -14
- package/lib/commonjs/utils/incremental-ast.js.map +1 -1
- package/lib/commonjs/utils/link-security.js +2 -2
- package/lib/commonjs/utils/link-security.js.map +1 -1
- package/lib/commonjs/utils/stream-timeline.js +13 -7
- package/lib/commonjs/utils/stream-timeline.js.map +1 -1
- package/lib/module/MarkdownContext.js +1 -3
- package/lib/module/MarkdownContext.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/markdown-stream.js +113 -30
- package/lib/module/markdown-stream.js.map +1 -1
- package/lib/module/markdown.js +96 -38
- package/lib/module/markdown.js.map +1 -1
- package/lib/module/renderers/image.js +12 -3
- package/lib/module/renderers/image.js.map +1 -1
- package/lib/module/renderers/math.js +22 -2
- package/lib/module/renderers/math.js.map +1 -1
- package/lib/module/renderers/table/index.js +4 -4
- package/lib/module/renderers/table/index.js.map +1 -1
- package/lib/module/renderers/table/table-utils.js +1 -1
- package/lib/module/renderers/table/table-utils.js.map +1 -1
- package/lib/module/use-markdown-stream.js +2 -6
- package/lib/module/use-markdown-stream.js.map +1 -1
- package/lib/module/utils/code-highlight.js +1 -1
- package/lib/module/utils/code-highlight.js.map +1 -1
- package/lib/module/utils/incremental-ast.js +65 -13
- package/lib/module/utils/incremental-ast.js.map +1 -1
- package/lib/module/utils/link-security.js +2 -2
- package/lib/module/utils/link-security.js.map +1 -1
- package/lib/module/utils/stream-timeline.js +13 -7
- package/lib/module/utils/stream-timeline.js.map +1 -1
- package/lib/typescript/commonjs/MarkdownContext.d.ts +1 -0
- package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown-stream.d.ts +28 -7
- package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
- package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/incremental-ast.d.ts +1 -0
- package/lib/typescript/commonjs/utils/incremental-ast.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/stream-timeline.d.ts.map +1 -1
- package/lib/typescript/module/MarkdownContext.d.ts +1 -0
- package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/markdown-stream.d.ts +28 -7
- package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/markdown.d.ts.map +1 -1
- package/lib/typescript/module/renderers/image.d.ts.map +1 -1
- package/lib/typescript/module/renderers/math.d.ts.map +1 -1
- package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/utils/incremental-ast.d.ts +1 -0
- package/lib/typescript/module/utils/incremental-ast.d.ts.map +1 -1
- package/lib/typescript/module/utils/stream-timeline.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/MarkdownContext.ts +2 -2
- package/src/index.ts +10 -2
- package/src/markdown-stream.tsx +153 -36
- package/src/markdown.tsx +82 -42
- package/src/renderers/image.tsx +13 -2
- package/src/renderers/math.tsx +20 -2
- package/src/renderers/table/index.tsx +4 -4
- package/src/renderers/table/table-utils.ts +1 -1
- package/src/use-markdown-stream.ts +2 -6
- package/src/utils/code-highlight.ts +1 -1
- package/src/utils/incremental-ast.ts +104 -11
- package/src/utils/link-security.ts +3 -3
- package/src/utils/stream-timeline.ts +10 -7
|
@@ -21,13 +21,9 @@ export function useMarkdownSession(initialText?: string) {
|
|
|
21
21
|
const session = sessionRef.current!;
|
|
22
22
|
return () => {
|
|
23
23
|
try {
|
|
24
|
-
session.
|
|
24
|
+
session.dispose();
|
|
25
25
|
} finally {
|
|
26
|
-
|
|
27
|
-
session.dispose();
|
|
28
|
-
} finally {
|
|
29
|
-
sessionRef.current = null;
|
|
30
|
-
}
|
|
26
|
+
sessionRef.current = null;
|
|
31
27
|
}
|
|
32
28
|
};
|
|
33
29
|
}, []);
|
|
@@ -182,7 +182,7 @@ export function defaultHighlighter(
|
|
|
182
182
|
const lines = code.split("\n");
|
|
183
183
|
const result: HighlightedToken[] = [];
|
|
184
184
|
for (let i = 0; i < lines.length; i++) {
|
|
185
|
-
result.push(...tokenizeLine(lines[i], language));
|
|
185
|
+
result.push(...tokenizeLine(lines[i] ?? "", language));
|
|
186
186
|
if (i < lines.length - 1) {
|
|
187
187
|
result.push({ text: "\n", type: "default" });
|
|
188
188
|
}
|
|
@@ -24,7 +24,7 @@ const isInsideFencedCodeBlock = (text: string): boolean => {
|
|
|
24
24
|
const fenceMatch = line.match(FENCE_LINE_PATTERN);
|
|
25
25
|
if (!fenceMatch) continue;
|
|
26
26
|
|
|
27
|
-
const marker = fenceMatch[1];
|
|
27
|
+
const marker = fenceMatch[1] ?? "";
|
|
28
28
|
const markerChar = marker[0] as "`" | "~";
|
|
29
29
|
const markerLength = marker.length;
|
|
30
30
|
|
|
@@ -94,7 +94,11 @@ const findTrailingLeafPath = (
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
const lastIndex = children.length - 1;
|
|
97
|
-
|
|
97
|
+
const lastChild = children[lastIndex];
|
|
98
|
+
if (lastChild === undefined) {
|
|
99
|
+
return path;
|
|
100
|
+
}
|
|
101
|
+
return findTrailingLeafPath(lastChild, [...path, lastIndex]);
|
|
98
102
|
};
|
|
99
103
|
|
|
100
104
|
const getNodeAtPath = (
|
|
@@ -126,11 +130,14 @@ const appendPlainTextToAst = (
|
|
|
126
130
|
const delta = appendedChunk.length;
|
|
127
131
|
const update = (node: MarkdownNode, depth: number): MarkdownNode => {
|
|
128
132
|
if (depth === path.length) {
|
|
129
|
-
|
|
133
|
+
const updatedNode: MarkdownNode = {
|
|
130
134
|
...node,
|
|
131
135
|
content: (node.content ?? "") + appendedChunk,
|
|
132
|
-
end: typeof node.end === "number" ? node.end + delta : node.end,
|
|
133
136
|
};
|
|
137
|
+
if (typeof node.end === "number") {
|
|
138
|
+
updatedNode.end = node.end + delta;
|
|
139
|
+
}
|
|
140
|
+
return updatedNode;
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
const childIndex = path[depth];
|
|
@@ -138,11 +145,16 @@ const appendPlainTextToAst = (
|
|
|
138
145
|
index === childIndex ? update(child, depth + 1) : child,
|
|
139
146
|
);
|
|
140
147
|
|
|
141
|
-
|
|
148
|
+
const updatedNode: MarkdownNode = {
|
|
142
149
|
...node,
|
|
143
|
-
end: typeof node.end === "number" ? node.end + delta : node.end,
|
|
144
|
-
children,
|
|
145
150
|
};
|
|
151
|
+
if (typeof node.end === "number") {
|
|
152
|
+
updatedNode.end = node.end + delta;
|
|
153
|
+
}
|
|
154
|
+
if (children) {
|
|
155
|
+
updatedNode.children = children;
|
|
156
|
+
}
|
|
157
|
+
return updatedNode;
|
|
146
158
|
};
|
|
147
159
|
|
|
148
160
|
return update(ast, 0);
|
|
@@ -152,6 +164,87 @@ const endsAtBlockBoundary = (text: string): boolean => {
|
|
|
152
164
|
return text.endsWith("\n") || text.endsWith("\r");
|
|
153
165
|
};
|
|
154
166
|
|
|
167
|
+
const nodesHaveMatchingMetadata = (
|
|
168
|
+
previousNode: MarkdownNode,
|
|
169
|
+
nextNode: MarkdownNode,
|
|
170
|
+
): boolean => {
|
|
171
|
+
return (
|
|
172
|
+
previousNode.type === nextNode.type &&
|
|
173
|
+
previousNode.content === nextNode.content &&
|
|
174
|
+
previousNode.level === nextNode.level &&
|
|
175
|
+
previousNode.href === nextNode.href &&
|
|
176
|
+
previousNode.title === nextNode.title &&
|
|
177
|
+
previousNode.alt === nextNode.alt &&
|
|
178
|
+
previousNode.language === nextNode.language &&
|
|
179
|
+
previousNode.ordered === nextNode.ordered &&
|
|
180
|
+
previousNode.start === nextNode.start &&
|
|
181
|
+
previousNode.checked === nextNode.checked &&
|
|
182
|
+
previousNode.isHeader === nextNode.isHeader &&
|
|
183
|
+
previousNode.align === nextNode.align &&
|
|
184
|
+
previousNode.beg === nextNode.beg &&
|
|
185
|
+
previousNode.end === nextNode.end
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const reuseStableAstNodes = (
|
|
190
|
+
previousNode: MarkdownNode,
|
|
191
|
+
nextNode: MarkdownNode,
|
|
192
|
+
): MarkdownNode => {
|
|
193
|
+
if (previousNode.type !== nextNode.type) {
|
|
194
|
+
return nextNode;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const hasMatchingMetadata = nodesHaveMatchingMetadata(previousNode, nextNode);
|
|
198
|
+
const previousChildren = previousNode.children;
|
|
199
|
+
const nextChildren = nextNode.children;
|
|
200
|
+
|
|
201
|
+
if (!previousChildren || !nextChildren) {
|
|
202
|
+
return hasMatchingMetadata && previousChildren === nextChildren
|
|
203
|
+
? previousNode
|
|
204
|
+
: nextNode;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (previousChildren.length !== nextChildren.length) {
|
|
208
|
+
return {
|
|
209
|
+
...nextNode,
|
|
210
|
+
children: nextChildren.map((nextChild, index) => {
|
|
211
|
+
const previousChild = previousChildren[index];
|
|
212
|
+
return previousChild
|
|
213
|
+
? reuseStableAstNodes(previousChild, nextChild)
|
|
214
|
+
: nextChild;
|
|
215
|
+
}),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let hasChildChange = false;
|
|
220
|
+
const children = nextChildren.map((nextChild, index) => {
|
|
221
|
+
const previousChild = previousChildren[index];
|
|
222
|
+
const child =
|
|
223
|
+
previousChild === undefined
|
|
224
|
+
? nextChild
|
|
225
|
+
: reuseStableAstNodes(previousChild, nextChild);
|
|
226
|
+
hasChildChange ||= child !== previousChild;
|
|
227
|
+
return child;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (hasMatchingMetadata && !hasChildChange) {
|
|
231
|
+
return previousNode;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
...nextNode,
|
|
236
|
+
children,
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const parseAstWithStableNodes = (
|
|
241
|
+
previousAst: MarkdownNode,
|
|
242
|
+
text: string,
|
|
243
|
+
options?: ParserOptions,
|
|
244
|
+
): MarkdownNode => {
|
|
245
|
+
return reuseStableAstNodes(previousAst, parseAst(text, options));
|
|
246
|
+
};
|
|
247
|
+
|
|
155
248
|
export type IncrementalAstInput = {
|
|
156
249
|
allowIncremental?: boolean;
|
|
157
250
|
nextText: string;
|
|
@@ -168,7 +261,7 @@ export const getNextStreamAst = ({
|
|
|
168
261
|
previousText,
|
|
169
262
|
}: IncrementalAstInput): MarkdownNode => {
|
|
170
263
|
if (!allowIncremental || !nextText.startsWith(previousText)) {
|
|
171
|
-
return
|
|
264
|
+
return parseAstWithStableNodes(previousAst, nextText, options);
|
|
172
265
|
}
|
|
173
266
|
|
|
174
267
|
const appendedChunk = nextText.slice(previousText.length);
|
|
@@ -194,7 +287,7 @@ export const getNextStreamAst = ({
|
|
|
194
287
|
|
|
195
288
|
if (!PLAIN_TEXT_APPEND_PATTERN.test(appendedChunk)) {
|
|
196
289
|
if (endsAtBlockBoundary(previousText)) {
|
|
197
|
-
return
|
|
290
|
+
return parseAstWithStableNodes(previousAst, nextText, options);
|
|
198
291
|
}
|
|
199
292
|
|
|
200
293
|
const textAppendedAst = appendPlainTextToAst(
|
|
@@ -208,12 +301,12 @@ export const getNextStreamAst = ({
|
|
|
208
301
|
}
|
|
209
302
|
|
|
210
303
|
if (insideFencedCodeBlock) {
|
|
211
|
-
return
|
|
304
|
+
return parseAstWithStableNodes(previousAst, nextText, options);
|
|
212
305
|
}
|
|
213
306
|
|
|
214
307
|
// Correctness-first fallback: full reparse for all non-trivial appends.
|
|
215
308
|
// Incremental append is only used for plain text chunks at the true trailing leaf.
|
|
216
|
-
return
|
|
309
|
+
return parseAstWithStableNodes(previousAst, nextText, options);
|
|
217
310
|
};
|
|
218
311
|
|
|
219
312
|
export const parseMarkdownAst = (
|
|
@@ -26,14 +26,14 @@ const parseAbsoluteHref = (
|
|
|
26
26
|
const protocolMatch = href.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
27
27
|
if (!protocolMatch) return null;
|
|
28
28
|
|
|
29
|
-
const protocol = normalizeProtocol(protocolMatch[1]);
|
|
29
|
+
const protocol = normalizeProtocol(protocolMatch[1] ?? "");
|
|
30
30
|
const rest = href.slice(protocolMatch[0].length);
|
|
31
31
|
const authorityMatch = rest.match(/^\/\/([^/?#]*)/);
|
|
32
32
|
const rawHost = authorityMatch?.[1] ?? "";
|
|
33
|
-
const hostname = rawHost
|
|
33
|
+
const hostname = (rawHost
|
|
34
34
|
.replace(/^[^@]*@/, "")
|
|
35
35
|
.replace(/^\[|\]$/g, "")
|
|
36
|
-
.split(":")[0]
|
|
36
|
+
.split(":")[0] ?? "")
|
|
37
37
|
.toLowerCase();
|
|
38
38
|
|
|
39
39
|
return { protocol, hostname };
|
|
@@ -19,15 +19,17 @@ export const createTimestampTimeline = (
|
|
|
19
19
|
.map(Number)
|
|
20
20
|
.filter((index) => Number.isFinite(index))
|
|
21
21
|
.sort((a, b) => a - b)
|
|
22
|
-
.
|
|
23
|
-
index
|
|
24
|
-
time:
|
|
25
|
-
})
|
|
22
|
+
.flatMap((index) => {
|
|
23
|
+
const time = timestamps[index];
|
|
24
|
+
return time === undefined ? [] : [{ index, time }];
|
|
25
|
+
})
|
|
26
26
|
.filter((entry) => Number.isFinite(entry.time));
|
|
27
27
|
|
|
28
28
|
let monotonic = true;
|
|
29
29
|
for (let i = 1; i < entries.length; i += 1) {
|
|
30
|
-
|
|
30
|
+
const current = entries[i];
|
|
31
|
+
const previous = entries[i - 1];
|
|
32
|
+
if (current && previous && current.time < previous.time) {
|
|
31
33
|
monotonic = false;
|
|
32
34
|
break;
|
|
33
35
|
}
|
|
@@ -59,7 +61,8 @@ export const resolveHighlightPosition = (
|
|
|
59
61
|
|
|
60
62
|
while (left <= right) {
|
|
61
63
|
const mid = (left + right) >> 1;
|
|
62
|
-
|
|
64
|
+
const entry = entries[mid];
|
|
65
|
+
if (entry !== undefined && entry.time <= currentTimeMs) {
|
|
63
66
|
resolvedIndex = mid;
|
|
64
67
|
left = mid + 1;
|
|
65
68
|
} else {
|
|
@@ -68,5 +71,5 @@ export const resolveHighlightPosition = (
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
if (resolvedIndex === -1) return 0;
|
|
71
|
-
return entries[resolvedIndex]
|
|
74
|
+
return (entries[resolvedIndex]?.index ?? 0) + 1;
|
|
72
75
|
};
|