@incremark/react 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +218 -14
- package/dist/index.js +1101 -213
- package/package.json +10 -5
- package/dist/styles.css +0 -206
package/dist/index.js
CHANGED
|
@@ -1,44 +1,167 @@
|
|
|
1
1
|
// src/hooks/useIncremark.ts
|
|
2
|
-
import { useState, useCallback, useMemo, useRef
|
|
2
|
+
import { useState as useState3, useCallback as useCallback3, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
3
|
+
import {
|
|
4
|
+
createIncremarkParser
|
|
5
|
+
} from "@incremark/core";
|
|
6
|
+
|
|
7
|
+
// src/contexts/DefinitionsContext.tsx
|
|
8
|
+
import { createContext, useContext, useState, useCallback } from "react";
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
var DefinitionsContext = createContext(void 0);
|
|
11
|
+
var DefinitionsProvider = ({ children }) => {
|
|
12
|
+
const [definitions, setDefinitionsState] = useState({});
|
|
13
|
+
const [footnoteDefinitions, setFootnoteDefinitionsState] = useState({});
|
|
14
|
+
const [footnoteReferenceOrder, setFootnoteReferenceOrderState] = useState([]);
|
|
15
|
+
const setDefinitions = useCallback((defs) => {
|
|
16
|
+
setDefinitionsState(defs);
|
|
17
|
+
}, []);
|
|
18
|
+
const setFootnoteDefinitions = useCallback((defs) => {
|
|
19
|
+
setFootnoteDefinitionsState(defs);
|
|
20
|
+
}, []);
|
|
21
|
+
const setFootnoteReferenceOrder = useCallback((order) => {
|
|
22
|
+
setFootnoteReferenceOrderState(order);
|
|
23
|
+
}, []);
|
|
24
|
+
const clearDefinitions = useCallback(() => {
|
|
25
|
+
setDefinitionsState({});
|
|
26
|
+
}, []);
|
|
27
|
+
const clearFootnoteDefinitions = useCallback(() => {
|
|
28
|
+
setFootnoteDefinitionsState({});
|
|
29
|
+
}, []);
|
|
30
|
+
const clearFootnoteReferenceOrder = useCallback(() => {
|
|
31
|
+
setFootnoteReferenceOrderState([]);
|
|
32
|
+
}, []);
|
|
33
|
+
const clearAllDefinitions = useCallback(() => {
|
|
34
|
+
setDefinitionsState({});
|
|
35
|
+
setFootnoteDefinitionsState({});
|
|
36
|
+
setFootnoteReferenceOrderState([]);
|
|
37
|
+
}, []);
|
|
38
|
+
const value = {
|
|
39
|
+
definitions,
|
|
40
|
+
footnoteDefinitions,
|
|
41
|
+
footnoteReferenceOrder,
|
|
42
|
+
setDefinitions,
|
|
43
|
+
setFootnoteDefinitions,
|
|
44
|
+
setFootnoteReferenceOrder,
|
|
45
|
+
clearDefinitions,
|
|
46
|
+
clearFootnoteDefinitions,
|
|
47
|
+
clearFootnoteReferenceOrder,
|
|
48
|
+
clearAllDefinitions
|
|
49
|
+
};
|
|
50
|
+
return /* @__PURE__ */ jsx(DefinitionsContext.Provider, { value, children });
|
|
51
|
+
};
|
|
52
|
+
function useDefinitions() {
|
|
53
|
+
const context = useContext(DefinitionsContext);
|
|
54
|
+
if (!context) {
|
|
55
|
+
throw new Error("useDefinitions must be used within a DefinitionsProvider");
|
|
56
|
+
}
|
|
57
|
+
return context;
|
|
58
|
+
}
|
|
59
|
+
function useProvideDefinitions() {
|
|
60
|
+
const [definitions, setDefinitionsState] = useState({});
|
|
61
|
+
const [footnoteDefinitions, setFootnoteDefinitionsState] = useState({});
|
|
62
|
+
const [footnoteReferenceOrder, setFootnoteReferenceOrderState] = useState([]);
|
|
63
|
+
const setDefinitions = useCallback((defs) => {
|
|
64
|
+
setDefinitionsState(defs);
|
|
65
|
+
}, []);
|
|
66
|
+
const setFootnoteDefinitions = useCallback((defs) => {
|
|
67
|
+
setFootnoteDefinitionsState(defs);
|
|
68
|
+
}, []);
|
|
69
|
+
const setFootnoteReferenceOrder = useCallback((order) => {
|
|
70
|
+
setFootnoteReferenceOrderState(order);
|
|
71
|
+
}, []);
|
|
72
|
+
const clearDefinitions = useCallback(() => {
|
|
73
|
+
setDefinitionsState({});
|
|
74
|
+
}, []);
|
|
75
|
+
const clearFootnoteDefinitions = useCallback(() => {
|
|
76
|
+
setFootnoteDefinitionsState({});
|
|
77
|
+
}, []);
|
|
78
|
+
const clearFootnoteReferenceOrder = useCallback(() => {
|
|
79
|
+
setFootnoteReferenceOrderState([]);
|
|
80
|
+
}, []);
|
|
81
|
+
const clearAllDefinitions = useCallback(() => {
|
|
82
|
+
setDefinitionsState({});
|
|
83
|
+
setFootnoteDefinitionsState({});
|
|
84
|
+
setFootnoteReferenceOrderState([]);
|
|
85
|
+
}, []);
|
|
86
|
+
const value = {
|
|
87
|
+
definitions,
|
|
88
|
+
footnoteDefinitions,
|
|
89
|
+
footnoteReferenceOrder,
|
|
90
|
+
setDefinitions,
|
|
91
|
+
setFootnoteDefinitions,
|
|
92
|
+
setFootnoteReferenceOrder,
|
|
93
|
+
clearDefinitions,
|
|
94
|
+
clearFootnoteDefinitions,
|
|
95
|
+
clearFootnoteReferenceOrder,
|
|
96
|
+
clearAllDefinitions
|
|
97
|
+
};
|
|
98
|
+
return { value, setDefinitions, setFootnoteDefinitions, setFootnoteReferenceOrder };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/hooks/useTypewriter.ts
|
|
102
|
+
import { useState as useState2, useCallback as useCallback2, useMemo, useRef, useEffect } from "react";
|
|
3
103
|
import {
|
|
4
|
-
createIncremarkParser,
|
|
5
104
|
createBlockTransformer,
|
|
6
105
|
defaultPlugins
|
|
7
106
|
} from "@incremark/core";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
107
|
+
|
|
108
|
+
// src/utils/cursor.ts
|
|
109
|
+
function addCursorToNode(node, cursor) {
|
|
110
|
+
const cloned = JSON.parse(JSON.stringify(node));
|
|
111
|
+
function addToLast(n) {
|
|
112
|
+
if (n.children && n.children.length > 0) {
|
|
113
|
+
for (let i = n.children.length - 1; i >= 0; i--) {
|
|
114
|
+
if (addToLast(n.children[i])) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
n.children.push({ type: "text", value: cursor });
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
if (n.type === "text" && typeof n.value === "string") {
|
|
122
|
+
n.value += cursor;
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
if (typeof n.value === "string") {
|
|
126
|
+
n.value += cursor;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
15
130
|
}
|
|
131
|
+
addToLast(cloned);
|
|
132
|
+
return cloned;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/hooks/useTypewriter.ts
|
|
136
|
+
function useTypewriter(options) {
|
|
137
|
+
const { typewriter: typewriterConfig, completedBlocks, pendingBlocks } = options;
|
|
138
|
+
const hasTypewriterConfig = !!typewriterConfig;
|
|
139
|
+
const cursorRef = useRef(typewriterConfig?.cursor ?? "|");
|
|
140
|
+
const transformerRef = useRef(null);
|
|
141
|
+
const completedBlocksCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
142
|
+
const [typewriterEnabled, setTypewriterEnabled] = useState2(typewriterConfig?.enabled ?? hasTypewriterConfig);
|
|
143
|
+
const [isTypewriterProcessing, setIsTypewriterProcessing] = useState2(false);
|
|
144
|
+
const [isTypewriterPaused, setIsTypewriterPaused] = useState2(false);
|
|
145
|
+
const [typewriterEffect, setTypewriterEffect] = useState2(
|
|
146
|
+
typewriterConfig?.effect ?? "none"
|
|
147
|
+
);
|
|
148
|
+
const [displayBlocks, setDisplayBlocks] = useState2([]);
|
|
16
149
|
if (hasTypewriterConfig && !transformerRef.current) {
|
|
17
|
-
const twOptions =
|
|
150
|
+
const twOptions = typewriterConfig;
|
|
18
151
|
transformerRef.current = createBlockTransformer({
|
|
19
152
|
charsPerTick: twOptions.charsPerTick ?? [1, 3],
|
|
20
153
|
tickInterval: twOptions.tickInterval ?? 30,
|
|
21
154
|
effect: twOptions.effect ?? "none",
|
|
22
155
|
pauseOnHidden: twOptions.pauseOnHidden ?? true,
|
|
23
156
|
plugins: twOptions.plugins ?? defaultPlugins,
|
|
24
|
-
onChange: () => {
|
|
25
|
-
|
|
157
|
+
onChange: (blocks2) => {
|
|
158
|
+
setDisplayBlocks(blocks2);
|
|
159
|
+
setIsTypewriterProcessing(transformerRef.current?.isProcessing() ?? false);
|
|
160
|
+
setIsTypewriterPaused(transformerRef.current?.isPausedState() ?? false);
|
|
26
161
|
}
|
|
27
162
|
});
|
|
28
163
|
}
|
|
29
|
-
const parser = parserRef.current;
|
|
30
164
|
const transformer = transformerRef.current;
|
|
31
|
-
const [markdown, setMarkdown] = useState("");
|
|
32
|
-
const [completedBlocks, setCompletedBlocks] = useState([]);
|
|
33
|
-
const [pendingBlocks, setPendingBlocks] = useState([]);
|
|
34
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
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
165
|
const sourceBlocks = useMemo(
|
|
43
166
|
() => completedBlocks.map((block) => ({
|
|
44
167
|
id: block.id,
|
|
@@ -50,8 +173,8 @@ function useIncremark(options = {}) {
|
|
|
50
173
|
useEffect(() => {
|
|
51
174
|
if (!transformer) return;
|
|
52
175
|
transformer.push(sourceBlocks);
|
|
53
|
-
const
|
|
54
|
-
const currentDisplaying =
|
|
176
|
+
const displayBlocks2 = transformer.getDisplayBlocks();
|
|
177
|
+
const currentDisplaying = displayBlocks2.find((b) => !b.isDisplayComplete);
|
|
55
178
|
if (currentDisplaying) {
|
|
56
179
|
const updated = sourceBlocks.find((b) => b.id === currentDisplaying.id);
|
|
57
180
|
if (updated) {
|
|
@@ -61,31 +184,6 @@ function useIncremark(options = {}) {
|
|
|
61
184
|
setIsTypewriterProcessing(transformer.isProcessing());
|
|
62
185
|
setIsTypewriterPaused(transformer.isPausedState());
|
|
63
186
|
}, [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;
|
|
85
|
-
}
|
|
86
|
-
addToLast(cloned);
|
|
87
|
-
return cloned;
|
|
88
|
-
}, []);
|
|
89
187
|
const blocks = useMemo(() => {
|
|
90
188
|
if (!typewriterEnabled || !transformer) {
|
|
91
189
|
const result = [];
|
|
@@ -100,15 +198,20 @@ function useIncremark(options = {}) {
|
|
|
100
198
|
}
|
|
101
199
|
return result;
|
|
102
200
|
}
|
|
103
|
-
const displayBlocks = transformer.getDisplayBlocks();
|
|
104
201
|
return displayBlocks.map((db, index) => {
|
|
105
202
|
const isPending = !db.isDisplayComplete;
|
|
106
203
|
const isLastPending = isPending && index === displayBlocks.length - 1;
|
|
204
|
+
if (db.isDisplayComplete) {
|
|
205
|
+
const cached = completedBlocksCacheRef.current.get(db.id);
|
|
206
|
+
if (cached) {
|
|
207
|
+
return cached;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
107
210
|
let node = db.displayNode;
|
|
108
211
|
if (typewriterEffect === "typing" && isLastPending) {
|
|
109
212
|
node = addCursorToNode(db.displayNode, cursorRef.current);
|
|
110
213
|
}
|
|
111
|
-
|
|
214
|
+
const block = {
|
|
112
215
|
id: db.id,
|
|
113
216
|
stableId: db.id,
|
|
114
217
|
status: db.isDisplayComplete ? "completed" : "pending",
|
|
@@ -118,73 +221,25 @@ function useIncremark(options = {}) {
|
|
|
118
221
|
endOffset: 0,
|
|
119
222
|
rawText: ""
|
|
120
223
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const ast = useMemo(
|
|
124
|
-
() => ({
|
|
125
|
-
type: "root",
|
|
126
|
-
children: [...completedBlocks.map((b) => b.node), ...pendingBlocks.map((b) => b.node)]
|
|
127
|
-
}),
|
|
128
|
-
[completedBlocks, pendingBlocks]
|
|
129
|
-
);
|
|
130
|
-
const append = useCallback(
|
|
131
|
-
(chunk) => {
|
|
132
|
-
setIsLoading(true);
|
|
133
|
-
const update = parser.append(chunk);
|
|
134
|
-
setMarkdown(parser.getBuffer());
|
|
135
|
-
if (update.completed.length > 0) {
|
|
136
|
-
setCompletedBlocks((prev) => [...prev, ...update.completed]);
|
|
224
|
+
if (db.isDisplayComplete) {
|
|
225
|
+
completedBlocksCacheRef.current.set(db.id, block);
|
|
137
226
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
);
|
|
143
|
-
const finalize = useCallback(() => {
|
|
144
|
-
const update = parser.finalize();
|
|
145
|
-
setMarkdown(parser.getBuffer());
|
|
146
|
-
if (update.completed.length > 0) {
|
|
147
|
-
setCompletedBlocks((prev) => [...prev, ...update.completed]);
|
|
148
|
-
}
|
|
149
|
-
setPendingBlocks([]);
|
|
150
|
-
setIsLoading(false);
|
|
151
|
-
return update;
|
|
152
|
-
}, [parser]);
|
|
153
|
-
const abort = useCallback(() => {
|
|
154
|
-
return finalize();
|
|
155
|
-
}, [finalize]);
|
|
156
|
-
const reset = useCallback(() => {
|
|
157
|
-
parser.reset();
|
|
158
|
-
setCompletedBlocks([]);
|
|
159
|
-
setPendingBlocks([]);
|
|
160
|
-
setMarkdown("");
|
|
161
|
-
setIsLoading(false);
|
|
162
|
-
transformer?.reset();
|
|
163
|
-
}, [parser, transformer]);
|
|
164
|
-
const render = useCallback(
|
|
165
|
-
(content) => {
|
|
166
|
-
const update = parser.render(content);
|
|
167
|
-
setMarkdown(parser.getBuffer());
|
|
168
|
-
setCompletedBlocks(parser.getCompletedBlocks());
|
|
169
|
-
setPendingBlocks([]);
|
|
170
|
-
setIsLoading(false);
|
|
171
|
-
return update;
|
|
172
|
-
},
|
|
173
|
-
[parser]
|
|
174
|
-
);
|
|
175
|
-
const skip = useCallback(() => {
|
|
227
|
+
return block;
|
|
228
|
+
});
|
|
229
|
+
}, [completedBlocks, pendingBlocks, typewriterEnabled, typewriterEffect, displayBlocks]);
|
|
230
|
+
const skip = useCallback2(() => {
|
|
176
231
|
transformer?.skip();
|
|
177
232
|
setIsTypewriterProcessing(false);
|
|
178
233
|
}, [transformer]);
|
|
179
|
-
const pause =
|
|
234
|
+
const pause = useCallback2(() => {
|
|
180
235
|
transformer?.pause();
|
|
181
236
|
setIsTypewriterPaused(true);
|
|
182
237
|
}, [transformer]);
|
|
183
|
-
const resume =
|
|
238
|
+
const resume = useCallback2(() => {
|
|
184
239
|
transformer?.resume();
|
|
185
240
|
setIsTypewriterPaused(false);
|
|
186
241
|
}, [transformer]);
|
|
187
|
-
const setTypewriterOptions =
|
|
242
|
+
const setTypewriterOptions = useCallback2(
|
|
188
243
|
(opts) => {
|
|
189
244
|
if (opts.enabled !== void 0) {
|
|
190
245
|
setTypewriterEnabled(opts.enabled);
|
|
@@ -206,7 +261,7 @@ function useIncremark(options = {}) {
|
|
|
206
261
|
},
|
|
207
262
|
[transformer]
|
|
208
263
|
);
|
|
209
|
-
const
|
|
264
|
+
const typewriterControls = useMemo(
|
|
210
265
|
() => ({
|
|
211
266
|
enabled: typewriterEnabled,
|
|
212
267
|
setEnabled: setTypewriterEnabled,
|
|
@@ -225,6 +280,108 @@ function useIncremark(options = {}) {
|
|
|
225
280
|
transformer?.destroy();
|
|
226
281
|
};
|
|
227
282
|
}, [transformer]);
|
|
283
|
+
return {
|
|
284
|
+
blocks,
|
|
285
|
+
typewriter: typewriterControls,
|
|
286
|
+
transformer
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/hooks/useIncremark.ts
|
|
291
|
+
function useIncremark(options = {}) {
|
|
292
|
+
const parserRef = useRef2(null);
|
|
293
|
+
const { value: definitionsContextValue, setDefinitions, setFootnoteDefinitions, setFootnoteReferenceOrder } = useProvideDefinitions();
|
|
294
|
+
const _definitionsContextValue = definitionsContextValue;
|
|
295
|
+
const lastDefinitionsEmptyRef = useRef2(true);
|
|
296
|
+
const lastFootnoteDefinitionsEmptyRef = useRef2(true);
|
|
297
|
+
if (!parserRef.current) {
|
|
298
|
+
parserRef.current = createIncremarkParser({
|
|
299
|
+
...options,
|
|
300
|
+
onChange: (state) => {
|
|
301
|
+
const definitionsIsEmpty = Object.keys(state.definitions).length === 0;
|
|
302
|
+
const footnoteDefinitionsIsEmpty = Object.keys(state.footnoteDefinitions).length === 0;
|
|
303
|
+
if (!(definitionsIsEmpty && lastDefinitionsEmptyRef.current)) {
|
|
304
|
+
setDefinitions(state.definitions);
|
|
305
|
+
lastDefinitionsEmptyRef.current = definitionsIsEmpty;
|
|
306
|
+
}
|
|
307
|
+
if (!(footnoteDefinitionsIsEmpty && lastFootnoteDefinitionsEmptyRef.current)) {
|
|
308
|
+
setFootnoteDefinitions(state.footnoteDefinitions);
|
|
309
|
+
lastFootnoteDefinitionsEmptyRef.current = footnoteDefinitionsIsEmpty;
|
|
310
|
+
}
|
|
311
|
+
options.onChange?.(state);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const parser = parserRef.current;
|
|
316
|
+
const [markdown, setMarkdown] = useState3("");
|
|
317
|
+
const [completedBlocks, setCompletedBlocks] = useState3([]);
|
|
318
|
+
const [pendingBlocks, setPendingBlocks] = useState3([]);
|
|
319
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
320
|
+
const [isFinalized, setIsFinalized] = useState3(false);
|
|
321
|
+
const { blocks, typewriter, transformer } = useTypewriter({
|
|
322
|
+
typewriter: options.typewriter,
|
|
323
|
+
completedBlocks,
|
|
324
|
+
pendingBlocks
|
|
325
|
+
});
|
|
326
|
+
const ast = useMemo2(
|
|
327
|
+
() => ({
|
|
328
|
+
type: "root",
|
|
329
|
+
children: [...completedBlocks.map((b) => b.node), ...pendingBlocks.map((b) => b.node)]
|
|
330
|
+
}),
|
|
331
|
+
[completedBlocks, pendingBlocks]
|
|
332
|
+
);
|
|
333
|
+
const append = useCallback3(
|
|
334
|
+
(chunk) => {
|
|
335
|
+
setIsLoading(true);
|
|
336
|
+
const update = parser.append(chunk);
|
|
337
|
+
setMarkdown(parser.getBuffer());
|
|
338
|
+
if (update.completed.length > 0) {
|
|
339
|
+
setCompletedBlocks((prev) => [...prev, ...update.completed]);
|
|
340
|
+
}
|
|
341
|
+
setPendingBlocks(update.pending);
|
|
342
|
+
setFootnoteReferenceOrder(update.footnoteReferenceOrder);
|
|
343
|
+
return update;
|
|
344
|
+
},
|
|
345
|
+
[parser, setFootnoteReferenceOrder]
|
|
346
|
+
);
|
|
347
|
+
const finalize = useCallback3(() => {
|
|
348
|
+
const update = parser.finalize();
|
|
349
|
+
setMarkdown(parser.getBuffer());
|
|
350
|
+
if (update.completed.length > 0) {
|
|
351
|
+
setCompletedBlocks((prev) => [...prev, ...update.completed]);
|
|
352
|
+
}
|
|
353
|
+
setPendingBlocks([]);
|
|
354
|
+
setIsLoading(false);
|
|
355
|
+
setIsFinalized(true);
|
|
356
|
+
setFootnoteReferenceOrder(update.footnoteReferenceOrder);
|
|
357
|
+
return update;
|
|
358
|
+
}, [parser, setFootnoteReferenceOrder]);
|
|
359
|
+
const abort = useCallback3(() => {
|
|
360
|
+
return finalize();
|
|
361
|
+
}, [finalize]);
|
|
362
|
+
const reset = useCallback3(() => {
|
|
363
|
+
parser.reset();
|
|
364
|
+
setCompletedBlocks([]);
|
|
365
|
+
setPendingBlocks([]);
|
|
366
|
+
setMarkdown("");
|
|
367
|
+
setIsLoading(false);
|
|
368
|
+
setIsFinalized(false);
|
|
369
|
+
setFootnoteReferenceOrder([]);
|
|
370
|
+
transformer?.reset();
|
|
371
|
+
}, [parser, transformer, setFootnoteReferenceOrder]);
|
|
372
|
+
const render = useCallback3(
|
|
373
|
+
(content) => {
|
|
374
|
+
const update = parser.render(content);
|
|
375
|
+
setMarkdown(parser.getBuffer());
|
|
376
|
+
setCompletedBlocks(parser.getCompletedBlocks());
|
|
377
|
+
setPendingBlocks([]);
|
|
378
|
+
setIsLoading(false);
|
|
379
|
+
setIsFinalized(true);
|
|
380
|
+
setFootnoteReferenceOrder(update.footnoteReferenceOrder);
|
|
381
|
+
return update;
|
|
382
|
+
},
|
|
383
|
+
[parser, setFootnoteReferenceOrder]
|
|
384
|
+
);
|
|
228
385
|
return {
|
|
229
386
|
/** 已收集的完整 Markdown 字符串 */
|
|
230
387
|
markdown,
|
|
@@ -238,6 +395,8 @@ function useIncremark(options = {}) {
|
|
|
238
395
|
blocks,
|
|
239
396
|
/** 是否正在加载 */
|
|
240
397
|
isLoading,
|
|
398
|
+
/** 是否已完成(finalize) */
|
|
399
|
+
isFinalized,
|
|
241
400
|
/** 追加内容 */
|
|
242
401
|
append,
|
|
243
402
|
/** 完成解析 */
|
|
@@ -251,16 +410,18 @@ function useIncremark(options = {}) {
|
|
|
251
410
|
/** 解析器实例 */
|
|
252
411
|
parser,
|
|
253
412
|
/** 打字机控制 */
|
|
254
|
-
typewriter
|
|
413
|
+
typewriter,
|
|
414
|
+
/** @internal 提供给 Incremark 组件使用的 context value */
|
|
415
|
+
_definitionsContextValue
|
|
255
416
|
};
|
|
256
417
|
}
|
|
257
418
|
|
|
258
419
|
// src/hooks/useDevTools.ts
|
|
259
|
-
import { useEffect as useEffect2, useRef as
|
|
420
|
+
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
260
421
|
import { createDevTools } from "@incremark/devtools";
|
|
261
422
|
function useDevTools(incremark, options = {}) {
|
|
262
|
-
const devtoolsRef =
|
|
263
|
-
const optionsRef =
|
|
423
|
+
const devtoolsRef = useRef3(null);
|
|
424
|
+
const optionsRef = useRef3(options);
|
|
264
425
|
useEffect2(() => {
|
|
265
426
|
const devtools = createDevTools(optionsRef.current);
|
|
266
427
|
devtoolsRef.current = devtools;
|
|
@@ -288,16 +449,16 @@ function useDevTools(incremark, options = {}) {
|
|
|
288
449
|
}
|
|
289
450
|
|
|
290
451
|
// src/hooks/useBlockTransformer.ts
|
|
291
|
-
import { useState as
|
|
452
|
+
import { useState as useState4, useCallback as useCallback4, useRef as useRef4, useEffect as useEffect3 } from "react";
|
|
292
453
|
import {
|
|
293
454
|
createBlockTransformer as createBlockTransformer2
|
|
294
455
|
} from "@incremark/core";
|
|
295
456
|
function useBlockTransformer(sourceBlocks, options = {}) {
|
|
296
|
-
const [displayBlocks, setDisplayBlocks] =
|
|
297
|
-
const [isProcessing, setIsProcessing] =
|
|
298
|
-
const [isPaused, setIsPaused] =
|
|
299
|
-
const [effect, setEffect] =
|
|
300
|
-
const transformerRef =
|
|
457
|
+
const [displayBlocks, setDisplayBlocks] = useState4([]);
|
|
458
|
+
const [isProcessing, setIsProcessing] = useState4(false);
|
|
459
|
+
const [isPaused, setIsPaused] = useState4(false);
|
|
460
|
+
const [effect, setEffect] = useState4(options.effect ?? "none");
|
|
461
|
+
const transformerRef = useRef4(null);
|
|
301
462
|
if (!transformerRef.current) {
|
|
302
463
|
transformerRef.current = createBlockTransformer2({
|
|
303
464
|
...options,
|
|
@@ -324,21 +485,21 @@ function useBlockTransformer(sourceBlocks, options = {}) {
|
|
|
324
485
|
transformer.destroy();
|
|
325
486
|
};
|
|
326
487
|
}, [transformer]);
|
|
327
|
-
const skip =
|
|
488
|
+
const skip = useCallback4(() => {
|
|
328
489
|
transformer.skip();
|
|
329
490
|
}, [transformer]);
|
|
330
|
-
const reset =
|
|
491
|
+
const reset = useCallback4(() => {
|
|
331
492
|
transformer.reset();
|
|
332
493
|
}, [transformer]);
|
|
333
|
-
const pause =
|
|
494
|
+
const pause = useCallback4(() => {
|
|
334
495
|
transformer.pause();
|
|
335
496
|
setIsPaused(true);
|
|
336
497
|
}, [transformer]);
|
|
337
|
-
const resume =
|
|
498
|
+
const resume = useCallback4(() => {
|
|
338
499
|
transformer.resume();
|
|
339
500
|
setIsPaused(false);
|
|
340
501
|
}, [transformer]);
|
|
341
|
-
const setOptionsCallback =
|
|
502
|
+
const setOptionsCallback = useCallback4(
|
|
342
503
|
(opts) => {
|
|
343
504
|
transformer.setOptions(opts);
|
|
344
505
|
if (opts.effect !== void 0) {
|
|
@@ -362,71 +523,617 @@ function useBlockTransformer(sourceBlocks, options = {}) {
|
|
|
362
523
|
}
|
|
363
524
|
|
|
364
525
|
// src/components/IncremarkRenderer.tsx
|
|
365
|
-
import
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
526
|
+
import React6 from "react";
|
|
527
|
+
|
|
528
|
+
// src/components/IncremarkInline.tsx
|
|
529
|
+
import React3 from "react";
|
|
530
|
+
import {
|
|
531
|
+
hasChunks,
|
|
532
|
+
getStableText,
|
|
533
|
+
isHtmlNode
|
|
534
|
+
} from "@incremark/shared";
|
|
535
|
+
|
|
536
|
+
// src/components/IncremarkHtmlElement.tsx
|
|
537
|
+
import React2 from "react";
|
|
538
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
539
|
+
var INLINE_ELEMENTS = [
|
|
540
|
+
"a",
|
|
541
|
+
"abbr",
|
|
542
|
+
"acronym",
|
|
543
|
+
"b",
|
|
544
|
+
"bdo",
|
|
545
|
+
"big",
|
|
546
|
+
"br",
|
|
547
|
+
"button",
|
|
548
|
+
"cite",
|
|
549
|
+
"code",
|
|
550
|
+
"dfn",
|
|
551
|
+
"em",
|
|
552
|
+
"i",
|
|
553
|
+
"img",
|
|
554
|
+
"input",
|
|
555
|
+
"kbd",
|
|
556
|
+
"label",
|
|
557
|
+
"map",
|
|
558
|
+
"object",
|
|
559
|
+
"output",
|
|
560
|
+
"q",
|
|
561
|
+
"samp",
|
|
562
|
+
"script",
|
|
563
|
+
"select",
|
|
564
|
+
"small",
|
|
565
|
+
"span",
|
|
566
|
+
"strong",
|
|
567
|
+
"sub",
|
|
568
|
+
"sup",
|
|
569
|
+
"textarea",
|
|
570
|
+
"time",
|
|
571
|
+
"tt",
|
|
572
|
+
"var"
|
|
573
|
+
];
|
|
574
|
+
var VOID_ELEMENTS = [
|
|
575
|
+
"area",
|
|
576
|
+
"base",
|
|
577
|
+
"br",
|
|
578
|
+
"col",
|
|
579
|
+
"embed",
|
|
580
|
+
"hr",
|
|
581
|
+
"img",
|
|
582
|
+
"input",
|
|
583
|
+
"link",
|
|
584
|
+
"meta",
|
|
585
|
+
"param",
|
|
586
|
+
"source",
|
|
587
|
+
"track",
|
|
588
|
+
"wbr"
|
|
589
|
+
];
|
|
590
|
+
function isInlineElement(tagName) {
|
|
591
|
+
return INLINE_ELEMENTS.includes(tagName.toLowerCase());
|
|
592
|
+
}
|
|
593
|
+
function isVoidElement(tagName) {
|
|
594
|
+
return VOID_ELEMENTS.includes(tagName.toLowerCase());
|
|
595
|
+
}
|
|
596
|
+
function hasOnlyInlineChildren(children) {
|
|
597
|
+
if (!children || children.length === 0) return true;
|
|
598
|
+
return children.every((child) => {
|
|
599
|
+
const type = child.type;
|
|
600
|
+
const inlineTypes = ["text", "strong", "emphasis", "inlineCode", "link", "image", "break", "html", "htmlElement"];
|
|
601
|
+
if (inlineTypes.includes(type)) {
|
|
602
|
+
if (type === "htmlElement") {
|
|
603
|
+
return isInlineElement(child.tagName);
|
|
604
|
+
}
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
return false;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
function filterAttrs(attrs) {
|
|
611
|
+
const result = {};
|
|
612
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
613
|
+
if (key.toLowerCase().startsWith("on")) continue;
|
|
614
|
+
result[key] = value;
|
|
370
615
|
}
|
|
371
|
-
return
|
|
616
|
+
return result;
|
|
372
617
|
}
|
|
373
|
-
function
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
618
|
+
function parseStyleString(styleStr) {
|
|
619
|
+
const result = {};
|
|
620
|
+
if (!styleStr || typeof styleStr !== "string") return result;
|
|
621
|
+
try {
|
|
622
|
+
const rules = styleStr.split(";");
|
|
623
|
+
for (const rule of rules) {
|
|
624
|
+
const trimmed = rule.trim();
|
|
625
|
+
if (!trimmed) continue;
|
|
626
|
+
const colonIndex = trimmed.indexOf(":");
|
|
627
|
+
if (colonIndex === -1) continue;
|
|
628
|
+
const property = trimmed.slice(0, colonIndex).trim();
|
|
629
|
+
const value = trimmed.slice(colonIndex + 1).trim();
|
|
630
|
+
if (!property || !value) continue;
|
|
631
|
+
if (!/^[a-zA-Z-][a-zA-Z0-9-]*$/.test(property)) continue;
|
|
632
|
+
const camelProperty = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
633
|
+
result[camelProperty] = value;
|
|
634
|
+
}
|
|
635
|
+
} catch {
|
|
636
|
+
console.warn("[IncremarkHtmlElement] Failed to parse style string:", styleStr);
|
|
637
|
+
return {};
|
|
638
|
+
}
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
function toReactProps(attrs) {
|
|
642
|
+
const filtered = filterAttrs(attrs);
|
|
643
|
+
const result = {};
|
|
644
|
+
for (const [key, value] of Object.entries(filtered)) {
|
|
645
|
+
if (key === "class") {
|
|
646
|
+
result.className = value;
|
|
647
|
+
} else if (key === "for") {
|
|
648
|
+
result.htmlFor = value;
|
|
649
|
+
} else if (key === "style") {
|
|
650
|
+
result.style = parseStyleString(value);
|
|
651
|
+
} else {
|
|
652
|
+
result[key] = value;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
function renderChildren(children) {
|
|
658
|
+
if (!children || children.length === 0) return null;
|
|
659
|
+
if (hasOnlyInlineChildren(children)) {
|
|
660
|
+
return /* @__PURE__ */ jsx2(IncremarkInline, { nodes: children });
|
|
661
|
+
}
|
|
662
|
+
return children.map((child, idx) => {
|
|
663
|
+
if (child.type === "htmlElement") {
|
|
664
|
+
return /* @__PURE__ */ jsx2(IncremarkHtmlElement, { node: child }, idx);
|
|
665
|
+
}
|
|
666
|
+
if (child.type === "text") {
|
|
667
|
+
return /* @__PURE__ */ jsx2(React2.Fragment, { children: child.value }, idx);
|
|
668
|
+
}
|
|
669
|
+
if (["strong", "emphasis", "inlineCode", "link", "image", "break"].includes(child.type)) {
|
|
670
|
+
return /* @__PURE__ */ jsx2(IncremarkInline, { nodes: [child] }, idx);
|
|
407
671
|
}
|
|
672
|
+
if (child.type === "paragraph") {
|
|
673
|
+
return /* @__PURE__ */ jsx2("p", { children: /* @__PURE__ */ jsx2(IncremarkInline, { nodes: child.children }) }, idx);
|
|
674
|
+
}
|
|
675
|
+
return /* @__PURE__ */ jsx2("div", { className: "incremark-unknown-child", children: child.type }, idx);
|
|
408
676
|
});
|
|
409
677
|
}
|
|
410
|
-
var
|
|
678
|
+
var IncremarkHtmlElement = ({ node }) => {
|
|
679
|
+
const { tagName, attrs, children } = node;
|
|
680
|
+
const Tag = tagName;
|
|
681
|
+
const reactProps = toReactProps(attrs);
|
|
682
|
+
if (isVoidElement(tagName)) {
|
|
683
|
+
return /* @__PURE__ */ jsx2(Tag, { ...reactProps, className: `incremark-html-element incremark-${tagName} ${reactProps.className || ""}` });
|
|
684
|
+
}
|
|
685
|
+
return /* @__PURE__ */ jsx2(Tag, { ...reactProps, className: `incremark-html-element incremark-${tagName} ${reactProps.className || ""}`, children: renderChildren(children) });
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// src/components/IncremarkInline.tsx
|
|
689
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
690
|
+
function isHtmlElementNode(node) {
|
|
691
|
+
return node.type === "htmlElement";
|
|
692
|
+
}
|
|
693
|
+
function isImageReference(node) {
|
|
694
|
+
return node.type === "imageReference";
|
|
695
|
+
}
|
|
696
|
+
function isLinkReference(node) {
|
|
697
|
+
return node.type === "linkReference";
|
|
698
|
+
}
|
|
699
|
+
var IncremarkInline = ({ nodes }) => {
|
|
700
|
+
if (!nodes || nodes.length === 0) return null;
|
|
701
|
+
const { definitions, footnoteDefinitions } = useDefinitions();
|
|
702
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: nodes.map((node, i) => {
|
|
703
|
+
if (node.type === "text") {
|
|
704
|
+
const textNode = node;
|
|
705
|
+
if (hasChunks(node) && textNode.chunks && textNode.chunks.length > 0) {
|
|
706
|
+
return /* @__PURE__ */ jsxs(React3.Fragment, { children: [
|
|
707
|
+
getStableText(textNode),
|
|
708
|
+
textNode.chunks.map((chunk) => /* @__PURE__ */ jsx3("span", { "data-chunk-key": chunk.createdAt, className: "incremark-fade-in", children: chunk.text }, chunk.createdAt))
|
|
709
|
+
] }, i);
|
|
710
|
+
}
|
|
711
|
+
return /* @__PURE__ */ jsx3(React3.Fragment, { children: node.value }, i);
|
|
712
|
+
}
|
|
713
|
+
if (isHtmlElementNode(node)) {
|
|
714
|
+
return /* @__PURE__ */ jsx3(IncremarkHtmlElement, { node }, i);
|
|
715
|
+
}
|
|
716
|
+
if (isHtmlNode(node)) {
|
|
717
|
+
return /* @__PURE__ */ jsx3(
|
|
718
|
+
"span",
|
|
719
|
+
{
|
|
720
|
+
style: { display: "contents" },
|
|
721
|
+
dangerouslySetInnerHTML: { __html: node.value }
|
|
722
|
+
},
|
|
723
|
+
i
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
if (node.type === "strong") {
|
|
727
|
+
return /* @__PURE__ */ jsx3("strong", { children: /* @__PURE__ */ jsx3(IncremarkInline, { nodes: node.children }) }, i);
|
|
728
|
+
}
|
|
729
|
+
if (node.type === "emphasis") {
|
|
730
|
+
return /* @__PURE__ */ jsx3("em", { children: /* @__PURE__ */ jsx3(IncremarkInline, { nodes: node.children }) }, i);
|
|
731
|
+
}
|
|
732
|
+
if (node.type === "inlineCode") {
|
|
733
|
+
return /* @__PURE__ */ jsx3("code", { className: "incremark-inline-code", children: node.value }, i);
|
|
734
|
+
}
|
|
735
|
+
if (node.type === "link") {
|
|
736
|
+
return /* @__PURE__ */ jsx3("a", { href: node.url, target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx3(IncremarkInline, { nodes: node.children }) }, i);
|
|
737
|
+
}
|
|
738
|
+
if (node.type === "image") {
|
|
739
|
+
const imageNode = node;
|
|
740
|
+
return /* @__PURE__ */ jsx3(
|
|
741
|
+
"img",
|
|
742
|
+
{
|
|
743
|
+
src: imageNode.url,
|
|
744
|
+
alt: imageNode.alt || "",
|
|
745
|
+
title: imageNode.title || void 0,
|
|
746
|
+
loading: "lazy"
|
|
747
|
+
},
|
|
748
|
+
i
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
if (isImageReference(node)) {
|
|
752
|
+
const definition = definitions[node.identifier];
|
|
753
|
+
if (definition) {
|
|
754
|
+
return /* @__PURE__ */ jsx3(
|
|
755
|
+
"img",
|
|
756
|
+
{
|
|
757
|
+
src: definition.url,
|
|
758
|
+
alt: node.alt || "",
|
|
759
|
+
title: definition.title || void 0,
|
|
760
|
+
loading: "lazy"
|
|
761
|
+
},
|
|
762
|
+
i
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
return /* @__PURE__ */ jsxs("span", { className: "incremark-image-ref-missing", children: [
|
|
766
|
+
"![",
|
|
767
|
+
node.alt,
|
|
768
|
+
"][",
|
|
769
|
+
node.identifier || node.label,
|
|
770
|
+
"]"
|
|
771
|
+
] }, i);
|
|
772
|
+
}
|
|
773
|
+
if (isLinkReference(node)) {
|
|
774
|
+
const definition = definitions[node.identifier];
|
|
775
|
+
if (definition) {
|
|
776
|
+
return /* @__PURE__ */ jsx3(
|
|
777
|
+
"a",
|
|
778
|
+
{
|
|
779
|
+
href: definition.url,
|
|
780
|
+
title: definition.title || void 0,
|
|
781
|
+
target: "_blank",
|
|
782
|
+
rel: "noopener noreferrer",
|
|
783
|
+
children: /* @__PURE__ */ jsx3(IncremarkInline, { nodes: node.children })
|
|
784
|
+
},
|
|
785
|
+
i
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
return /* @__PURE__ */ jsxs("span", { className: "incremark-link-ref-missing", children: [
|
|
789
|
+
"[",
|
|
790
|
+
node.children.map((c) => c.value).join(""),
|
|
791
|
+
"][",
|
|
792
|
+
node.identifier || node.label,
|
|
793
|
+
"]"
|
|
794
|
+
] }, i);
|
|
795
|
+
}
|
|
796
|
+
if (node.type === "footnoteReference") {
|
|
797
|
+
const footnoteRef = node;
|
|
798
|
+
const hasDefinition = footnoteDefinitions[footnoteRef.identifier];
|
|
799
|
+
return /* @__PURE__ */ jsx3("sup", { className: "incremark-footnote-ref", children: /* @__PURE__ */ jsx3("a", { href: `#fn-${footnoteRef.identifier}`, id: `fnref-${footnoteRef.identifier}`, children: hasDefinition ? `[${footnoteRef.identifier}]` : `[^${footnoteRef.identifier}]` }) }, i);
|
|
800
|
+
}
|
|
801
|
+
if (node.type === "break") {
|
|
802
|
+
return /* @__PURE__ */ jsx3("br", {}, i);
|
|
803
|
+
}
|
|
804
|
+
if (node.type === "delete") {
|
|
805
|
+
return /* @__PURE__ */ jsx3("del", { children: /* @__PURE__ */ jsx3(IncremarkInline, { nodes: node.children }) }, i);
|
|
806
|
+
}
|
|
807
|
+
return /* @__PURE__ */ jsx3("span", { children: node.value || "" }, i);
|
|
808
|
+
}) });
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// src/components/IncremarkHeading.tsx
|
|
812
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
813
|
+
var IncremarkHeading = ({ node }) => {
|
|
411
814
|
const Tag = `h${node.depth}`;
|
|
412
|
-
return /* @__PURE__ */
|
|
815
|
+
return /* @__PURE__ */ jsx4(Tag, { className: `incremark-heading h${node.depth}`, children: /* @__PURE__ */ jsx4(IncremarkInline, { nodes: node.children }) });
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
// src/components/IncremarkParagraph.tsx
|
|
819
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
820
|
+
var IncremarkParagraph = ({ node }) => {
|
|
821
|
+
return /* @__PURE__ */ jsx5("p", { className: "incremark-paragraph", children: /* @__PURE__ */ jsx5(IncremarkInline, { nodes: node.children }) });
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
// src/components/IncremarkCode.tsx
|
|
825
|
+
import { useState as useState5, useEffect as useEffect4, useRef as useRef5, useCallback as useCallback5 } from "react";
|
|
826
|
+
import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
827
|
+
var IncremarkCode = ({
|
|
828
|
+
node,
|
|
829
|
+
theme = "github-dark",
|
|
830
|
+
disableHighlight = false,
|
|
831
|
+
mermaidDelay = 500
|
|
832
|
+
}) => {
|
|
833
|
+
const [copied, setCopied] = useState5(false);
|
|
834
|
+
const [highlightedHtml, setHighlightedHtml] = useState5("");
|
|
835
|
+
const [isHighlighting, setIsHighlighting] = useState5(false);
|
|
836
|
+
const [mermaidSvg, setMermaidSvg] = useState5("");
|
|
837
|
+
const [mermaidLoading, setMermaidLoading] = useState5(false);
|
|
838
|
+
const [mermaidViewMode, setMermaidViewMode] = useState5("preview");
|
|
839
|
+
const mermaidRef = useRef5(null);
|
|
840
|
+
const highlighterRef = useRef5(null);
|
|
841
|
+
const mermaidTimerRef = useRef5(null);
|
|
842
|
+
const loadedLanguagesRef = useRef5(/* @__PURE__ */ new Set());
|
|
843
|
+
const loadedThemesRef = useRef5(/* @__PURE__ */ new Set());
|
|
844
|
+
const language = node.lang || "text";
|
|
845
|
+
const code = node.value;
|
|
846
|
+
const isMermaid = language === "mermaid";
|
|
847
|
+
const toggleMermaidView = useCallback5(() => {
|
|
848
|
+
setMermaidViewMode((prev) => prev === "preview" ? "source" : "preview");
|
|
849
|
+
}, []);
|
|
850
|
+
const copyCode = useCallback5(async () => {
|
|
851
|
+
try {
|
|
852
|
+
await navigator.clipboard.writeText(code);
|
|
853
|
+
setCopied(true);
|
|
854
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
855
|
+
} catch {
|
|
856
|
+
}
|
|
857
|
+
}, [code]);
|
|
858
|
+
const doRenderMermaid = useCallback5(async () => {
|
|
859
|
+
if (!code) return;
|
|
860
|
+
try {
|
|
861
|
+
if (!mermaidRef.current) {
|
|
862
|
+
const mermaidModule = await import("mermaid");
|
|
863
|
+
mermaidRef.current = mermaidModule.default;
|
|
864
|
+
mermaidRef.current.initialize({
|
|
865
|
+
startOnLoad: false,
|
|
866
|
+
theme: "dark",
|
|
867
|
+
securityLevel: "loose"
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
const mermaid = mermaidRef.current;
|
|
871
|
+
const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
872
|
+
const { svg } = await mermaid.render(id, code);
|
|
873
|
+
setMermaidSvg(svg);
|
|
874
|
+
} catch {
|
|
875
|
+
setMermaidSvg("");
|
|
876
|
+
} finally {
|
|
877
|
+
setMermaidLoading(false);
|
|
878
|
+
}
|
|
879
|
+
}, [code]);
|
|
880
|
+
const scheduleRenderMermaid = useCallback5(() => {
|
|
881
|
+
if (!isMermaid || !code) return;
|
|
882
|
+
if (mermaidTimerRef.current) {
|
|
883
|
+
clearTimeout(mermaidTimerRef.current);
|
|
884
|
+
}
|
|
885
|
+
setMermaidLoading(true);
|
|
886
|
+
mermaidTimerRef.current = setTimeout(() => {
|
|
887
|
+
doRenderMermaid();
|
|
888
|
+
}, mermaidDelay);
|
|
889
|
+
}, [isMermaid, code, mermaidDelay, doRenderMermaid]);
|
|
890
|
+
const highlight = useCallback5(async () => {
|
|
891
|
+
if (isMermaid) {
|
|
892
|
+
scheduleRenderMermaid();
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (!code || disableHighlight) {
|
|
896
|
+
setHighlightedHtml("");
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
setIsHighlighting(true);
|
|
900
|
+
try {
|
|
901
|
+
if (!highlighterRef.current) {
|
|
902
|
+
const { createHighlighter } = await import("shiki");
|
|
903
|
+
highlighterRef.current = await createHighlighter({
|
|
904
|
+
themes: [theme],
|
|
905
|
+
langs: []
|
|
906
|
+
});
|
|
907
|
+
loadedThemesRef.current.add(theme);
|
|
908
|
+
}
|
|
909
|
+
const highlighter = highlighterRef.current;
|
|
910
|
+
const lang = language;
|
|
911
|
+
if (!loadedLanguagesRef.current.has(lang) && lang !== "text") {
|
|
912
|
+
try {
|
|
913
|
+
await highlighter.loadLanguage(lang);
|
|
914
|
+
loadedLanguagesRef.current.add(lang);
|
|
915
|
+
} catch {
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (!loadedThemesRef.current.has(theme)) {
|
|
919
|
+
try {
|
|
920
|
+
await highlighter.loadTheme(theme);
|
|
921
|
+
loadedThemesRef.current.add(theme);
|
|
922
|
+
} catch {
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const html = highlighter.codeToHtml(code, {
|
|
926
|
+
lang: loadedLanguagesRef.current.has(lang) ? lang : "text",
|
|
927
|
+
theme: loadedThemesRef.current.has(theme) ? theme : "github-dark"
|
|
928
|
+
});
|
|
929
|
+
setHighlightedHtml(html);
|
|
930
|
+
} catch {
|
|
931
|
+
setHighlightedHtml("");
|
|
932
|
+
} finally {
|
|
933
|
+
setIsHighlighting(false);
|
|
934
|
+
}
|
|
935
|
+
}, [code, language, theme, disableHighlight, isMermaid, scheduleRenderMermaid]);
|
|
936
|
+
useEffect4(() => {
|
|
937
|
+
highlight();
|
|
938
|
+
}, [highlight]);
|
|
939
|
+
useEffect4(() => {
|
|
940
|
+
return () => {
|
|
941
|
+
if (mermaidTimerRef.current) {
|
|
942
|
+
clearTimeout(mermaidTimerRef.current);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
}, []);
|
|
946
|
+
if (isMermaid) {
|
|
947
|
+
return /* @__PURE__ */ jsxs2("div", { className: "incremark-mermaid", children: [
|
|
948
|
+
/* @__PURE__ */ jsxs2("div", { className: "mermaid-header", children: [
|
|
949
|
+
/* @__PURE__ */ jsx6("span", { className: "language", children: "MERMAID" }),
|
|
950
|
+
/* @__PURE__ */ jsxs2("div", { className: "mermaid-actions", children: [
|
|
951
|
+
/* @__PURE__ */ jsx6(
|
|
952
|
+
"button",
|
|
953
|
+
{
|
|
954
|
+
className: "code-btn",
|
|
955
|
+
onClick: toggleMermaidView,
|
|
956
|
+
type: "button",
|
|
957
|
+
disabled: !mermaidSvg,
|
|
958
|
+
children: mermaidViewMode === "preview" ? "\u6E90\u7801" : "\u9884\u89C8"
|
|
959
|
+
}
|
|
960
|
+
),
|
|
961
|
+
/* @__PURE__ */ jsx6("button", { className: "code-btn", onClick: copyCode, type: "button", children: copied ? "\u2713 \u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
962
|
+
] })
|
|
963
|
+
] }),
|
|
964
|
+
/* @__PURE__ */ jsx6("div", { className: "mermaid-content", children: mermaidLoading && !mermaidSvg ? /* @__PURE__ */ jsx6("div", { className: "mermaid-loading", children: /* @__PURE__ */ jsx6("pre", { className: "mermaid-source-code", children: code }) }) : mermaidViewMode === "source" ? /* @__PURE__ */ jsx6("pre", { className: "mermaid-source-code", children: code }) : mermaidSvg ? /* @__PURE__ */ jsx6("div", { className: "mermaid-svg", dangerouslySetInnerHTML: { __html: mermaidSvg } }) : /* @__PURE__ */ jsx6("pre", { className: "mermaid-source-code", children: code }) })
|
|
965
|
+
] });
|
|
966
|
+
}
|
|
967
|
+
return /* @__PURE__ */ jsxs2("div", { className: "incremark-code", children: [
|
|
968
|
+
/* @__PURE__ */ jsxs2("div", { className: "code-header", children: [
|
|
969
|
+
/* @__PURE__ */ jsx6("span", { className: "language", children: language }),
|
|
970
|
+
/* @__PURE__ */ jsx6("button", { className: "code-btn", onClick: copyCode, type: "button", children: copied ? "\u2713 \u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
971
|
+
] }),
|
|
972
|
+
/* @__PURE__ */ jsx6("div", { className: "code-content", children: isHighlighting && !highlightedHtml ? /* @__PURE__ */ jsx6("div", { className: "code-loading", children: /* @__PURE__ */ jsx6("pre", { children: /* @__PURE__ */ jsx6("code", { children: code }) }) }) : highlightedHtml ? /* @__PURE__ */ jsx6("div", { className: "shiki-wrapper", dangerouslySetInnerHTML: { __html: highlightedHtml } }) : /* @__PURE__ */ jsx6("pre", { className: "code-fallback", children: /* @__PURE__ */ jsx6("code", { children: code }) }) })
|
|
973
|
+
] });
|
|
413
974
|
};
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
]
|
|
419
|
-
|
|
975
|
+
|
|
976
|
+
// src/components/IncremarkList.tsx
|
|
977
|
+
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
978
|
+
function getItemContent(item) {
|
|
979
|
+
const firstChild = item.children[0];
|
|
980
|
+
if (firstChild?.type === "paragraph") {
|
|
981
|
+
return firstChild.children;
|
|
982
|
+
}
|
|
983
|
+
return [];
|
|
984
|
+
}
|
|
985
|
+
var IncremarkList = ({ node }) => {
|
|
420
986
|
const Tag = node.ordered ? "ol" : "ul";
|
|
421
|
-
|
|
987
|
+
const isTaskList = node.children?.some((item) => item.checked !== null && item.checked !== void 0);
|
|
988
|
+
return /* @__PURE__ */ jsx7(Tag, { className: `incremark-list ${isTaskList ? "task-list" : ""}`, children: node.children?.map((item, index) => {
|
|
989
|
+
const isTaskItem = item.checked !== null && item.checked !== void 0;
|
|
990
|
+
const content = getItemContent(item);
|
|
991
|
+
if (isTaskItem) {
|
|
992
|
+
return /* @__PURE__ */ jsx7("li", { className: "incremark-list-item task-item", children: /* @__PURE__ */ jsxs3("label", { className: "task-label", children: [
|
|
993
|
+
/* @__PURE__ */ jsx7(
|
|
994
|
+
"input",
|
|
995
|
+
{
|
|
996
|
+
type: "checkbox",
|
|
997
|
+
checked: item.checked || false,
|
|
998
|
+
disabled: true,
|
|
999
|
+
className: "checkbox"
|
|
1000
|
+
}
|
|
1001
|
+
),
|
|
1002
|
+
/* @__PURE__ */ jsx7("span", { className: "task-content", children: /* @__PURE__ */ jsx7(IncremarkInline, { nodes: content }) })
|
|
1003
|
+
] }) }, index);
|
|
1004
|
+
}
|
|
1005
|
+
return /* @__PURE__ */ jsx7("li", { className: "incremark-list-item", children: /* @__PURE__ */ jsx7(IncremarkInline, { nodes: content }) }, index);
|
|
1006
|
+
}) });
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
// src/components/IncremarkBlockquote.tsx
|
|
1010
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1011
|
+
var IncremarkBlockquote = ({ node }) => {
|
|
1012
|
+
return /* @__PURE__ */ jsx8("blockquote", { className: "incremark-blockquote", children: node.children.map((child, index) => {
|
|
1013
|
+
if (child.type === "paragraph") {
|
|
1014
|
+
return /* @__PURE__ */ jsx8(IncremarkParagraph, { node: child }, index);
|
|
1015
|
+
}
|
|
1016
|
+
return /* @__PURE__ */ jsx8("div", { className: "unknown-child", children: child.type }, index);
|
|
1017
|
+
}) });
|
|
422
1018
|
};
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
var
|
|
1019
|
+
|
|
1020
|
+
// src/components/IncremarkTable.tsx
|
|
1021
|
+
import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1022
|
+
function getCellContent(cell) {
|
|
1023
|
+
return cell.children;
|
|
1024
|
+
}
|
|
1025
|
+
var IncremarkTable = ({ node }) => {
|
|
1026
|
+
return /* @__PURE__ */ jsx9("div", { className: "incremark-table-wrapper", children: /* @__PURE__ */ jsxs4("table", { className: "incremark-table", children: [
|
|
1027
|
+
/* @__PURE__ */ jsx9("thead", { children: node.children?.[0] && /* @__PURE__ */ jsx9("tr", { children: node.children[0].children.map((cell, cellIndex) => /* @__PURE__ */ jsx9(
|
|
1028
|
+
"th",
|
|
1029
|
+
{
|
|
1030
|
+
style: { textAlign: node.align?.[cellIndex] || "left" },
|
|
1031
|
+
children: /* @__PURE__ */ jsx9(IncremarkInline, { nodes: getCellContent(cell) })
|
|
1032
|
+
},
|
|
1033
|
+
cellIndex
|
|
1034
|
+
)) }) }),
|
|
1035
|
+
/* @__PURE__ */ jsx9("tbody", { children: node.children?.slice(1).map((row, rowIndex) => /* @__PURE__ */ jsx9("tr", { children: row.children.map((cell, cellIndex) => /* @__PURE__ */ jsx9(
|
|
1036
|
+
"td",
|
|
1037
|
+
{
|
|
1038
|
+
style: { textAlign: node.align?.[cellIndex] || "left" },
|
|
1039
|
+
children: /* @__PURE__ */ jsx9(IncremarkInline, { nodes: getCellContent(cell) })
|
|
1040
|
+
},
|
|
1041
|
+
cellIndex
|
|
1042
|
+
)) }, rowIndex)) })
|
|
1043
|
+
] }) });
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// src/components/IncremarkThematicBreak.tsx
|
|
1047
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1048
|
+
var IncremarkThematicBreak = () => {
|
|
1049
|
+
return /* @__PURE__ */ jsx10("hr", { className: "incremark-hr" });
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
// src/components/IncremarkMath.tsx
|
|
1053
|
+
import { useState as useState6, useEffect as useEffect5, useRef as useRef6, useCallback as useCallback6 } from "react";
|
|
1054
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1055
|
+
var IncremarkMath = ({
|
|
1056
|
+
node,
|
|
1057
|
+
renderDelay = 300
|
|
1058
|
+
}) => {
|
|
1059
|
+
const [renderedHtml, setRenderedHtml] = useState6("");
|
|
1060
|
+
const [isLoading, setIsLoading] = useState6(false);
|
|
1061
|
+
const katexRef = useRef6(null);
|
|
1062
|
+
const renderTimerRef = useRef6(null);
|
|
1063
|
+
const isInline = node.type === "inlineMath";
|
|
1064
|
+
const formula = node.value;
|
|
1065
|
+
const doRender = useCallback6(async () => {
|
|
1066
|
+
if (!formula) return;
|
|
1067
|
+
try {
|
|
1068
|
+
if (!katexRef.current) {
|
|
1069
|
+
const katexModule = await import("katex");
|
|
1070
|
+
katexRef.current = katexModule.default;
|
|
1071
|
+
}
|
|
1072
|
+
const katex = katexRef.current;
|
|
1073
|
+
const html = katex.renderToString(formula, {
|
|
1074
|
+
displayMode: !isInline,
|
|
1075
|
+
throwOnError: false,
|
|
1076
|
+
strict: false
|
|
1077
|
+
});
|
|
1078
|
+
setRenderedHtml(html);
|
|
1079
|
+
} catch {
|
|
1080
|
+
setRenderedHtml("");
|
|
1081
|
+
} finally {
|
|
1082
|
+
setIsLoading(false);
|
|
1083
|
+
}
|
|
1084
|
+
}, [formula, isInline]);
|
|
1085
|
+
const scheduleRender = useCallback6(() => {
|
|
1086
|
+
if (!formula) {
|
|
1087
|
+
setRenderedHtml("");
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (renderTimerRef.current) {
|
|
1091
|
+
clearTimeout(renderTimerRef.current);
|
|
1092
|
+
}
|
|
1093
|
+
setIsLoading(true);
|
|
1094
|
+
renderTimerRef.current = setTimeout(() => {
|
|
1095
|
+
doRender();
|
|
1096
|
+
}, renderDelay);
|
|
1097
|
+
}, [formula, renderDelay, doRender]);
|
|
1098
|
+
useEffect5(() => {
|
|
1099
|
+
scheduleRender();
|
|
1100
|
+
}, [scheduleRender]);
|
|
1101
|
+
useEffect5(() => {
|
|
1102
|
+
return () => {
|
|
1103
|
+
if (renderTimerRef.current) {
|
|
1104
|
+
clearTimeout(renderTimerRef.current);
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
}, []);
|
|
1108
|
+
if (isInline) {
|
|
1109
|
+
return /* @__PURE__ */ jsx11("span", { className: "incremark-math-inline", children: renderedHtml && !isLoading ? /* @__PURE__ */ jsx11("span", { dangerouslySetInnerHTML: { __html: renderedHtml } }) : /* @__PURE__ */ jsx11("code", { className: "math-source", children: formula }) });
|
|
1110
|
+
}
|
|
1111
|
+
return /* @__PURE__ */ jsx11("div", { className: "incremark-math-block", children: renderedHtml && !isLoading ? /* @__PURE__ */ jsx11("div", { className: "math-rendered", dangerouslySetInnerHTML: { __html: renderedHtml } }) : /* @__PURE__ */ jsx11("pre", { className: "math-source-block", children: /* @__PURE__ */ jsx11("code", { children: formula }) }) });
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
// src/components/IncremarkDefault.tsx
|
|
1115
|
+
import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1116
|
+
var IncremarkDefault = ({ node }) => {
|
|
1117
|
+
return /* @__PURE__ */ jsxs5("div", { className: "incremark-default", children: [
|
|
1118
|
+
/* @__PURE__ */ jsx12("span", { className: "type-badge", children: node.type }),
|
|
1119
|
+
/* @__PURE__ */ jsx12("pre", { children: JSON.stringify(node, null, 2) })
|
|
1120
|
+
] });
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
// src/components/IncremarkRenderer.tsx
|
|
1124
|
+
import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1125
|
+
var DefaultHeading = ({ node }) => {
|
|
1126
|
+
return /* @__PURE__ */ jsx13(IncremarkHeading, { node });
|
|
1127
|
+
};
|
|
1128
|
+
var DefaultParagraph = ({ node }) => /* @__PURE__ */ jsx13(IncremarkParagraph, { node });
|
|
1129
|
+
var DefaultCode = ({ node }) => /* @__PURE__ */ jsx13(IncremarkCode, { node });
|
|
1130
|
+
var DefaultList = ({ node }) => /* @__PURE__ */ jsx13(IncremarkList, { node });
|
|
1131
|
+
var DefaultBlockquote = ({ node }) => /* @__PURE__ */ jsx13(IncremarkBlockquote, { node });
|
|
1132
|
+
var DefaultTable = ({ node }) => /* @__PURE__ */ jsx13(IncremarkTable, { node });
|
|
1133
|
+
var DefaultThematicBreak = () => /* @__PURE__ */ jsx13(IncremarkThematicBreak, {});
|
|
1134
|
+
var DefaultMath = ({ node }) => /* @__PURE__ */ jsx13(IncremarkMath, { node });
|
|
1135
|
+
var DefaultHtmlElement = ({ node }) => /* @__PURE__ */ jsx13(IncremarkHtmlElement, { node });
|
|
1136
|
+
var DefaultDefault = ({ node }) => /* @__PURE__ */ jsx13(IncremarkDefault, { node });
|
|
430
1137
|
var defaultComponents = {
|
|
431
1138
|
heading: DefaultHeading,
|
|
432
1139
|
paragraph: DefaultParagraph,
|
|
@@ -434,43 +1141,146 @@ var defaultComponents = {
|
|
|
434
1141
|
list: DefaultList,
|
|
435
1142
|
blockquote: DefaultBlockquote,
|
|
436
1143
|
table: DefaultTable,
|
|
437
|
-
thematicBreak: DefaultThematicBreak
|
|
1144
|
+
thematicBreak: DefaultThematicBreak,
|
|
1145
|
+
math: DefaultMath,
|
|
1146
|
+
inlineMath: DefaultMath,
|
|
1147
|
+
htmlElement: DefaultHtmlElement,
|
|
1148
|
+
default: DefaultDefault
|
|
438
1149
|
};
|
|
439
1150
|
var IncremarkRenderer = ({ node, components = {} }) => {
|
|
1151
|
+
if (node.type === "footnoteDefinition") {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
if (node.type === "html") {
|
|
1155
|
+
return /* @__PURE__ */ jsx13("pre", { className: "incremark-html-code", children: /* @__PURE__ */ jsx13("code", { children: node.value }) });
|
|
1156
|
+
}
|
|
440
1157
|
const mergedComponents = { ...defaultComponents, ...components };
|
|
441
1158
|
const Component = mergedComponents[node.type] || DefaultDefault;
|
|
442
|
-
return /* @__PURE__ */
|
|
1159
|
+
return /* @__PURE__ */ jsx13(Component, { node });
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
// src/components/IncremarkFootnotes.tsx
|
|
1163
|
+
import React7 from "react";
|
|
1164
|
+
import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1165
|
+
var IncremarkFootnotes = () => {
|
|
1166
|
+
const { footnoteDefinitions, footnoteReferenceOrder } = useDefinitions();
|
|
1167
|
+
const orderedFootnotes = React7.useMemo(() => {
|
|
1168
|
+
return footnoteReferenceOrder.map((identifier) => ({
|
|
1169
|
+
identifier,
|
|
1170
|
+
definition: footnoteDefinitions[identifier]
|
|
1171
|
+
})).filter((item) => item.definition !== void 0);
|
|
1172
|
+
}, [footnoteReferenceOrder, footnoteDefinitions]);
|
|
1173
|
+
if (orderedFootnotes.length === 0) {
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
return /* @__PURE__ */ jsxs7("section", { className: "incremark-footnotes", children: [
|
|
1177
|
+
/* @__PURE__ */ jsx14("hr", { className: "incremark-footnotes-divider" }),
|
|
1178
|
+
/* @__PURE__ */ jsx14("ol", { className: "incremark-footnotes-list", children: orderedFootnotes.map((item, index) => /* @__PURE__ */ jsxs7(
|
|
1179
|
+
"li",
|
|
1180
|
+
{
|
|
1181
|
+
id: `fn-${item.identifier}`,
|
|
1182
|
+
className: "incremark-footnote-item",
|
|
1183
|
+
children: [
|
|
1184
|
+
/* @__PURE__ */ jsxs7("div", { className: "incremark-footnote-content", children: [
|
|
1185
|
+
/* @__PURE__ */ jsxs7("span", { className: "incremark-footnote-number", children: [
|
|
1186
|
+
index + 1,
|
|
1187
|
+
"."
|
|
1188
|
+
] }),
|
|
1189
|
+
/* @__PURE__ */ jsx14("div", { className: "incremark-footnote-body", children: item.definition.children.map((child, childIndex) => /* @__PURE__ */ jsx14(IncremarkRenderer, { node: child }, childIndex)) })
|
|
1190
|
+
] }),
|
|
1191
|
+
/* @__PURE__ */ jsx14(
|
|
1192
|
+
"a",
|
|
1193
|
+
{
|
|
1194
|
+
href: `#fnref-${item.identifier}`,
|
|
1195
|
+
className: "incremark-footnote-backref",
|
|
1196
|
+
"aria-label": "\u8FD4\u56DE\u5F15\u7528\u4F4D\u7F6E",
|
|
1197
|
+
children: "\u21A9"
|
|
1198
|
+
}
|
|
1199
|
+
)
|
|
1200
|
+
]
|
|
1201
|
+
},
|
|
1202
|
+
item.identifier
|
|
1203
|
+
)) })
|
|
1204
|
+
] });
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
// src/components/IncremarkContainerProvider.tsx
|
|
1208
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1209
|
+
var IncremarkContainerProvider = ({ children, definitions }) => {
|
|
1210
|
+
return /* @__PURE__ */ jsx15(DefinitionsContext.Provider, { value: definitions, children });
|
|
443
1211
|
};
|
|
444
1212
|
|
|
445
1213
|
// src/components/Incremark.tsx
|
|
446
|
-
import { jsx as
|
|
447
|
-
var Incremark = ({
|
|
1214
|
+
import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1215
|
+
var Incremark = (props) => {
|
|
1216
|
+
const {
|
|
1217
|
+
blocks: propsBlocks,
|
|
1218
|
+
components,
|
|
1219
|
+
showBlockStatus = true,
|
|
1220
|
+
className = "",
|
|
1221
|
+
incremark
|
|
1222
|
+
} = props;
|
|
1223
|
+
if (incremark) {
|
|
1224
|
+
const { blocks: blocks2, isFinalized: isFinalized2, _definitionsContextValue } = incremark;
|
|
1225
|
+
return /* @__PURE__ */ jsx16(IncremarkContainerProvider, { definitions: _definitionsContextValue, children: /* @__PURE__ */ jsx16(
|
|
1226
|
+
IncremarkInternal,
|
|
1227
|
+
{
|
|
1228
|
+
blocks: blocks2,
|
|
1229
|
+
components,
|
|
1230
|
+
showBlockStatus,
|
|
1231
|
+
className,
|
|
1232
|
+
isFinalized: isFinalized2
|
|
1233
|
+
}
|
|
1234
|
+
) });
|
|
1235
|
+
}
|
|
1236
|
+
const blocks = propsBlocks || [];
|
|
1237
|
+
const isFinalized = blocks.length > 0 && blocks.every((b) => b.status === "completed");
|
|
1238
|
+
return /* @__PURE__ */ jsx16(
|
|
1239
|
+
IncremarkInternal,
|
|
1240
|
+
{
|
|
1241
|
+
blocks,
|
|
1242
|
+
components,
|
|
1243
|
+
showBlockStatus,
|
|
1244
|
+
className,
|
|
1245
|
+
isFinalized
|
|
1246
|
+
}
|
|
1247
|
+
);
|
|
1248
|
+
};
|
|
1249
|
+
var IncremarkInternal = ({
|
|
448
1250
|
blocks,
|
|
449
1251
|
components,
|
|
450
|
-
showBlockStatus
|
|
451
|
-
className
|
|
1252
|
+
showBlockStatus,
|
|
1253
|
+
className,
|
|
1254
|
+
isFinalized
|
|
452
1255
|
}) => {
|
|
453
|
-
return /* @__PURE__ */
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
block.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
1256
|
+
return /* @__PURE__ */ jsxs8("div", { className: `incremark ${className}`, children: [
|
|
1257
|
+
blocks.map((block) => {
|
|
1258
|
+
if (block.node.type === "definition" || block.node.type === "footnoteDefinition") {
|
|
1259
|
+
return null;
|
|
1260
|
+
}
|
|
1261
|
+
const isPending = block.status === "pending";
|
|
1262
|
+
const classes = [
|
|
1263
|
+
"incremark-block",
|
|
1264
|
+
isPending ? "incremark-pending" : "incremark-completed",
|
|
1265
|
+
showBlockStatus && "incremark-show-status",
|
|
1266
|
+
block.isLastPending && "incremark-last-pending"
|
|
1267
|
+
].filter(Boolean).join(" ");
|
|
1268
|
+
return /* @__PURE__ */ jsx16("div", { className: classes, children: /* @__PURE__ */ jsx16(IncremarkRenderer, { node: block.node, components }) }, block.stableId);
|
|
1269
|
+
}),
|
|
1270
|
+
isFinalized && /* @__PURE__ */ jsx16(IncremarkFootnotes, {})
|
|
1271
|
+
] });
|
|
462
1272
|
};
|
|
463
1273
|
|
|
464
1274
|
// src/components/AutoScrollContainer.tsx
|
|
465
1275
|
import {
|
|
466
|
-
useRef as
|
|
467
|
-
useEffect as
|
|
468
|
-
useCallback as
|
|
469
|
-
useState as
|
|
1276
|
+
useRef as useRef7,
|
|
1277
|
+
useEffect as useEffect6,
|
|
1278
|
+
useCallback as useCallback7,
|
|
1279
|
+
useState as useState7,
|
|
470
1280
|
forwardRef,
|
|
471
1281
|
useImperativeHandle
|
|
472
1282
|
} from "react";
|
|
473
|
-
import { jsx as
|
|
1283
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
474
1284
|
var AutoScrollContainer = forwardRef(
|
|
475
1285
|
({
|
|
476
1286
|
children,
|
|
@@ -480,17 +1290,17 @@ var AutoScrollContainer = forwardRef(
|
|
|
480
1290
|
style,
|
|
481
1291
|
className
|
|
482
1292
|
}, ref) => {
|
|
483
|
-
const containerRef =
|
|
484
|
-
const [isUserScrolledUp, setIsUserScrolledUp] =
|
|
485
|
-
const lastScrollTopRef =
|
|
486
|
-
const lastScrollHeightRef =
|
|
487
|
-
const isNearBottom =
|
|
1293
|
+
const containerRef = useRef7(null);
|
|
1294
|
+
const [isUserScrolledUp, setIsUserScrolledUp] = useState7(false);
|
|
1295
|
+
const lastScrollTopRef = useRef7(0);
|
|
1296
|
+
const lastScrollHeightRef = useRef7(0);
|
|
1297
|
+
const isNearBottom = useCallback7(() => {
|
|
488
1298
|
const container = containerRef.current;
|
|
489
1299
|
if (!container) return true;
|
|
490
1300
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
491
1301
|
return scrollHeight - scrollTop - clientHeight <= threshold;
|
|
492
1302
|
}, [threshold]);
|
|
493
|
-
const scrollToBottom =
|
|
1303
|
+
const scrollToBottom = useCallback7(
|
|
494
1304
|
(force = false) => {
|
|
495
1305
|
const container = containerRef.current;
|
|
496
1306
|
if (!container) return;
|
|
@@ -502,12 +1312,12 @@ var AutoScrollContainer = forwardRef(
|
|
|
502
1312
|
},
|
|
503
1313
|
[isUserScrolledUp, behavior]
|
|
504
1314
|
);
|
|
505
|
-
const hasScrollbar =
|
|
1315
|
+
const hasScrollbar = useCallback7(() => {
|
|
506
1316
|
const container = containerRef.current;
|
|
507
1317
|
if (!container) return false;
|
|
508
1318
|
return container.scrollHeight > container.clientHeight;
|
|
509
1319
|
}, []);
|
|
510
|
-
const handleScroll =
|
|
1320
|
+
const handleScroll = useCallback7(() => {
|
|
511
1321
|
const container = containerRef.current;
|
|
512
1322
|
if (!container) return;
|
|
513
1323
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
@@ -529,14 +1339,14 @@ var AutoScrollContainer = forwardRef(
|
|
|
529
1339
|
lastScrollTopRef.current = scrollTop;
|
|
530
1340
|
lastScrollHeightRef.current = scrollHeight;
|
|
531
1341
|
}, [isNearBottom]);
|
|
532
|
-
|
|
1342
|
+
useEffect6(() => {
|
|
533
1343
|
const container = containerRef.current;
|
|
534
1344
|
if (container) {
|
|
535
1345
|
lastScrollTopRef.current = container.scrollTop;
|
|
536
1346
|
lastScrollHeightRef.current = container.scrollHeight;
|
|
537
1347
|
}
|
|
538
1348
|
}, []);
|
|
539
|
-
|
|
1349
|
+
useEffect6(() => {
|
|
540
1350
|
const container = containerRef.current;
|
|
541
1351
|
if (!container || !enabled) return;
|
|
542
1352
|
const observer = new MutationObserver(() => {
|
|
@@ -568,7 +1378,7 @@ var AutoScrollContainer = forwardRef(
|
|
|
568
1378
|
}),
|
|
569
1379
|
[scrollToBottom, isUserScrolledUp]
|
|
570
1380
|
);
|
|
571
|
-
return /* @__PURE__ */
|
|
1381
|
+
return /* @__PURE__ */ jsx17(
|
|
572
1382
|
"div",
|
|
573
1383
|
{
|
|
574
1384
|
ref: containerRef,
|
|
@@ -586,6 +1396,24 @@ var AutoScrollContainer = forwardRef(
|
|
|
586
1396
|
);
|
|
587
1397
|
AutoScrollContainer.displayName = "AutoScrollContainer";
|
|
588
1398
|
|
|
1399
|
+
// src/ThemeProvider.tsx
|
|
1400
|
+
import { useEffect as useEffect7, useRef as useRef8 } from "react";
|
|
1401
|
+
import { applyTheme } from "@incremark/theme";
|
|
1402
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1403
|
+
var ThemeProvider = ({
|
|
1404
|
+
theme,
|
|
1405
|
+
children,
|
|
1406
|
+
className = ""
|
|
1407
|
+
}) => {
|
|
1408
|
+
const containerRef = useRef8(null);
|
|
1409
|
+
useEffect7(() => {
|
|
1410
|
+
if (containerRef.current) {
|
|
1411
|
+
applyTheme(containerRef.current, theme);
|
|
1412
|
+
}
|
|
1413
|
+
}, [theme]);
|
|
1414
|
+
return /* @__PURE__ */ jsx18("div", { ref: containerRef, className, children });
|
|
1415
|
+
};
|
|
1416
|
+
|
|
589
1417
|
// src/index.ts
|
|
590
1418
|
import {
|
|
591
1419
|
BlockTransformer as BlockTransformer2,
|
|
@@ -602,24 +1430,84 @@ import {
|
|
|
602
1430
|
allPlugins,
|
|
603
1431
|
createPlugin
|
|
604
1432
|
} from "@incremark/core";
|
|
1433
|
+
import {
|
|
1434
|
+
defaultTheme,
|
|
1435
|
+
darkTheme,
|
|
1436
|
+
generateCSSVars,
|
|
1437
|
+
mergeTheme,
|
|
1438
|
+
applyTheme as applyTheme2
|
|
1439
|
+
} from "@incremark/theme";
|
|
605
1440
|
export {
|
|
606
1441
|
AutoScrollContainer,
|
|
607
1442
|
BlockTransformer2 as BlockTransformer,
|
|
1443
|
+
DefinitionsProvider,
|
|
608
1444
|
Incremark,
|
|
1445
|
+
IncremarkContainerProvider,
|
|
1446
|
+
IncremarkFootnotes,
|
|
1447
|
+
IncremarkHtmlElement,
|
|
609
1448
|
IncremarkRenderer,
|
|
1449
|
+
ThemeProvider,
|
|
610
1450
|
allPlugins,
|
|
1451
|
+
applyTheme2 as applyTheme,
|
|
611
1452
|
cloneNode,
|
|
612
1453
|
codeBlockPlugin,
|
|
613
1454
|
countChars,
|
|
614
1455
|
createBlockTransformer3 as createBlockTransformer,
|
|
615
1456
|
createPlugin,
|
|
1457
|
+
darkTheme,
|
|
616
1458
|
defaultPlugins2 as defaultPlugins,
|
|
1459
|
+
defaultTheme,
|
|
1460
|
+
generateCSSVars,
|
|
617
1461
|
imagePlugin,
|
|
618
1462
|
mathPlugin,
|
|
1463
|
+
mergeTheme,
|
|
619
1464
|
mermaidPlugin,
|
|
620
1465
|
sliceAst,
|
|
621
1466
|
thematicBreakPlugin,
|
|
622
1467
|
useBlockTransformer,
|
|
1468
|
+
useDefinitions,
|
|
623
1469
|
useDevTools,
|
|
624
1470
|
useIncremark
|
|
625
1471
|
};
|
|
1472
|
+
/**
|
|
1473
|
+
* @file Definitions Context - 管理 Markdown 引用定义
|
|
1474
|
+
*
|
|
1475
|
+
* @description
|
|
1476
|
+
* 提供 definitions 和 footnoteDefinitions 的 Context,
|
|
1477
|
+
* 用于支持引用式图片/链接的解析和渲染。
|
|
1478
|
+
*
|
|
1479
|
+
* @author Incremark Team
|
|
1480
|
+
* @license MIT
|
|
1481
|
+
*/
|
|
1482
|
+
/**
|
|
1483
|
+
* @file Cursor Utils - 光标工具函数
|
|
1484
|
+
*
|
|
1485
|
+
* @description
|
|
1486
|
+
* 用于在 AST 节点末尾添加光标的工具函数。
|
|
1487
|
+
*
|
|
1488
|
+
* @author Incremark Team
|
|
1489
|
+
* @license MIT
|
|
1490
|
+
*/
|
|
1491
|
+
/**
|
|
1492
|
+
* @file useTypewriter Hook - 打字机效果管理
|
|
1493
|
+
*
|
|
1494
|
+
* @description
|
|
1495
|
+
* 管理打字机效果的状态和控制逻辑,从 useIncremark 中拆分出来以简化代码。
|
|
1496
|
+
*
|
|
1497
|
+
* @author Incremark Team
|
|
1498
|
+
* @license MIT
|
|
1499
|
+
*/
|
|
1500
|
+
/**
|
|
1501
|
+
* @file IncremarkContainerProvider - Incremark 容器级 Provider
|
|
1502
|
+
*
|
|
1503
|
+
* @description
|
|
1504
|
+
* 用于在 Incremark 的渲染树根部统一注入各种 Context / 全局能力。
|
|
1505
|
+
* 目前主要用于注入 DefinitionsContext(引用定义、脚注定义等)。
|
|
1506
|
+
*
|
|
1507
|
+
* 设计目标:
|
|
1508
|
+
* - **对用户隐藏实现细节**:用户只需 `<Incremark incremark={incremark} />`
|
|
1509
|
+
* - **为未来扩展留空间**:后续可在这里统一添加更多 Provider(主题、滚动、性能开关等)
|
|
1510
|
+
*
|
|
1511
|
+
* @author Incremark Team
|
|
1512
|
+
* @license MIT
|
|
1513
|
+
*/
|