pd-markdown 1.1.0 → 2.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/README.md +52 -170
- package/package.json +12 -11
- package/packages/parser/README.md +120 -0
- package/packages/utils/README.md +130 -0
- package/packages/web/README.md +110 -0
- package/packages/parser/dist/index.cjs +0 -191
- package/packages/parser/dist/index.cjs.map +0 -1
- package/packages/parser/dist/index.d.ts +0 -4
- package/packages/parser/dist/index.d.ts.map +0 -1
- package/packages/parser/dist/index.mjs +0 -185
- package/packages/parser/dist/index.mjs.map +0 -1
- package/packages/parser/dist/plugins/index.d.ts +0 -4
- package/packages/parser/dist/plugins/index.d.ts.map +0 -1
- package/packages/parser/dist/plugins/transform/heading.d.ts +0 -6
- package/packages/parser/dist/plugins/transform/heading.d.ts.map +0 -1
- package/packages/parser/dist/plugins/transform/list.d.ts +0 -14
- package/packages/parser/dist/plugins/transform/list.d.ts.map +0 -1
- package/packages/parser/dist/plugins/transform/table.d.ts +0 -27
- package/packages/parser/dist/plugins/transform/table.d.ts.map +0 -1
- package/packages/parser/dist/processor.d.ts +0 -22
- package/packages/parser/dist/processor.d.ts.map +0 -1
- package/packages/parser/dist/types/index.d.ts +0 -55
- package/packages/parser/dist/types/index.d.ts.map +0 -1
- package/packages/utils/dist/ast/query.d.ts +0 -36
- package/packages/utils/dist/ast/query.d.ts.map +0 -1
- package/packages/utils/dist/ast/traverse.d.ts +0 -22
- package/packages/utils/dist/ast/traverse.d.ts.map +0 -1
- package/packages/utils/dist/index.cjs +0 -358
- package/packages/utils/dist/index.cjs.map +0 -1
- package/packages/utils/dist/index.d.ts +0 -7
- package/packages/utils/dist/index.d.ts.map +0 -1
- package/packages/utils/dist/index.mjs +0 -343
- package/packages/utils/dist/index.mjs.map +0 -1
- package/packages/utils/dist/string/sanitize.d.ts +0 -22
- package/packages/utils/dist/string/sanitize.d.ts.map +0 -1
- package/packages/utils/dist/string/slugify.d.ts +0 -16
- package/packages/utils/dist/string/slugify.d.ts.map +0 -1
- package/packages/utils/dist/types/index.d.ts +0 -49
- package/packages/utils/dist/types/index.d.ts.map +0 -1
- package/packages/web/dist/components/MarkdownRenderer.d.ts +0 -27
- package/packages/web/dist/components/MarkdownRenderer.d.ts.map +0 -1
- package/packages/web/dist/components/NodeRenderer.d.ts +0 -10
- package/packages/web/dist/components/NodeRenderer.d.ts.map +0 -1
- package/packages/web/dist/components/context.d.ts +0 -17
- package/packages/web/dist/components/context.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/Blockquote.d.ts +0 -8
- package/packages/web/dist/components/defaults/Blockquote.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/Code.d.ts +0 -13
- package/packages/web/dist/components/defaults/Code.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/Heading.d.ts +0 -8
- package/packages/web/dist/components/defaults/Heading.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/Image.d.ts +0 -7
- package/packages/web/dist/components/defaults/Image.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/Link.d.ts +0 -8
- package/packages/web/dist/components/defaults/Link.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/List.d.ts +0 -13
- package/packages/web/dist/components/defaults/List.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/Paragraph.d.ts +0 -8
- package/packages/web/dist/components/defaults/Paragraph.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/Table.d.ts +0 -19
- package/packages/web/dist/components/defaults/Table.d.ts.map +0 -1
- package/packages/web/dist/components/defaults/index.d.ts +0 -34
- package/packages/web/dist/components/defaults/index.d.ts.map +0 -1
- package/packages/web/dist/hooks/useMarkdown.d.ts +0 -11
- package/packages/web/dist/hooks/useMarkdown.d.ts.map +0 -1
- package/packages/web/dist/index.cjs +0 -306
- package/packages/web/dist/index.cjs.map +0 -1
- package/packages/web/dist/index.d.ts +0 -6
- package/packages/web/dist/index.d.ts.map +0 -1
- package/packages/web/dist/index.mjs +0 -287
- package/packages/web/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# pd-markdown
|
|
2
2
|
|
|
3
|
-
一个现代化的 Markdown 解析和渲染工具库,基于 unified/remark
|
|
3
|
+
一个现代化的 Markdown 解析和渲染工具库,基于 unified/remark 构建,专为 React 和现代流式 Web 应用设计。
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
7
|
- 🚀 **高性能解析** - 基于 unified/remark 的 Markdown 解析器
|
|
8
|
+
- ⚡ **流式渲染** - 专为 AI 场景设计的 `StreamMarkdownRenderer` 和 `useStreamMarkdown`
|
|
8
9
|
- 📝 **GFM 支持** - 完整支持 GitHub Flavored Markdown
|
|
9
10
|
- 📋 **Frontmatter** - 支持 YAML frontmatter 解析
|
|
10
11
|
- ⚛️ **React 组件** - 提供开箱即用的 React 渲染组件
|
|
11
|
-
- 🎨 **可定制** -
|
|
12
|
+
- 🎨 **可定制** - 支持自定义组件覆盖(Heading, Code, Table 等)
|
|
12
13
|
- 🔗 **自动锚点** - 标题自动生成 slug 锚点
|
|
13
14
|
- 📦 **Tree-shakable** - 支持 ESM,优化打包体积
|
|
14
15
|
|
|
@@ -30,7 +31,7 @@ yarn add pd-markdown
|
|
|
30
31
|
| 模块路径 | 描述 |
|
|
31
32
|
| --- | --- |
|
|
32
33
|
| `pd-markdown/parser` | Markdown 解析器,将 Markdown 转换为 AST |
|
|
33
|
-
| `pd-markdown/web` | React
|
|
34
|
+
| `pd-markdown/web` | React 组件和 Hooks,用于渲染 Markdown 和处理流式内容 |
|
|
34
35
|
| `pd-markdown/utils` | 工具函数库,提供 AST 操作和字符串处理 |
|
|
35
36
|
|
|
36
37
|
## 快速开始
|
|
@@ -45,19 +46,37 @@ function App() {
|
|
|
45
46
|
# Hello World
|
|
46
47
|
|
|
47
48
|
This is a **markdown** document.
|
|
48
|
-
|
|
49
|
-
## Features
|
|
50
|
-
|
|
51
|
-
- GFM support
|
|
52
|
-
- Frontmatter parsing
|
|
53
|
-
- Custom components
|
|
54
49
|
`
|
|
55
50
|
|
|
56
51
|
return <MarkdownRenderer source={markdown} />
|
|
57
52
|
}
|
|
58
53
|
```
|
|
59
54
|
|
|
60
|
-
###
|
|
55
|
+
### 流式渲染 (AI 场景)
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { StreamMarkdownRenderer, useStreamMarkdown } from 'pd-markdown/web'
|
|
59
|
+
|
|
60
|
+
function AIResponse() {
|
|
61
|
+
const stream = useStreamMarkdown()
|
|
62
|
+
|
|
63
|
+
// 模拟从流中读取数据
|
|
64
|
+
const onNewChunk = (chunk: string) => {
|
|
65
|
+
stream.append(chunk)
|
|
66
|
+
if (isLastChunk) stream.done()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<StreamMarkdownRenderer
|
|
71
|
+
source={stream.source}
|
|
72
|
+
isStreaming={stream.isStreaming}
|
|
73
|
+
showCursor={true}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 使用预解析的 AST (SSR 优化)
|
|
61
80
|
|
|
62
81
|
```tsx
|
|
63
82
|
import { createParser } from 'pd-markdown/parser'
|
|
@@ -73,19 +92,6 @@ function Page({ ast }) {
|
|
|
73
92
|
}
|
|
74
93
|
```
|
|
75
94
|
|
|
76
|
-
### 使用 Hook
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
import { useMarkdown, MarkdownRenderer } from 'pd-markdown/web'
|
|
80
|
-
|
|
81
|
-
function MarkdownPreview({ source }) {
|
|
82
|
-
const ast = useMarkdown(source)
|
|
83
|
-
|
|
84
|
-
// 可以对 AST 进行自定义处理
|
|
85
|
-
return <MarkdownRenderer ast={ast} />
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
95
|
## API 文档
|
|
90
96
|
|
|
91
97
|
### pd-markdown/parser
|
|
@@ -100,192 +106,68 @@ import { createParser } from 'pd-markdown/parser'
|
|
|
100
106
|
const parser = createParser({
|
|
101
107
|
gfm: true, // 启用 GFM 支持(默认 true)
|
|
102
108
|
frontmatter: true, // 启用 frontmatter 解析(默认 true)
|
|
103
|
-
plugins: [], // 自定义插件
|
|
104
109
|
})
|
|
105
110
|
|
|
106
111
|
const ast = parser.parse('# Hello World')
|
|
107
112
|
```
|
|
108
113
|
|
|
109
|
-
|
|
114
|
+
### pd-markdown/web
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
#### `<MarkdownRenderer />`
|
|
112
117
|
|
|
113
|
-
|
|
114
|
-
import { definePlugin } from 'pd-markdown/parser'
|
|
115
|
-
|
|
116
|
-
const myPlugin = definePlugin({
|
|
117
|
-
name: 'my-plugin',
|
|
118
|
-
phase: 'after', // 'before' | 'after'
|
|
119
|
-
transform: () => (tree, file) => {
|
|
120
|
-
// 处理 AST
|
|
121
|
-
console.log(tree)
|
|
122
|
-
},
|
|
123
|
-
})
|
|
124
|
-
```
|
|
118
|
+
主渲染组件。支持 `source` 字符串或预解析的 `ast`。
|
|
125
119
|
|
|
126
|
-
|
|
120
|
+
#### `<StreamMarkdownRenderer />`
|
|
127
121
|
|
|
128
|
-
|
|
122
|
+
流式渲染组件,专为实时生成的内容优化。
|
|
129
123
|
|
|
130
|
-
|
|
124
|
+
- `showCursor`: 是否显示打字机光标
|
|
125
|
+
- `enableAnimation`: 是否启用新块淡入动画
|
|
126
|
+
- `isStreaming`: 标记当前流是否仍在进行
|
|
131
127
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
/** 包装器 CSS 类名 */
|
|
141
|
-
className?: string
|
|
142
|
-
/** 包装器内联样式 */
|
|
143
|
-
style?: CSSProperties
|
|
144
|
-
/** 解析器选项(仅在使用 source 时生效) */
|
|
145
|
-
parserOptions?: ParserOptions
|
|
146
|
-
}
|
|
128
|
+
#### `useStreamMarkdown(options?)`
|
|
129
|
+
|
|
130
|
+
管理流式 Markdown 状态的 Hook。
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const { source, ast, append, done, reset } = useStreamMarkdown({
|
|
134
|
+
parseDebounceMs: 30, // 解析防抖,优化超快流速下的性能
|
|
135
|
+
})
|
|
147
136
|
```
|
|
148
137
|
|
|
149
|
-
####
|
|
138
|
+
#### 自定义组件覆盖
|
|
150
139
|
|
|
151
140
|
```tsx
|
|
152
141
|
import { MarkdownRenderer, type ComponentMap } from 'pd-markdown/web'
|
|
153
142
|
|
|
154
143
|
const customComponents: Partial<ComponentMap> = {
|
|
155
144
|
heading: ({ node, children }) => (
|
|
156
|
-
<h2
|
|
157
|
-
),
|
|
158
|
-
code: ({ node, children }) => (
|
|
159
|
-
<pre className="custom-code-block">
|
|
160
|
-
<code>{children}</code>
|
|
161
|
-
</pre>
|
|
145
|
+
<h2 style={{ color: 'blue' }}>{children}</h2>
|
|
162
146
|
),
|
|
163
147
|
}
|
|
164
148
|
|
|
165
149
|
function App() {
|
|
166
|
-
return
|
|
167
|
-
<MarkdownRenderer
|
|
168
|
-
source={markdown}
|
|
169
|
-
components={customComponents}
|
|
170
|
-
/>
|
|
171
|
-
)
|
|
150
|
+
return <MarkdownRenderer source={md} components={customComponents} />
|
|
172
151
|
}
|
|
173
152
|
```
|
|
174
153
|
|
|
175
|
-
#### 可自定义的组件列表
|
|
176
|
-
|
|
177
|
-
| 组件名 | 对应节点 |
|
|
178
|
-
| --- | --- |
|
|
179
|
-
| `heading` | 标题 (h1-h6) |
|
|
180
|
-
| `paragraph` | 段落 |
|
|
181
|
-
| `list` | 列表 |
|
|
182
|
-
| `listItem` | 列表项 |
|
|
183
|
-
| `table` | 表格 |
|
|
184
|
-
| `tableRow` | 表格行 |
|
|
185
|
-
| `tableCell` | 表格单元格 |
|
|
186
|
-
| `code` | 代码块 |
|
|
187
|
-
| `inlineCode` | 行内代码 |
|
|
188
|
-
| `link` | 链接 |
|
|
189
|
-
| `image` | 图片 |
|
|
190
|
-
| `blockquote` | 引用块 |
|
|
191
|
-
|
|
192
154
|
### pd-markdown/utils
|
|
193
155
|
|
|
194
|
-
|
|
156
|
+
提供强大的 AST 遍历与处理工具。
|
|
195
157
|
|
|
196
158
|
```ts
|
|
197
|
-
import {
|
|
198
|
-
// AST 遍历
|
|
199
|
-
traverseAst,
|
|
200
|
-
findNodes,
|
|
201
|
-
findNode,
|
|
202
|
-
// 类型守卫
|
|
203
|
-
isParent,
|
|
204
|
-
isLiteral,
|
|
205
|
-
isNodeType,
|
|
206
|
-
// 字符串处理
|
|
207
|
-
slugify,
|
|
208
|
-
uniqueSlugify,
|
|
209
|
-
escapeHtml,
|
|
210
|
-
sanitizeHtml,
|
|
211
|
-
} from 'pd-markdown/utils'
|
|
212
|
-
|
|
213
|
-
// 查找所有标题节点
|
|
214
|
-
const headings = findNodes(ast, 'heading')
|
|
215
|
-
|
|
216
|
-
// 生成 slug
|
|
217
|
-
const slug = slugify('Hello World') // 'hello-world'
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
## 示例
|
|
221
|
-
|
|
222
|
-
### 博客文章渲染
|
|
223
|
-
|
|
224
|
-
```tsx
|
|
225
|
-
import { createParser } from 'pd-markdown/parser'
|
|
226
|
-
import { MarkdownRenderer } from 'pd-markdown/web'
|
|
227
|
-
|
|
228
|
-
const parser = createParser()
|
|
229
|
-
|
|
230
|
-
function BlogPost({ content }) {
|
|
231
|
-
const ast = parser.parse(content)
|
|
232
|
-
|
|
233
|
-
return (
|
|
234
|
-
<article className="prose">
|
|
235
|
-
<MarkdownRenderer
|
|
236
|
-
ast={ast}
|
|
237
|
-
className="markdown-body"
|
|
238
|
-
/>
|
|
239
|
-
</article>
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### 代码高亮
|
|
245
|
-
|
|
246
|
-
```tsx
|
|
247
|
-
import { MarkdownRenderer, type CodeProps } from 'pd-markdown/web'
|
|
248
|
-
import Prism from 'prismjs'
|
|
249
|
-
|
|
250
|
-
function CodeBlock({ node, children }: CodeProps) {
|
|
251
|
-
const lang = node.lang || 'text'
|
|
252
|
-
const highlighted = Prism.highlight(
|
|
253
|
-
String(children),
|
|
254
|
-
Prism.languages[lang] || Prism.languages.text,
|
|
255
|
-
lang
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
return (
|
|
259
|
-
<pre className={`language-${lang}`}>
|
|
260
|
-
<code dangerouslySetInnerHTML={{ __html: highlighted }} />
|
|
261
|
-
</pre>
|
|
262
|
-
)
|
|
263
|
-
}
|
|
159
|
+
import { traverseAst, findNodes, slugify } from 'pd-markdown/utils'
|
|
264
160
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<MarkdownRenderer
|
|
268
|
-
source={markdown}
|
|
269
|
-
components={{ code: CodeBlock }}
|
|
270
|
-
/>
|
|
271
|
-
)
|
|
272
|
-
}
|
|
161
|
+
// 提取所有标题
|
|
162
|
+
const headings = findNodes(ast, 'heading')
|
|
273
163
|
```
|
|
274
164
|
|
|
275
165
|
## 开发
|
|
276
166
|
|
|
277
167
|
```bash
|
|
278
|
-
# 安装依赖
|
|
279
168
|
pnpm install
|
|
280
|
-
|
|
281
|
-
# 构建
|
|
282
169
|
pnpm build
|
|
283
|
-
|
|
284
|
-
# 运行测试
|
|
285
170
|
pnpm test
|
|
286
|
-
|
|
287
|
-
# 类型检查
|
|
288
|
-
pnpm typecheck
|
|
289
171
|
```
|
|
290
172
|
|
|
291
173
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pd-markdown",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A modular markdown parsing and rendering library",
|
|
6
6
|
"exports": {
|
|
@@ -36,6 +36,15 @@
|
|
|
36
36
|
"files": [
|
|
37
37
|
"packages/*/dist"
|
|
38
38
|
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "pnpm -r run build",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"test:coverage": "vitest run --coverage",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"clean": "pnpm -r run clean",
|
|
46
|
+
"prepublishOnly": "pnpm run build"
|
|
47
|
+
},
|
|
39
48
|
"peerDependencies": {
|
|
40
49
|
"react": "^18.0.0 || ^19.0.0",
|
|
41
50
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
@@ -82,13 +91,5 @@
|
|
|
82
91
|
"type": "git",
|
|
83
92
|
"url": "https://github.com/LaoChen1994/pd-markdown"
|
|
84
93
|
},
|
|
85
|
-
"sideEffects": false
|
|
86
|
-
|
|
87
|
-
"build": "pnpm -r run build",
|
|
88
|
-
"test": "vitest run",
|
|
89
|
-
"test:watch": "vitest",
|
|
90
|
-
"test:coverage": "vitest run --coverage",
|
|
91
|
-
"typecheck": "tsc --noEmit",
|
|
92
|
-
"clean": "pnpm -r run clean"
|
|
93
|
-
}
|
|
94
|
-
}
|
|
94
|
+
"sideEffects": false
|
|
95
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# pd-markdown/parser
|
|
2
|
+
|
|
3
|
+
基于 unified/remark 的 Markdown 解析器,将 Markdown 转换为 AST。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install pd-markdown
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 使用
|
|
12
|
+
|
|
13
|
+
### 基础解析
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createParser } from 'pd-markdown/parser'
|
|
17
|
+
|
|
18
|
+
const parser = createParser()
|
|
19
|
+
const ast = parser.parse('# Hello World')
|
|
20
|
+
|
|
21
|
+
console.log(ast)
|
|
22
|
+
// {
|
|
23
|
+
// type: 'root',
|
|
24
|
+
// children: [
|
|
25
|
+
// { type: 'heading', depth: 1, children: [...] }
|
|
26
|
+
// ]
|
|
27
|
+
// }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 解析选项
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const parser = createParser({
|
|
34
|
+
gfm: true, // 启用 GFM 支持(默认 true)
|
|
35
|
+
frontmatter: true, // 启用 frontmatter 解析(默认 true)
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Frontmatter 支持
|
|
40
|
+
|
|
41
|
+
解析 YAML frontmatter:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const markdown = `---
|
|
45
|
+
title: My Article
|
|
46
|
+
date: 2024-01-01
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
# Hello World
|
|
50
|
+
`
|
|
51
|
+
|
|
52
|
+
const parser = createParser({ frontmatter: true })
|
|
53
|
+
const ast = parser.parse(markdown)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 自定义插件
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { createParser, definePlugin } from 'pd-markdown/parser'
|
|
60
|
+
|
|
61
|
+
const myPlugin = definePlugin({
|
|
62
|
+
name: 'my-plugin',
|
|
63
|
+
phase: 'after',
|
|
64
|
+
transform: () => (tree, file) => {
|
|
65
|
+
// 处理 AST
|
|
66
|
+
console.log('Processing tree:', tree.type)
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const parser = createParser({
|
|
71
|
+
plugins: [myPlugin],
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API
|
|
76
|
+
|
|
77
|
+
### `createParser(options?)`
|
|
78
|
+
|
|
79
|
+
创建解析器实例。
|
|
80
|
+
|
|
81
|
+
**参数:**
|
|
82
|
+
- `options.gfm` - 启用 GFM 支持(默认 `true`)
|
|
83
|
+
- `options.frontmatter` - 启用 frontmatter 解析(默认 `true`)
|
|
84
|
+
- `options.plugins` - 自定义插件数组
|
|
85
|
+
|
|
86
|
+
**返回:**
|
|
87
|
+
- `Parser` 实例,包含 `parse(content)` 方法
|
|
88
|
+
|
|
89
|
+
### `definePlugin(config)`
|
|
90
|
+
|
|
91
|
+
定义自定义插件。
|
|
92
|
+
|
|
93
|
+
**参数:**
|
|
94
|
+
- `config.name` - 插件名称
|
|
95
|
+
- `config.phase` - 执行阶段:`'before'` 或 `'after'`
|
|
96
|
+
- `config.transform` - 转换函数
|
|
97
|
+
|
|
98
|
+
## 导出
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// 核心函数
|
|
102
|
+
export { createParser, definePlugin }
|
|
103
|
+
|
|
104
|
+
// 类型
|
|
105
|
+
export type {
|
|
106
|
+
Parser,
|
|
107
|
+
ParserOptions,
|
|
108
|
+
ParserPlugin,
|
|
109
|
+
PluginConfig,
|
|
110
|
+
FrontmatterData,
|
|
111
|
+
FileData,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 内置转换插件(高级用法)
|
|
115
|
+
export { transformHeading, transformList, transformTable }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# pd-markdown/utils
|
|
2
|
+
|
|
3
|
+
Markdown 处理工具函数库。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install pd-markdown
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### AST 遍历
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { traverseAst, traverseAstWithCallbacks } from 'pd-markdown/utils'
|
|
17
|
+
|
|
18
|
+
// 遍历所有节点
|
|
19
|
+
traverseAst(tree, (node, index, parent) => {
|
|
20
|
+
console.log(node.type)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// 带回调的遍历
|
|
24
|
+
traverseAstWithCallbacks(tree, {
|
|
25
|
+
heading: (node) => console.log('Found heading:', node.depth),
|
|
26
|
+
paragraph: (node) => console.log('Found paragraph'),
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### AST 查询
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { findNodes, findNode, findNodesBy, findParent } from 'pd-markdown/utils'
|
|
34
|
+
|
|
35
|
+
// 查找所有指定类型的节点
|
|
36
|
+
const headings = findNodes(tree, 'heading')
|
|
37
|
+
|
|
38
|
+
// 查找第一个匹配的节点
|
|
39
|
+
const firstHeading = findNode(tree, 'heading')
|
|
40
|
+
|
|
41
|
+
// 按条件查找
|
|
42
|
+
const h1Headings = findNodesBy(tree,
|
|
43
|
+
(node) => node.type === 'heading' && node.depth === 1
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// 查找父节点
|
|
47
|
+
const parent = findParent(tree, someNode)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 类型守卫
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { isParent, isLiteral, isNodeType } from 'pd-markdown/utils'
|
|
54
|
+
|
|
55
|
+
if (isParent(node)) {
|
|
56
|
+
console.log('Children:', node.children)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isLiteral(node)) {
|
|
60
|
+
console.log('Value:', node.value)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (isNodeType(node, 'heading')) {
|
|
64
|
+
console.log('Heading depth:', node.depth)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Slug 生成
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { slugify, uniqueSlugify } from 'pd-markdown/utils'
|
|
72
|
+
|
|
73
|
+
// 基础 slug 生成
|
|
74
|
+
slugify('Hello World') // 'hello-world'
|
|
75
|
+
slugify('API 参考') // 'api-参考'
|
|
76
|
+
slugify('What is React?') // 'what-is-react'
|
|
77
|
+
|
|
78
|
+
// 唯一 slug 生成(避免重复)
|
|
79
|
+
const slugs = new Set<string>()
|
|
80
|
+
slug = uniqueSlugify('Hello', slugs) // 'hello'
|
|
81
|
+
slug = uniqueSlugify('Hello', slugs) // 'hello-1'
|
|
82
|
+
slug = uniqueSlugify('Hello', slugs) // 'hello-2'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### HTML 处理
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { escapeHtml, sanitizeHtml, stripHtml } from 'pd-markdown/utils'
|
|
89
|
+
|
|
90
|
+
// 转义 HTML 特殊字符
|
|
91
|
+
escapeHtml('<script>alert("xss")</script>')
|
|
92
|
+
// '<script>alert("xss")</script>'
|
|
93
|
+
|
|
94
|
+
// 清理 HTML 标签
|
|
95
|
+
sanitizeHtml('<p>Hello</p>') // 'Hello'
|
|
96
|
+
|
|
97
|
+
// 移除 HTML 标签
|
|
98
|
+
stripHtml('<p>Hello <b>World</b></p>') // 'Hello World'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 导出
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// 类型
|
|
105
|
+
export type {
|
|
106
|
+
MdNode,
|
|
107
|
+
MdRoot,
|
|
108
|
+
Parent,
|
|
109
|
+
Literal,
|
|
110
|
+
Visitor,
|
|
111
|
+
PluginOptions,
|
|
112
|
+
Position,
|
|
113
|
+
Location,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// AST 操作
|
|
117
|
+
export { traverseAst, traverseAstWithCallbacks }
|
|
118
|
+
export { findNodes, findNode, findNodesBy, findParent }
|
|
119
|
+
|
|
120
|
+
// 类型守卫
|
|
121
|
+
export { isParent, isLiteral, isNodeType }
|
|
122
|
+
|
|
123
|
+
// 字符串处理
|
|
124
|
+
export { slugify, uniqueSlugify }
|
|
125
|
+
export { escapeHtml, sanitizeHtml, stripHtml }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# pd-markdown/web
|
|
2
|
+
|
|
3
|
+
React 组件库,用于渲染 Markdown,支持静态渲染和流式输出。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install pd-markdown
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Peer Dependencies:**
|
|
12
|
+
- react >= 18.0.0
|
|
13
|
+
- react-dom >= 18.0.0
|
|
14
|
+
|
|
15
|
+
## 使用
|
|
16
|
+
|
|
17
|
+
### 基础渲染
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { MarkdownRenderer } from 'pd-markdown/web'
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
const markdown = '# Hello World'
|
|
24
|
+
return <MarkdownRenderer source={markdown} />
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 流式渲染 (专为 AI 场景优化)
|
|
29
|
+
|
|
30
|
+
支持实时显示正在生成的 Markdown 内容,带有光标指示器和区块加载动画。
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { StreamMarkdownRenderer, useStreamMarkdown } from 'pd-markdown/web'
|
|
34
|
+
|
|
35
|
+
function AIConversation() {
|
|
36
|
+
const stream = useStreamMarkdown()
|
|
37
|
+
|
|
38
|
+
// 当收到新的内容块时
|
|
39
|
+
const onNewData = (chunk: string) => {
|
|
40
|
+
stream.append(chunk)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 结束流
|
|
44
|
+
const onDataDone = () => {
|
|
45
|
+
stream.done()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<StreamMarkdownRenderer
|
|
50
|
+
source={stream.source}
|
|
51
|
+
ast={stream.ast} // 使用 hook 提供的预解析 AST 性能更佳
|
|
52
|
+
isStreaming={stream.isStreaming}
|
|
53
|
+
showCursor={true}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 自定义组件覆盖
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { MarkdownRenderer, type ComponentMap } from 'pd-markdown/web'
|
|
63
|
+
|
|
64
|
+
const components: Partial<ComponentMap> = {
|
|
65
|
+
// 覆盖标题渲染
|
|
66
|
+
heading: ({ node, children }) => (
|
|
67
|
+
<h2 className={`heading-level-${node.depth}`}>{children}</h2>
|
|
68
|
+
),
|
|
69
|
+
// 覆盖代码块渲染
|
|
70
|
+
code: ({ node }) => (
|
|
71
|
+
<pre><code>{node.value}</code></pre>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function App() {
|
|
76
|
+
return <MarkdownRenderer source={md} components={components} />
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API 参考
|
|
81
|
+
|
|
82
|
+
### 组件
|
|
83
|
+
|
|
84
|
+
#### `<MarkdownRenderer />`
|
|
85
|
+
用于普通 Markdown 字符串或 AST 的渲染。
|
|
86
|
+
|
|
87
|
+
#### `<StreamMarkdownRenderer />`
|
|
88
|
+
继承自 `MarkdownRenderer`,增加了对流式状态的支持。
|
|
89
|
+
- `showCursor`: `boolean` - 是否显示闪烁的光标。
|
|
90
|
+
- `enableAnimation`: `boolean` - 是否开启新内容飞入/淡入动画。
|
|
91
|
+
- `isStreaming`: `boolean` - 标记流是否活跃。
|
|
92
|
+
|
|
93
|
+
### Hooks
|
|
94
|
+
|
|
95
|
+
#### `useStreamMarkdown(options?)`
|
|
96
|
+
管理流式内容的状态管理 Hook。返回:
|
|
97
|
+
- `source`: 当前累计的所有文本。
|
|
98
|
+
- `ast`: 当前文本对应的 Root 节点。
|
|
99
|
+
- `isStreaming`: 是否正在流式传输中。
|
|
100
|
+
- `isDone`: 是否已完成。
|
|
101
|
+
- `append(chunk)`: 追加新的内容块。
|
|
102
|
+
- `done()`: 标记流已终结。
|
|
103
|
+
- `reset()`: 重置所有状态。
|
|
104
|
+
|
|
105
|
+
#### `useMarkdown(source, options?)`
|
|
106
|
+
用于手动触发解析并获取 AST 的 Hook。
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|