@incremark/react 0.1.0 → 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.js CHANGED
@@ -1,44 +1,167 @@
1
1
  // src/hooks/useIncremark.ts
2
- import { useState, useCallback, useMemo, useRef, useEffect } from "react";
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
- function useIncremark(options = {}) {
9
- const parserRef = useRef(null);
10
- const transformerRef = useRef(null);
11
- const hasTypewriterConfig = !!options.typewriter;
12
- const cursorRef = useRef(options.typewriter?.cursor ?? "|");
13
- if (!parserRef.current) {
14
- parserRef.current = createIncremarkParser(options);
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 = options.typewriter;
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
- setForceUpdateCount((c) => c + 1);
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 displayBlocks = transformer.getDisplayBlocks();
54
- const currentDisplaying = displayBlocks.find((b) => !b.isDisplayComplete);
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
- return {
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
- }, [completedBlocks, pendingBlocks, typewriterEnabled, typewriterEffect, addCursorToNode, forceUpdateCount]);
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
- setPendingBlocks(update.pending);
139
- return update;
140
- },
141
- [parser]
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 = useCallback(() => {
234
+ const pause = useCallback2(() => {
180
235
  transformer?.pause();
181
236
  setIsTypewriterPaused(true);
182
237
  }, [transformer]);
183
- const resume = useCallback(() => {
238
+ const resume = useCallback2(() => {
184
239
  transformer?.resume();
185
240
  setIsTypewriterPaused(false);
186
241
  }, [transformer]);
187
- const setTypewriterOptions = useCallback(
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 typewriter = useMemo(
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 useRef2 } from "react";
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 = useRef2(null);
263
- const optionsRef = useRef2(options);
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 useState2, useCallback as useCallback2, useRef as useRef3, useEffect as useEffect3 } from "react";
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] = useState2([]);
297
- const [isProcessing, setIsProcessing] = useState2(false);
298
- const [isPaused, setIsPaused] = useState2(false);
299
- const [effect, setEffect] = useState2(options.effect ?? "none");
300
- const transformerRef = useRef3(null);
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 = useCallback2(() => {
488
+ const skip = useCallback4(() => {
328
489
  transformer.skip();
329
490
  }, [transformer]);
330
- const reset = useCallback2(() => {
491
+ const reset = useCallback4(() => {
331
492
  transformer.reset();
332
493
  }, [transformer]);
333
- const pause = useCallback2(() => {
494
+ const pause = useCallback4(() => {
334
495
  transformer.pause();
335
496
  setIsPaused(true);
336
497
  }, [transformer]);
337
- const resume = useCallback2(() => {
498
+ const resume = useCallback4(() => {
338
499
  transformer.resume();
339
500
  setIsPaused(false);
340
501
  }, [transformer]);
341
- const setOptionsCallback = useCallback2(
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 React from "react";
366
- import { jsx, jsxs } from "react/jsx-runtime";
367
- function getStableText(node) {
368
- if (!node.chunks || node.chunks.length === 0) {
369
- return node.value;
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 node.value.slice(0, node.stableLength ?? 0);
616
+ return result;
372
617
  }
373
- function renderInlineChildren(children) {
374
- if (!children) return null;
375
- return children.map((child, i) => {
376
- switch (child.type) {
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
- }
387
- case "strong":
388
- return /* @__PURE__ */ jsx("strong", { children: renderInlineChildren(child.children) }, i);
389
- case "emphasis":
390
- return /* @__PURE__ */ jsx("em", { children: renderInlineChildren(child.children) }, i);
391
- case "inlineCode":
392
- return /* @__PURE__ */ jsx("code", { className: "incremark-inline-code", children: child.value }, i);
393
- case "link":
394
- return /* @__PURE__ */ jsx("a", { href: child.url, target: "_blank", rel: "noopener noreferrer", children: renderInlineChildren(child.children) }, i);
395
- case "image":
396
- return /* @__PURE__ */ jsx("img", { src: child.url, alt: child.alt || "", loading: "lazy" }, i);
397
- case "break":
398
- return /* @__PURE__ */ jsx("br", {}, i);
399
- case "delete":
400
- return /* @__PURE__ */ jsx("del", { children: renderInlineChildren(child.children) }, i);
401
- case "paragraph":
402
- return /* @__PURE__ */ jsx(React.Fragment, { children: renderInlineChildren(child.children) }, i);
403
- case "html":
404
- return /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: child.value } }, i);
405
- default:
406
- return /* @__PURE__ */ jsx("span", { children: child.value || "" }, i);
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 DefaultHeading = ({ node }) => {
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__ */ jsx(Tag, { className: "incremark-heading", children: renderInlineChildren(node.children) });
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
- var DefaultParagraph = ({ node }) => /* @__PURE__ */ jsx("p", { className: "incremark-paragraph", children: renderInlineChildren(node.children) });
415
- var DefaultCode = ({ node }) => /* @__PURE__ */ jsxs("div", { className: "incremark-code", children: [
416
- /* @__PURE__ */ jsx("div", { className: "code-header", children: /* @__PURE__ */ jsx("span", { className: "language", children: node.lang || "text" }) }),
417
- /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: node.value }) })
418
- ] });
419
- var DefaultList = ({ node }) => {
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
- return /* @__PURE__ */ jsx(Tag, { className: "incremark-list", children: node.children?.map((item, i) => /* @__PURE__ */ jsx("li", { children: renderInlineChildren(item.children) }, i)) });
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
- 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)) });
424
- var DefaultTable = ({ node }) => /* @__PURE__ */ jsx("div", { className: "incremark-table-wrapper", children: /* @__PURE__ */ jsxs("table", { className: "incremark-table", children: [
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)) }) }),
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)) })
427
- ] }) });
428
- var DefaultThematicBreak = () => /* @__PURE__ */ jsx("hr", { className: "incremark-hr" });
429
- var DefaultDefault = ({ node }) => /* @__PURE__ */ jsx("div", { className: "incremark-unknown", "data-type": node.type, children: /* @__PURE__ */ jsx("pre", { children: JSON.stringify(node, null, 2) }) });
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__ */ jsx(Component, { node });
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 jsx2 } from "react/jsx-runtime";
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 = true,
451
- className = ""
1252
+ showBlockStatus,
1253
+ className,
1254
+ isFinalized
452
1255
  }) => {
453
- return /* @__PURE__ */ jsx2("div", { className: `incremark ${className}`, children: blocks.map((block) => {
454
- const isPending = block.status === "pending";
455
- const classes = [
456
- "incremark-block",
457
- isPending ? "incremark-pending" : "incremark-completed",
458
- block.isLastPending ? "incremark-last-pending" : ""
459
- ].filter(Boolean).join(" ");
460
- return /* @__PURE__ */ jsx2("div", { className: classes, children: /* @__PURE__ */ jsx2(IncremarkRenderer, { node: block.node, components }) }, block.stableId);
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 useRef4,
467
- useEffect as useEffect4,
468
- useCallback as useCallback3,
469
- useState as useState3,
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 jsx3 } from "react/jsx-runtime";
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 = useRef4(null);
484
- const [isUserScrolledUp, setIsUserScrolledUp] = useState3(false);
485
- const lastScrollTopRef = useRef4(0);
486
- const lastScrollHeightRef = useRef4(0);
487
- const isNearBottom = useCallback3(() => {
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 = useCallback3(
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 = useCallback3(() => {
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 = useCallback3(() => {
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
- useEffect4(() => {
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
- useEffect4(() => {
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__ */ jsx3(
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
+ */