@incremark/react 0.0.5 → 0.1.2
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/index.d.ts +68 -13
- package/dist/index.js +200 -34
- package/dist/styles.css +15 -0
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,32 +1,85 @@
|
|
|
1
|
-
import { ParserOptions, ParsedBlock, Root, IncrementalUpdate, IncremarkParser, SourceBlock, TransformerOptions, DisplayBlock,
|
|
1
|
+
import { ParserOptions, AnimationEffect, TransformerPlugin, ParsedBlock, Root, IncrementalUpdate, IncremarkParser, SourceBlock, TransformerOptions, DisplayBlock, BlockTransformer, RootContent } from '@incremark/core';
|
|
2
2
|
export { AnimationEffect, BlockTransformer, DisplayBlock, IncrementalUpdate, ParsedBlock, ParserOptions, Root, RootContent, SourceBlock, TransformerOptions, TransformerPlugin, TransformerState, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin } from '@incremark/core';
|
|
3
3
|
import * as _incremark_devtools from '@incremark/devtools';
|
|
4
4
|
import { DevToolsOptions } from '@incremark/devtools';
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
+
/** 打字机效果配置 */
|
|
8
|
+
interface TypewriterOptions {
|
|
9
|
+
/** 是否启用打字机效果(可动态切换) */
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
/** 每次显示的字符数,可以是固定值或范围 [min, max] */
|
|
12
|
+
charsPerTick?: number | [number, number];
|
|
13
|
+
/** 更新间隔 (ms) */
|
|
14
|
+
tickInterval?: number;
|
|
15
|
+
/** 动画效果: 'none' | 'fade-in' | 'typing' */
|
|
16
|
+
effect?: AnimationEffect;
|
|
17
|
+
/** 光标字符(仅 typing 效果使用) */
|
|
18
|
+
cursor?: string;
|
|
19
|
+
/** 页面不可见时暂停 */
|
|
20
|
+
pauseOnHidden?: boolean;
|
|
21
|
+
/** 自定义插件 */
|
|
22
|
+
plugins?: TransformerPlugin[];
|
|
23
|
+
}
|
|
7
24
|
interface UseIncremarkOptions extends ParserOptions {
|
|
25
|
+
/** 打字机配置,传入即创建 transformer(可通过 enabled 控制是否启用) */
|
|
26
|
+
typewriter?: TypewriterOptions;
|
|
8
27
|
}
|
|
9
28
|
interface BlockWithStableId$1 extends ParsedBlock {
|
|
10
29
|
stableId: string;
|
|
11
30
|
}
|
|
31
|
+
/** 打字机控制对象 */
|
|
32
|
+
interface TypewriterControls {
|
|
33
|
+
/** 是否启用 */
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
/** 设置启用状态 */
|
|
36
|
+
setEnabled: (enabled: boolean) => void;
|
|
37
|
+
/** 是否正在处理中 */
|
|
38
|
+
isProcessing: boolean;
|
|
39
|
+
/** 是否已暂停 */
|
|
40
|
+
isPaused: boolean;
|
|
41
|
+
/** 当前动画效果 */
|
|
42
|
+
effect: AnimationEffect;
|
|
43
|
+
/** 跳过动画,直接显示全部 */
|
|
44
|
+
skip: () => void;
|
|
45
|
+
/** 暂停动画 */
|
|
46
|
+
pause: () => void;
|
|
47
|
+
/** 恢复动画 */
|
|
48
|
+
resume: () => void;
|
|
49
|
+
/** 动态更新配置 */
|
|
50
|
+
setOptions: (options: Partial<TypewriterOptions>) => void;
|
|
51
|
+
}
|
|
12
52
|
/**
|
|
13
53
|
* React Hook: Incremark 流式 Markdown 解析器
|
|
14
54
|
*
|
|
15
55
|
* @example
|
|
16
56
|
* ```tsx
|
|
17
|
-
* import { useIncremark } from '@incremark/react'
|
|
57
|
+
* import { useIncremark, Incremark } from '@incremark/react'
|
|
18
58
|
*
|
|
19
59
|
* function App() {
|
|
20
|
-
*
|
|
60
|
+
* // 基础用法
|
|
61
|
+
* const { blocks, append, finalize } = useIncremark()
|
|
21
62
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
63
|
+
* // 启用打字机效果
|
|
64
|
+
* const { blocks, append, finalize, typewriter } = useIncremark({
|
|
65
|
+
* typewriter: {
|
|
66
|
+
* enabled: true, // 可动态切换
|
|
67
|
+
* charsPerTick: [1, 3],
|
|
68
|
+
* tickInterval: 30,
|
|
69
|
+
* effect: 'typing',
|
|
70
|
+
* cursor: '|'
|
|
25
71
|
* }
|
|
26
|
-
*
|
|
27
|
-
*
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* // 动态切换打字机效果
|
|
75
|
+
* typewriter.setEnabled(false)
|
|
28
76
|
*
|
|
29
|
-
* return
|
|
77
|
+
* return (
|
|
78
|
+
* <>
|
|
79
|
+
* <Incremark blocks={blocks} />
|
|
80
|
+
* {typewriter.isProcessing && <button onClick={typewriter.skip}>跳过</button>}
|
|
81
|
+
* </>
|
|
82
|
+
* )
|
|
30
83
|
* }
|
|
31
84
|
* ```
|
|
32
85
|
*/
|
|
@@ -39,7 +92,7 @@ declare function useIncremark(options?: UseIncremarkOptions): {
|
|
|
39
92
|
pendingBlocks: ParsedBlock[];
|
|
40
93
|
/** 当前完整的 AST */
|
|
41
94
|
ast: Root;
|
|
42
|
-
/**
|
|
95
|
+
/** 用于渲染的 blocks(根据打字机设置自动处理) */
|
|
43
96
|
blocks: BlockWithStableId$1[];
|
|
44
97
|
/** 是否正在加载 */
|
|
45
98
|
isLoading: boolean;
|
|
@@ -49,12 +102,14 @@ declare function useIncremark(options?: UseIncremarkOptions): {
|
|
|
49
102
|
finalize: () => IncrementalUpdate;
|
|
50
103
|
/** 强制中断 */
|
|
51
104
|
abort: () => IncrementalUpdate;
|
|
52
|
-
/**
|
|
105
|
+
/** 重置解析器和打字机 */
|
|
53
106
|
reset: () => void;
|
|
54
107
|
/** 一次性渲染(reset + append + finalize) */
|
|
55
108
|
render: (content: string) => IncrementalUpdate;
|
|
56
109
|
/** 解析器实例 */
|
|
57
110
|
parser: IncremarkParser;
|
|
111
|
+
/** 打字机控制 */
|
|
112
|
+
typewriter: TypewriterControls;
|
|
58
113
|
};
|
|
59
114
|
type UseIncremarkReturn = ReturnType<typeof useIncremark>;
|
|
60
115
|
|
|
@@ -180,7 +235,7 @@ declare const Incremark: React.FC<IncremarkProps>;
|
|
|
180
235
|
interface IncremarkRendererProps {
|
|
181
236
|
node: RootContent;
|
|
182
237
|
components?: Partial<Record<string, React.ComponentType<{
|
|
183
|
-
node:
|
|
238
|
+
node: RootContent;
|
|
184
239
|
}>>>;
|
|
185
240
|
}
|
|
186
241
|
/**
|
|
@@ -230,4 +285,4 @@ interface AutoScrollContainerRef {
|
|
|
230
285
|
*/
|
|
231
286
|
declare const AutoScrollContainer: React.ForwardRefExoticComponent<AutoScrollContainerProps & React.RefAttributes<AutoScrollContainerRef>>;
|
|
232
287
|
|
|
233
|
-
export { AutoScrollContainer, type AutoScrollContainerProps, type AutoScrollContainerRef, Incremark, type IncremarkProps, IncremarkRenderer, type IncremarkRendererProps, type UseBlockTransformerOptions, type UseBlockTransformerReturn, type UseDevToolsOptions, type UseIncremarkOptions, type UseIncremarkReturn, useBlockTransformer, useDevTools, useIncremark };
|
|
288
|
+
export { AutoScrollContainer, type AutoScrollContainerProps, type AutoScrollContainerRef, Incremark, type IncremarkProps, IncremarkRenderer, type IncremarkRendererProps, type TypewriterControls, type TypewriterOptions, type UseBlockTransformerOptions, type UseBlockTransformerReturn, type UseDevToolsOptions, type UseIncremarkOptions, type UseIncremarkReturn, useBlockTransformer, useDevTools, useIncremark };
|
package/dist/index.js
CHANGED
|
@@ -1,31 +1,125 @@
|
|
|
1
1
|
// src/hooks/useIncremark.ts
|
|
2
|
-
import { useState, useCallback, useMemo, useRef } from "react";
|
|
2
|
+
import { useState, useCallback, useMemo, useRef, useEffect } from "react";
|
|
3
3
|
import {
|
|
4
|
-
createIncremarkParser
|
|
4
|
+
createIncremarkParser,
|
|
5
|
+
createBlockTransformer,
|
|
6
|
+
defaultPlugins
|
|
5
7
|
} from "@incremark/core";
|
|
6
8
|
function useIncremark(options = {}) {
|
|
7
9
|
const parserRef = useRef(null);
|
|
10
|
+
const transformerRef = useRef(null);
|
|
11
|
+
const hasTypewriterConfig = !!options.typewriter;
|
|
12
|
+
const cursorRef = useRef(options.typewriter?.cursor ?? "|");
|
|
8
13
|
if (!parserRef.current) {
|
|
9
14
|
parserRef.current = createIncremarkParser(options);
|
|
10
15
|
}
|
|
16
|
+
if (hasTypewriterConfig && !transformerRef.current) {
|
|
17
|
+
const twOptions = options.typewriter;
|
|
18
|
+
transformerRef.current = createBlockTransformer({
|
|
19
|
+
charsPerTick: twOptions.charsPerTick ?? [1, 3],
|
|
20
|
+
tickInterval: twOptions.tickInterval ?? 30,
|
|
21
|
+
effect: twOptions.effect ?? "none",
|
|
22
|
+
pauseOnHidden: twOptions.pauseOnHidden ?? true,
|
|
23
|
+
plugins: twOptions.plugins ?? defaultPlugins,
|
|
24
|
+
onChange: () => {
|
|
25
|
+
setForceUpdateCount((c) => c + 1);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
11
29
|
const parser = parserRef.current;
|
|
30
|
+
const transformer = transformerRef.current;
|
|
12
31
|
const [markdown, setMarkdown] = useState("");
|
|
13
32
|
const [completedBlocks, setCompletedBlocks] = useState([]);
|
|
14
33
|
const [pendingBlocks, setPendingBlocks] = useState([]);
|
|
15
34
|
const [isLoading, setIsLoading] = useState(false);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
const [forceUpdateCount, setForceUpdateCount] = useState(0);
|
|
36
|
+
const [typewriterEnabled, setTypewriterEnabled] = useState(options.typewriter?.enabled ?? hasTypewriterConfig);
|
|
37
|
+
const [isTypewriterProcessing, setIsTypewriterProcessing] = useState(false);
|
|
38
|
+
const [isTypewriterPaused, setIsTypewriterPaused] = useState(false);
|
|
39
|
+
const [typewriterEffect, setTypewriterEffect] = useState(
|
|
40
|
+
options.typewriter?.effect ?? "none"
|
|
41
|
+
);
|
|
42
|
+
const sourceBlocks = useMemo(
|
|
43
|
+
() => completedBlocks.map((block) => ({
|
|
44
|
+
id: block.id,
|
|
45
|
+
node: block.node,
|
|
46
|
+
status: block.status
|
|
47
|
+
})),
|
|
48
|
+
[completedBlocks]
|
|
49
|
+
);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!transformer) return;
|
|
52
|
+
transformer.push(sourceBlocks);
|
|
53
|
+
const displayBlocks = transformer.getDisplayBlocks();
|
|
54
|
+
const currentDisplaying = displayBlocks.find((b) => !b.isDisplayComplete);
|
|
55
|
+
if (currentDisplaying) {
|
|
56
|
+
const updated = sourceBlocks.find((b) => b.id === currentDisplaying.id);
|
|
57
|
+
if (updated) {
|
|
58
|
+
transformer.update(updated);
|
|
59
|
+
}
|
|
20
60
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
61
|
+
setIsTypewriterProcessing(transformer.isProcessing());
|
|
62
|
+
setIsTypewriterPaused(transformer.isPausedState());
|
|
63
|
+
}, [sourceBlocks, transformer]);
|
|
64
|
+
const addCursorToNode = useCallback((node, cursor) => {
|
|
65
|
+
const cloned = JSON.parse(JSON.stringify(node));
|
|
66
|
+
function addToLast(n) {
|
|
67
|
+
if (n.children && n.children.length > 0) {
|
|
68
|
+
for (let i = n.children.length - 1; i >= 0; i--) {
|
|
69
|
+
if (addToLast(n.children[i])) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
n.children.push({ type: "text", value: cursor });
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (n.type === "text" && typeof n.value === "string") {
|
|
77
|
+
n.value += cursor;
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (typeof n.value === "string") {
|
|
81
|
+
n.value += cursor;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
26
85
|
}
|
|
27
|
-
|
|
28
|
-
|
|
86
|
+
addToLast(cloned);
|
|
87
|
+
return cloned;
|
|
88
|
+
}, []);
|
|
89
|
+
const blocks = useMemo(() => {
|
|
90
|
+
if (!typewriterEnabled || !transformer) {
|
|
91
|
+
const result = [];
|
|
92
|
+
for (const block of completedBlocks) {
|
|
93
|
+
result.push({ ...block, stableId: block.id });
|
|
94
|
+
}
|
|
95
|
+
for (let i = 0; i < pendingBlocks.length; i++) {
|
|
96
|
+
result.push({
|
|
97
|
+
...pendingBlocks[i],
|
|
98
|
+
stableId: `pending-${i}`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
const displayBlocks = transformer.getDisplayBlocks();
|
|
104
|
+
return displayBlocks.map((db, index) => {
|
|
105
|
+
const isPending = !db.isDisplayComplete;
|
|
106
|
+
const isLastPending = isPending && index === displayBlocks.length - 1;
|
|
107
|
+
let node = db.displayNode;
|
|
108
|
+
if (typewriterEffect === "typing" && isLastPending) {
|
|
109
|
+
node = addCursorToNode(db.displayNode, cursorRef.current);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
id: db.id,
|
|
113
|
+
stableId: db.id,
|
|
114
|
+
status: db.isDisplayComplete ? "completed" : "pending",
|
|
115
|
+
isLastPending,
|
|
116
|
+
node,
|
|
117
|
+
startOffset: 0,
|
|
118
|
+
endOffset: 0,
|
|
119
|
+
rawText: ""
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}, [completedBlocks, pendingBlocks, typewriterEnabled, typewriterEffect, addCursorToNode, forceUpdateCount]);
|
|
29
123
|
const ast = useMemo(
|
|
30
124
|
() => ({
|
|
31
125
|
type: "root",
|
|
@@ -65,7 +159,8 @@ function useIncremark(options = {}) {
|
|
|
65
159
|
setPendingBlocks([]);
|
|
66
160
|
setMarkdown("");
|
|
67
161
|
setIsLoading(false);
|
|
68
|
-
|
|
162
|
+
transformer?.reset();
|
|
163
|
+
}, [parser, transformer]);
|
|
69
164
|
const render = useCallback(
|
|
70
165
|
(content) => {
|
|
71
166
|
const update = parser.render(content);
|
|
@@ -77,6 +172,59 @@ function useIncremark(options = {}) {
|
|
|
77
172
|
},
|
|
78
173
|
[parser]
|
|
79
174
|
);
|
|
175
|
+
const skip = useCallback(() => {
|
|
176
|
+
transformer?.skip();
|
|
177
|
+
setIsTypewriterProcessing(false);
|
|
178
|
+
}, [transformer]);
|
|
179
|
+
const pause = useCallback(() => {
|
|
180
|
+
transformer?.pause();
|
|
181
|
+
setIsTypewriterPaused(true);
|
|
182
|
+
}, [transformer]);
|
|
183
|
+
const resume = useCallback(() => {
|
|
184
|
+
transformer?.resume();
|
|
185
|
+
setIsTypewriterPaused(false);
|
|
186
|
+
}, [transformer]);
|
|
187
|
+
const setTypewriterOptions = useCallback(
|
|
188
|
+
(opts) => {
|
|
189
|
+
if (opts.enabled !== void 0) {
|
|
190
|
+
setTypewriterEnabled(opts.enabled);
|
|
191
|
+
}
|
|
192
|
+
if (opts.charsPerTick !== void 0 || opts.tickInterval !== void 0 || opts.effect !== void 0 || opts.pauseOnHidden !== void 0) {
|
|
193
|
+
transformer?.setOptions({
|
|
194
|
+
charsPerTick: opts.charsPerTick,
|
|
195
|
+
tickInterval: opts.tickInterval,
|
|
196
|
+
effect: opts.effect,
|
|
197
|
+
pauseOnHidden: opts.pauseOnHidden
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (opts.effect !== void 0) {
|
|
201
|
+
setTypewriterEffect(opts.effect);
|
|
202
|
+
}
|
|
203
|
+
if (opts.cursor !== void 0) {
|
|
204
|
+
cursorRef.current = opts.cursor;
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
[transformer]
|
|
208
|
+
);
|
|
209
|
+
const typewriter = useMemo(
|
|
210
|
+
() => ({
|
|
211
|
+
enabled: typewriterEnabled,
|
|
212
|
+
setEnabled: setTypewriterEnabled,
|
|
213
|
+
isProcessing: isTypewriterProcessing,
|
|
214
|
+
isPaused: isTypewriterPaused,
|
|
215
|
+
effect: typewriterEffect,
|
|
216
|
+
skip,
|
|
217
|
+
pause,
|
|
218
|
+
resume,
|
|
219
|
+
setOptions: setTypewriterOptions
|
|
220
|
+
}),
|
|
221
|
+
[typewriterEnabled, isTypewriterProcessing, isTypewriterPaused, typewriterEffect, skip, pause, resume, setTypewriterOptions]
|
|
222
|
+
);
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
return () => {
|
|
225
|
+
transformer?.destroy();
|
|
226
|
+
};
|
|
227
|
+
}, [transformer]);
|
|
80
228
|
return {
|
|
81
229
|
/** 已收集的完整 Markdown 字符串 */
|
|
82
230
|
markdown,
|
|
@@ -86,7 +234,7 @@ function useIncremark(options = {}) {
|
|
|
86
234
|
pendingBlocks,
|
|
87
235
|
/** 当前完整的 AST */
|
|
88
236
|
ast,
|
|
89
|
-
/**
|
|
237
|
+
/** 用于渲染的 blocks(根据打字机设置自动处理) */
|
|
90
238
|
blocks,
|
|
91
239
|
/** 是否正在加载 */
|
|
92
240
|
isLoading,
|
|
@@ -96,22 +244,24 @@ function useIncremark(options = {}) {
|
|
|
96
244
|
finalize,
|
|
97
245
|
/** 强制中断 */
|
|
98
246
|
abort,
|
|
99
|
-
/**
|
|
247
|
+
/** 重置解析器和打字机 */
|
|
100
248
|
reset,
|
|
101
249
|
/** 一次性渲染(reset + append + finalize) */
|
|
102
250
|
render,
|
|
103
251
|
/** 解析器实例 */
|
|
104
|
-
parser
|
|
252
|
+
parser,
|
|
253
|
+
/** 打字机控制 */
|
|
254
|
+
typewriter
|
|
105
255
|
};
|
|
106
256
|
}
|
|
107
257
|
|
|
108
258
|
// src/hooks/useDevTools.ts
|
|
109
|
-
import { useEffect, useRef as useRef2 } from "react";
|
|
259
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
110
260
|
import { createDevTools } from "@incremark/devtools";
|
|
111
261
|
function useDevTools(incremark, options = {}) {
|
|
112
262
|
const devtoolsRef = useRef2(null);
|
|
113
263
|
const optionsRef = useRef2(options);
|
|
114
|
-
|
|
264
|
+
useEffect2(() => {
|
|
115
265
|
const devtools = createDevTools(optionsRef.current);
|
|
116
266
|
devtoolsRef.current = devtools;
|
|
117
267
|
incremark.parser.setOnChange((state) => {
|
|
@@ -138,9 +288,9 @@ function useDevTools(incremark, options = {}) {
|
|
|
138
288
|
}
|
|
139
289
|
|
|
140
290
|
// src/hooks/useBlockTransformer.ts
|
|
141
|
-
import { useState as useState2, useCallback as useCallback2, useRef as useRef3, useEffect as
|
|
291
|
+
import { useState as useState2, useCallback as useCallback2, useRef as useRef3, useEffect as useEffect3 } from "react";
|
|
142
292
|
import {
|
|
143
|
-
createBlockTransformer
|
|
293
|
+
createBlockTransformer as createBlockTransformer2
|
|
144
294
|
} from "@incremark/core";
|
|
145
295
|
function useBlockTransformer(sourceBlocks, options = {}) {
|
|
146
296
|
const [displayBlocks, setDisplayBlocks] = useState2([]);
|
|
@@ -149,7 +299,7 @@ function useBlockTransformer(sourceBlocks, options = {}) {
|
|
|
149
299
|
const [effect, setEffect] = useState2(options.effect ?? "none");
|
|
150
300
|
const transformerRef = useRef3(null);
|
|
151
301
|
if (!transformerRef.current) {
|
|
152
|
-
transformerRef.current =
|
|
302
|
+
transformerRef.current = createBlockTransformer2({
|
|
153
303
|
...options,
|
|
154
304
|
onChange: (blocks) => {
|
|
155
305
|
setDisplayBlocks(blocks);
|
|
@@ -159,7 +309,7 @@ function useBlockTransformer(sourceBlocks, options = {}) {
|
|
|
159
309
|
});
|
|
160
310
|
}
|
|
161
311
|
const transformer = transformerRef.current;
|
|
162
|
-
|
|
312
|
+
useEffect3(() => {
|
|
163
313
|
transformer.push(sourceBlocks);
|
|
164
314
|
const currentDisplaying = displayBlocks.find((b) => !b.isDisplayComplete);
|
|
165
315
|
if (currentDisplaying) {
|
|
@@ -169,7 +319,7 @@ function useBlockTransformer(sourceBlocks, options = {}) {
|
|
|
169
319
|
}
|
|
170
320
|
}
|
|
171
321
|
}, [sourceBlocks, transformer]);
|
|
172
|
-
|
|
322
|
+
useEffect3(() => {
|
|
173
323
|
return () => {
|
|
174
324
|
transformer.destroy();
|
|
175
325
|
};
|
|
@@ -214,12 +364,26 @@ function useBlockTransformer(sourceBlocks, options = {}) {
|
|
|
214
364
|
// src/components/IncremarkRenderer.tsx
|
|
215
365
|
import React from "react";
|
|
216
366
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
367
|
+
function getStableText(node) {
|
|
368
|
+
if (!node.chunks || node.chunks.length === 0) {
|
|
369
|
+
return node.value;
|
|
370
|
+
}
|
|
371
|
+
return node.value.slice(0, node.stableLength ?? 0);
|
|
372
|
+
}
|
|
217
373
|
function renderInlineChildren(children) {
|
|
218
374
|
if (!children) return null;
|
|
219
375
|
return children.map((child, i) => {
|
|
220
376
|
switch (child.type) {
|
|
221
|
-
case "text":
|
|
222
|
-
|
|
377
|
+
case "text": {
|
|
378
|
+
const textNode = child;
|
|
379
|
+
if (textNode.chunks && textNode.chunks.length > 0) {
|
|
380
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
381
|
+
getStableText(textNode),
|
|
382
|
+
textNode.chunks.map((chunk) => /* @__PURE__ */ jsx("span", { className: "incremark-fade-in", children: chunk.text }, chunk.createdAt))
|
|
383
|
+
] }, i);
|
|
384
|
+
}
|
|
385
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: textNode.value }, i);
|
|
386
|
+
}
|
|
223
387
|
case "strong":
|
|
224
388
|
return /* @__PURE__ */ jsx("strong", { children: renderInlineChildren(child.children) }, i);
|
|
225
389
|
case "emphasis":
|
|
@@ -236,6 +400,8 @@ function renderInlineChildren(children) {
|
|
|
236
400
|
return /* @__PURE__ */ jsx("del", { children: renderInlineChildren(child.children) }, i);
|
|
237
401
|
case "paragraph":
|
|
238
402
|
return /* @__PURE__ */ jsx(React.Fragment, { children: renderInlineChildren(child.children) }, i);
|
|
403
|
+
case "html":
|
|
404
|
+
return /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: child.value } }, i);
|
|
239
405
|
default:
|
|
240
406
|
return /* @__PURE__ */ jsx("span", { children: child.value || "" }, i);
|
|
241
407
|
}
|
|
@@ -254,7 +420,7 @@ var DefaultList = ({ node }) => {
|
|
|
254
420
|
const Tag = node.ordered ? "ol" : "ul";
|
|
255
421
|
return /* @__PURE__ */ jsx(Tag, { className: "incremark-list", children: node.children?.map((item, i) => /* @__PURE__ */ jsx("li", { children: renderInlineChildren(item.children) }, i)) });
|
|
256
422
|
};
|
|
257
|
-
var DefaultBlockquote = ({ node }) => /* @__PURE__ */ jsx("blockquote", { className: "incremark-blockquote", children: node.children?.map((child, i) => /* @__PURE__ */ jsx(React.Fragment, { children: child.type === "paragraph" ? /* @__PURE__ */ jsx("p", { children: renderInlineChildren(child.children) }) :
|
|
423
|
+
var DefaultBlockquote = ({ node }) => /* @__PURE__ */ jsx("blockquote", { className: "incremark-blockquote", children: node.children?.map((child, i) => /* @__PURE__ */ jsx(React.Fragment, { children: child.type === "paragraph" ? /* @__PURE__ */ jsx("p", { children: renderInlineChildren(child.children) }) : "children" in child && Array.isArray(child.children) ? renderInlineChildren(child.children) : null }, i)) });
|
|
258
424
|
var DefaultTable = ({ node }) => /* @__PURE__ */ jsx("div", { className: "incremark-table-wrapper", children: /* @__PURE__ */ jsxs("table", { className: "incremark-table", children: [
|
|
259
425
|
/* @__PURE__ */ jsx("thead", { children: node.children?.[0] && /* @__PURE__ */ jsx("tr", { children: node.children[0].children?.map((cell, i) => /* @__PURE__ */ jsx("th", { children: renderInlineChildren(cell.children) }, i)) }) }),
|
|
260
426
|
/* @__PURE__ */ jsx("tbody", { children: node.children?.slice(1).map((row, i) => /* @__PURE__ */ jsx("tr", { children: row.children?.map((cell, j) => /* @__PURE__ */ jsx("td", { children: renderInlineChildren(cell.children) }, j)) }, i)) })
|
|
@@ -298,7 +464,7 @@ var Incremark = ({
|
|
|
298
464
|
// src/components/AutoScrollContainer.tsx
|
|
299
465
|
import {
|
|
300
466
|
useRef as useRef4,
|
|
301
|
-
useEffect as
|
|
467
|
+
useEffect as useEffect4,
|
|
302
468
|
useCallback as useCallback3,
|
|
303
469
|
useState as useState3,
|
|
304
470
|
forwardRef,
|
|
@@ -363,14 +529,14 @@ var AutoScrollContainer = forwardRef(
|
|
|
363
529
|
lastScrollTopRef.current = scrollTop;
|
|
364
530
|
lastScrollHeightRef.current = scrollHeight;
|
|
365
531
|
}, [isNearBottom]);
|
|
366
|
-
|
|
532
|
+
useEffect4(() => {
|
|
367
533
|
const container = containerRef.current;
|
|
368
534
|
if (container) {
|
|
369
535
|
lastScrollTopRef.current = container.scrollTop;
|
|
370
536
|
lastScrollHeightRef.current = container.scrollHeight;
|
|
371
537
|
}
|
|
372
538
|
}, []);
|
|
373
|
-
|
|
539
|
+
useEffect4(() => {
|
|
374
540
|
const container = containerRef.current;
|
|
375
541
|
if (!container || !enabled) return;
|
|
376
542
|
const observer = new MutationObserver(() => {
|
|
@@ -423,7 +589,7 @@ AutoScrollContainer.displayName = "AutoScrollContainer";
|
|
|
423
589
|
// src/index.ts
|
|
424
590
|
import {
|
|
425
591
|
BlockTransformer as BlockTransformer2,
|
|
426
|
-
createBlockTransformer as
|
|
592
|
+
createBlockTransformer as createBlockTransformer3,
|
|
427
593
|
countChars,
|
|
428
594
|
sliceAst,
|
|
429
595
|
cloneNode,
|
|
@@ -432,7 +598,7 @@ import {
|
|
|
432
598
|
imagePlugin,
|
|
433
599
|
mathPlugin,
|
|
434
600
|
thematicBreakPlugin,
|
|
435
|
-
defaultPlugins,
|
|
601
|
+
defaultPlugins as defaultPlugins2,
|
|
436
602
|
allPlugins,
|
|
437
603
|
createPlugin
|
|
438
604
|
} from "@incremark/core";
|
|
@@ -445,9 +611,9 @@ export {
|
|
|
445
611
|
cloneNode,
|
|
446
612
|
codeBlockPlugin,
|
|
447
613
|
countChars,
|
|
448
|
-
|
|
614
|
+
createBlockTransformer3 as createBlockTransformer,
|
|
449
615
|
createPlugin,
|
|
450
|
-
defaultPlugins,
|
|
616
|
+
defaultPlugins2 as defaultPlugins,
|
|
451
617
|
imagePlugin,
|
|
452
618
|
mathPlugin,
|
|
453
619
|
mermaidPlugin,
|
package/dist/styles.css
CHANGED
|
@@ -189,3 +189,18 @@
|
|
|
189
189
|
font-size: 11px;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
/* ============ 渐入动画效果 ============ */
|
|
193
|
+
|
|
194
|
+
.incremark-fade-in {
|
|
195
|
+
animation: incremark-fade-in 0.4s ease-out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@keyframes incremark-fade-in {
|
|
199
|
+
from {
|
|
200
|
+
opacity: 0;
|
|
201
|
+
}
|
|
202
|
+
to {
|
|
203
|
+
opacity: 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@incremark/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Incremark React integration - Incremental Markdown parser for AI streaming",
|
|
6
6
|
"type": "module",
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
],
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": ">=18.0.0",
|
|
22
|
-
"@incremark/core": "0.
|
|
22
|
+
"@incremark/core": "0.1.2"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@incremark/devtools": "0.
|
|
25
|
+
"@incremark/devtools": "0.1.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
+
"@types/mdast": "^4.0.0",
|
|
28
29
|
"@types/react": "^18.2.0",
|
|
29
30
|
"react": "^18.2.0",
|
|
30
31
|
"tsup": "^8.0.0",
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"url": "https://github.com/kingshuaishuai/incremark.git",
|
|
36
37
|
"directory": "packages/react"
|
|
37
38
|
},
|
|
38
|
-
"homepage": "https://incremark
|
|
39
|
+
"homepage": "https://www.incremark.com/",
|
|
39
40
|
"scripts": {
|
|
40
41
|
"build": "tsup && cp src/styles.css dist/styles.css",
|
|
41
42
|
"dev": "tsup --watch"
|