@incremark/react 0.0.1
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/LICENSE +22 -0
- package/README.md +137 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.js +214 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 wangyishuai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# @incremark/react
|
|
2
|
+
|
|
3
|
+
Incremark 的 React 18+ 集成库。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 📦 **开箱即用** - 提供 `useIncremark` hook 和 `<Incremark>` 组件
|
|
8
|
+
- 🎨 **可定制** - 支持自定义渲染组件
|
|
9
|
+
- ⚡ **高性能** - 利用 React 的 reconciliation 机制
|
|
10
|
+
- 🔧 **DevTools** - 内置开发者工具
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @incremark/core @incremark/react
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 快速开始
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { useIncremark, Incremark } from '@incremark/react'
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
const { blocks, append, finalize, reset } = useIncremark({ gfm: true })
|
|
25
|
+
|
|
26
|
+
async function handleStream(stream: ReadableStream) {
|
|
27
|
+
reset()
|
|
28
|
+
const reader = stream.getReader()
|
|
29
|
+
const decoder = new TextDecoder()
|
|
30
|
+
|
|
31
|
+
while (true) {
|
|
32
|
+
const { done, value } = await reader.read()
|
|
33
|
+
if (done) break
|
|
34
|
+
append(decoder.decode(value))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
finalize()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<button onClick={() => handleStream(stream)}>开始</button>
|
|
43
|
+
<Incremark blocks={blocks} />
|
|
44
|
+
</>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
### useIncremark(options)
|
|
52
|
+
|
|
53
|
+
核心 hook。
|
|
54
|
+
|
|
55
|
+
**返回值:**
|
|
56
|
+
|
|
57
|
+
| 属性 | 类型 | 说明 |
|
|
58
|
+
|------|------|------|
|
|
59
|
+
| `markdown` | `string` | 完整 Markdown |
|
|
60
|
+
| `blocks` | `Block[]` | 所有块 |
|
|
61
|
+
| `completedBlocks` | `Block[]` | 已完成块 |
|
|
62
|
+
| `pendingBlocks` | `Block[]` | 待处理块 |
|
|
63
|
+
| `append` | `Function` | 追加内容 |
|
|
64
|
+
| `finalize` | `Function` | 完成解析 |
|
|
65
|
+
| `reset` | `Function` | 重置状态 |
|
|
66
|
+
|
|
67
|
+
### useDevTools(incremark)
|
|
68
|
+
|
|
69
|
+
启用 DevTools。
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
const incremark = useIncremark()
|
|
73
|
+
useDevTools(incremark)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### \<Incremark\>
|
|
77
|
+
|
|
78
|
+
渲染组件。
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<Incremark
|
|
82
|
+
blocks={blocks}
|
|
83
|
+
components={{ heading: MyHeading }}
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 自定义组件
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { useIncremark, Incremark } from '@incremark/react'
|
|
91
|
+
import MyCode from './MyCode'
|
|
92
|
+
|
|
93
|
+
function App() {
|
|
94
|
+
const { blocks } = useIncremark()
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Incremark
|
|
98
|
+
blocks={blocks}
|
|
99
|
+
components={{ code: MyCode }}
|
|
100
|
+
/>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 与 React Query 集成
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { useQuery } from '@tanstack/react-query'
|
|
109
|
+
import { useIncremark, Incremark } from '@incremark/react'
|
|
110
|
+
|
|
111
|
+
function StreamingContent() {
|
|
112
|
+
const { blocks, append, finalize, reset } = useIncremark()
|
|
113
|
+
|
|
114
|
+
const { refetch } = useQuery({
|
|
115
|
+
queryKey: ['chat'],
|
|
116
|
+
queryFn: async () => {
|
|
117
|
+
reset()
|
|
118
|
+
// ... 流式处理
|
|
119
|
+
finalize()
|
|
120
|
+
return null
|
|
121
|
+
},
|
|
122
|
+
enabled: false
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<>
|
|
127
|
+
<button onClick={() => refetch()}>开始</button>
|
|
128
|
+
<Incremark blocks={blocks} />
|
|
129
|
+
</>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
137
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ParserOptions, ParsedBlock, Root, IncrementalUpdate, IncremarkParser, RootContent } from '@incremark/core';
|
|
2
|
+
export { IncrementalUpdate, ParsedBlock, ParserOptions, Root, RootContent } from '@incremark/core';
|
|
3
|
+
import * as _incremark_devtools from '@incremark/devtools';
|
|
4
|
+
import { DevToolsOptions } from '@incremark/devtools';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
interface UseIncremarkOptions extends ParserOptions {
|
|
8
|
+
}
|
|
9
|
+
interface BlockWithStableId$1 extends ParsedBlock {
|
|
10
|
+
stableId: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* React Hook: Incremark 流式 Markdown 解析器
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* import { useIncremark } from '@incremark/react'
|
|
18
|
+
*
|
|
19
|
+
* function App() {
|
|
20
|
+
* const { markdown, blocks, append, finalize, reset } = useIncremark()
|
|
21
|
+
*
|
|
22
|
+
* async function handleStream(stream) {
|
|
23
|
+
* for await (const chunk of stream) {
|
|
24
|
+
* append(chunk)
|
|
25
|
+
* }
|
|
26
|
+
* finalize()
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* return <div>{markdown.length} 字符</div>
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare function useIncremark(options?: UseIncremarkOptions): {
|
|
34
|
+
/** 已收集的完整 Markdown 字符串 */
|
|
35
|
+
markdown: string;
|
|
36
|
+
/** 已完成的块列表 */
|
|
37
|
+
completedBlocks: ParsedBlock[];
|
|
38
|
+
/** 待处理的块列表 */
|
|
39
|
+
pendingBlocks: ParsedBlock[];
|
|
40
|
+
/** 当前完整的 AST */
|
|
41
|
+
ast: Root;
|
|
42
|
+
/** 所有块(完成 + 待处理),带稳定 ID */
|
|
43
|
+
blocks: BlockWithStableId$1[];
|
|
44
|
+
/** 是否正在加载 */
|
|
45
|
+
isLoading: boolean;
|
|
46
|
+
/** 追加内容 */
|
|
47
|
+
append: (chunk: string) => IncrementalUpdate;
|
|
48
|
+
/** 完成解析 */
|
|
49
|
+
finalize: () => IncrementalUpdate;
|
|
50
|
+
/** 强制中断 */
|
|
51
|
+
abort: () => IncrementalUpdate;
|
|
52
|
+
/** 重置解析器 */
|
|
53
|
+
reset: () => void;
|
|
54
|
+
/** 解析器实例 */
|
|
55
|
+
parser: IncremarkParser;
|
|
56
|
+
};
|
|
57
|
+
type UseIncremarkReturn = ReturnType<typeof useIncremark>;
|
|
58
|
+
|
|
59
|
+
interface UseDevToolsOptions extends DevToolsOptions {
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* React DevTools 一行接入
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* import { useIncremark, useDevTools } from '@incremark/react'
|
|
67
|
+
*
|
|
68
|
+
* function App() {
|
|
69
|
+
* const incremark = useIncremark()
|
|
70
|
+
* useDevTools(incremark) // 就这一行!
|
|
71
|
+
*
|
|
72
|
+
* return <div>...</div>
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
declare function useDevTools(incremark: UseIncremarkReturn, options?: UseDevToolsOptions): _incremark_devtools.IncremarkDevTools | null;
|
|
77
|
+
|
|
78
|
+
interface BlockWithStableId extends ParsedBlock {
|
|
79
|
+
stableId: string;
|
|
80
|
+
}
|
|
81
|
+
interface IncremarkProps {
|
|
82
|
+
/** 要渲染的块列表 */
|
|
83
|
+
blocks: BlockWithStableId[];
|
|
84
|
+
/** 自定义组件映射 */
|
|
85
|
+
components?: Partial<Record<string, React.ComponentType<{
|
|
86
|
+
node: any;
|
|
87
|
+
}>>>;
|
|
88
|
+
/** 是否显示块状态(待处理块边框) */
|
|
89
|
+
showBlockStatus?: boolean;
|
|
90
|
+
/** 自定义类名 */
|
|
91
|
+
className?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Incremark 主渲染组件
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```tsx
|
|
98
|
+
* import { useIncremark, Incremark } from '@incremark/react'
|
|
99
|
+
*
|
|
100
|
+
* function App() {
|
|
101
|
+
* const { blocks } = useIncremark()
|
|
102
|
+
* return <Incremark blocks={blocks} />
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
declare const Incremark: React.FC<IncremarkProps>;
|
|
107
|
+
|
|
108
|
+
interface IncremarkRendererProps {
|
|
109
|
+
node: RootContent;
|
|
110
|
+
components?: Partial<Record<string, React.ComponentType<{
|
|
111
|
+
node: any;
|
|
112
|
+
}>>>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 渲染单个 AST 节点
|
|
116
|
+
*/
|
|
117
|
+
declare const IncremarkRenderer: React.FC<IncremarkRendererProps>;
|
|
118
|
+
|
|
119
|
+
export { Incremark, type IncremarkProps, IncremarkRenderer, type IncremarkRendererProps, type UseDevToolsOptions, type UseIncremarkOptions, type UseIncremarkReturn, useDevTools, useIncremark };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// src/hooks/useIncremark.ts
|
|
2
|
+
import { useState, useCallback, useMemo, useRef } from "react";
|
|
3
|
+
import {
|
|
4
|
+
createIncremarkParser
|
|
5
|
+
} from "@incremark/core";
|
|
6
|
+
function useIncremark(options = {}) {
|
|
7
|
+
const parserRef = useRef(null);
|
|
8
|
+
if (!parserRef.current) {
|
|
9
|
+
parserRef.current = createIncremarkParser(options);
|
|
10
|
+
}
|
|
11
|
+
const parser = parserRef.current;
|
|
12
|
+
const [markdown, setMarkdown] = useState("");
|
|
13
|
+
const [completedBlocks, setCompletedBlocks] = useState([]);
|
|
14
|
+
const [pendingBlocks, setPendingBlocks] = useState([]);
|
|
15
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
16
|
+
const blocks = useMemo(() => {
|
|
17
|
+
const result = [];
|
|
18
|
+
for (const block of completedBlocks) {
|
|
19
|
+
result.push({ ...block, stableId: block.id });
|
|
20
|
+
}
|
|
21
|
+
for (let i = 0; i < pendingBlocks.length; i++) {
|
|
22
|
+
result.push({
|
|
23
|
+
...pendingBlocks[i],
|
|
24
|
+
stableId: `pending-${i}`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}, [completedBlocks, pendingBlocks]);
|
|
29
|
+
const ast = useMemo(
|
|
30
|
+
() => ({
|
|
31
|
+
type: "root",
|
|
32
|
+
children: [...completedBlocks.map((b) => b.node), ...pendingBlocks.map((b) => b.node)]
|
|
33
|
+
}),
|
|
34
|
+
[completedBlocks, pendingBlocks]
|
|
35
|
+
);
|
|
36
|
+
const append = useCallback(
|
|
37
|
+
(chunk) => {
|
|
38
|
+
setIsLoading(true);
|
|
39
|
+
const update = parser.append(chunk);
|
|
40
|
+
setMarkdown(parser.getBuffer());
|
|
41
|
+
if (update.completed.length > 0) {
|
|
42
|
+
setCompletedBlocks((prev) => [...prev, ...update.completed]);
|
|
43
|
+
}
|
|
44
|
+
setPendingBlocks(update.pending);
|
|
45
|
+
return update;
|
|
46
|
+
},
|
|
47
|
+
[parser]
|
|
48
|
+
);
|
|
49
|
+
const finalize = useCallback(() => {
|
|
50
|
+
const update = parser.finalize();
|
|
51
|
+
setMarkdown(parser.getBuffer());
|
|
52
|
+
if (update.completed.length > 0) {
|
|
53
|
+
setCompletedBlocks((prev) => [...prev, ...update.completed]);
|
|
54
|
+
}
|
|
55
|
+
setPendingBlocks([]);
|
|
56
|
+
setIsLoading(false);
|
|
57
|
+
return update;
|
|
58
|
+
}, [parser]);
|
|
59
|
+
const abort = useCallback(() => {
|
|
60
|
+
return finalize();
|
|
61
|
+
}, [finalize]);
|
|
62
|
+
const reset = useCallback(() => {
|
|
63
|
+
parser.reset();
|
|
64
|
+
setCompletedBlocks([]);
|
|
65
|
+
setPendingBlocks([]);
|
|
66
|
+
setMarkdown("");
|
|
67
|
+
setIsLoading(false);
|
|
68
|
+
}, [parser]);
|
|
69
|
+
return {
|
|
70
|
+
/** 已收集的完整 Markdown 字符串 */
|
|
71
|
+
markdown,
|
|
72
|
+
/** 已完成的块列表 */
|
|
73
|
+
completedBlocks,
|
|
74
|
+
/** 待处理的块列表 */
|
|
75
|
+
pendingBlocks,
|
|
76
|
+
/** 当前完整的 AST */
|
|
77
|
+
ast,
|
|
78
|
+
/** 所有块(完成 + 待处理),带稳定 ID */
|
|
79
|
+
blocks,
|
|
80
|
+
/** 是否正在加载 */
|
|
81
|
+
isLoading,
|
|
82
|
+
/** 追加内容 */
|
|
83
|
+
append,
|
|
84
|
+
/** 完成解析 */
|
|
85
|
+
finalize,
|
|
86
|
+
/** 强制中断 */
|
|
87
|
+
abort,
|
|
88
|
+
/** 重置解析器 */
|
|
89
|
+
reset,
|
|
90
|
+
/** 解析器实例 */
|
|
91
|
+
parser
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/hooks/useDevTools.ts
|
|
96
|
+
import { useEffect, useRef as useRef2 } from "react";
|
|
97
|
+
import { createDevTools } from "@incremark/devtools";
|
|
98
|
+
function useDevTools(incremark, options = {}) {
|
|
99
|
+
const devtoolsRef = useRef2(null);
|
|
100
|
+
const optionsRef = useRef2(options);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const devtools = createDevTools(optionsRef.current);
|
|
103
|
+
devtoolsRef.current = devtools;
|
|
104
|
+
incremark.parser.setOnChange((state) => {
|
|
105
|
+
const blocks = [
|
|
106
|
+
...state.completedBlocks.map((b) => ({ ...b, stableId: b.id })),
|
|
107
|
+
...state.pendingBlocks.map((b, i) => ({ ...b, stableId: `pending-${i}` }))
|
|
108
|
+
];
|
|
109
|
+
devtools.update({
|
|
110
|
+
blocks,
|
|
111
|
+
completedBlocks: state.completedBlocks,
|
|
112
|
+
pendingBlocks: state.pendingBlocks,
|
|
113
|
+
markdown: state.markdown,
|
|
114
|
+
ast: state.ast,
|
|
115
|
+
isLoading: state.pendingBlocks.length > 0
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
devtools.mount();
|
|
119
|
+
return () => {
|
|
120
|
+
devtools.unmount();
|
|
121
|
+
incremark.parser.setOnChange(void 0);
|
|
122
|
+
};
|
|
123
|
+
}, [incremark.parser]);
|
|
124
|
+
return devtoolsRef.current;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/components/IncremarkRenderer.tsx
|
|
128
|
+
import React from "react";
|
|
129
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
130
|
+
function renderInlineChildren(children) {
|
|
131
|
+
if (!children) return null;
|
|
132
|
+
return children.map((child, i) => {
|
|
133
|
+
switch (child.type) {
|
|
134
|
+
case "text":
|
|
135
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: child.value }, i);
|
|
136
|
+
case "strong":
|
|
137
|
+
return /* @__PURE__ */ jsx("strong", { children: renderInlineChildren(child.children) }, i);
|
|
138
|
+
case "emphasis":
|
|
139
|
+
return /* @__PURE__ */ jsx("em", { children: renderInlineChildren(child.children) }, i);
|
|
140
|
+
case "inlineCode":
|
|
141
|
+
return /* @__PURE__ */ jsx("code", { className: "incremark-inline-code", children: child.value }, i);
|
|
142
|
+
case "link":
|
|
143
|
+
return /* @__PURE__ */ jsx("a", { href: child.url, target: "_blank", rel: "noopener noreferrer", children: renderInlineChildren(child.children) }, i);
|
|
144
|
+
case "image":
|
|
145
|
+
return /* @__PURE__ */ jsx("img", { src: child.url, alt: child.alt || "", loading: "lazy" }, i);
|
|
146
|
+
case "break":
|
|
147
|
+
return /* @__PURE__ */ jsx("br", {}, i);
|
|
148
|
+
case "delete":
|
|
149
|
+
return /* @__PURE__ */ jsx("del", { children: renderInlineChildren(child.children) }, i);
|
|
150
|
+
case "paragraph":
|
|
151
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: renderInlineChildren(child.children) }, i);
|
|
152
|
+
default:
|
|
153
|
+
return /* @__PURE__ */ jsx("span", { children: child.value || "" }, i);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
var DefaultHeading = ({ node }) => {
|
|
158
|
+
const Tag = `h${node.depth}`;
|
|
159
|
+
return /* @__PURE__ */ jsx(Tag, { className: "incremark-heading", children: renderInlineChildren(node.children) });
|
|
160
|
+
};
|
|
161
|
+
var DefaultParagraph = ({ node }) => /* @__PURE__ */ jsx("p", { className: "incremark-paragraph", children: renderInlineChildren(node.children) });
|
|
162
|
+
var DefaultCode = ({ node }) => /* @__PURE__ */ jsxs("div", { className: "incremark-code", children: [
|
|
163
|
+
/* @__PURE__ */ jsx("div", { className: "code-header", children: /* @__PURE__ */ jsx("span", { className: "language", children: node.lang || "text" }) }),
|
|
164
|
+
/* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: node.value }) })
|
|
165
|
+
] });
|
|
166
|
+
var DefaultList = ({ node }) => {
|
|
167
|
+
const Tag = node.ordered ? "ol" : "ul";
|
|
168
|
+
return /* @__PURE__ */ jsx(Tag, { className: "incremark-list", children: node.children?.map((item, i) => /* @__PURE__ */ jsx("li", { children: renderInlineChildren(item.children) }, i)) });
|
|
169
|
+
};
|
|
170
|
+
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) }) : renderInlineChildren(child.children || []) }, i)) });
|
|
171
|
+
var DefaultTable = ({ node }) => /* @__PURE__ */ jsx("div", { className: "incremark-table-wrapper", children: /* @__PURE__ */ jsxs("table", { className: "incremark-table", children: [
|
|
172
|
+
/* @__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)) }) }),
|
|
173
|
+
/* @__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)) })
|
|
174
|
+
] }) });
|
|
175
|
+
var DefaultThematicBreak = () => /* @__PURE__ */ jsx("hr", { className: "incremark-hr" });
|
|
176
|
+
var DefaultDefault = ({ node }) => /* @__PURE__ */ jsx("div", { className: "incremark-unknown", "data-type": node.type, children: /* @__PURE__ */ jsx("pre", { children: JSON.stringify(node, null, 2) }) });
|
|
177
|
+
var defaultComponents = {
|
|
178
|
+
heading: DefaultHeading,
|
|
179
|
+
paragraph: DefaultParagraph,
|
|
180
|
+
code: DefaultCode,
|
|
181
|
+
list: DefaultList,
|
|
182
|
+
blockquote: DefaultBlockquote,
|
|
183
|
+
table: DefaultTable,
|
|
184
|
+
thematicBreak: DefaultThematicBreak
|
|
185
|
+
};
|
|
186
|
+
var IncremarkRenderer = ({ node, components = {} }) => {
|
|
187
|
+
const mergedComponents = { ...defaultComponents, ...components };
|
|
188
|
+
const Component = mergedComponents[node.type] || DefaultDefault;
|
|
189
|
+
return /* @__PURE__ */ jsx(Component, { node });
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/components/Incremark.tsx
|
|
193
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
194
|
+
var Incremark = ({
|
|
195
|
+
blocks,
|
|
196
|
+
components,
|
|
197
|
+
showBlockStatus = true,
|
|
198
|
+
className = ""
|
|
199
|
+
}) => {
|
|
200
|
+
return /* @__PURE__ */ jsx2("div", { className: `incremark ${className}`, children: blocks.map((block) => /* @__PURE__ */ jsx2(
|
|
201
|
+
"div",
|
|
202
|
+
{
|
|
203
|
+
className: `incremark-block ${showBlockStatus && block.status === "pending" ? "pending" : ""}`,
|
|
204
|
+
children: /* @__PURE__ */ jsx2(IncremarkRenderer, { node: block.node, components })
|
|
205
|
+
},
|
|
206
|
+
block.stableId
|
|
207
|
+
)) });
|
|
208
|
+
};
|
|
209
|
+
export {
|
|
210
|
+
Incremark,
|
|
211
|
+
IncremarkRenderer,
|
|
212
|
+
useDevTools,
|
|
213
|
+
useIncremark
|
|
214
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@incremark/react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Incremark React integration - Incremental Markdown parser for AI streaming",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": ">=18.0.0",
|
|
21
|
+
"@incremark/core": "0.0.1"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@incremark/devtools": "0.0.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/react": "^18.2.0",
|
|
28
|
+
"react": "^18.2.0",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.3.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsup --watch"
|
|
35
|
+
}
|
|
36
|
+
}
|