@haklex/rich-editor 0.0.33 → 0.0.35

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 CHANGED
@@ -1,163 +1,334 @@
1
1
  # @haklex/rich-editor
2
2
 
3
- 基于 [Lexical](https://lexical.dev/) 的富文本编辑器,支持自定义节点、插件和 Markdown 快捷键。
3
+ 基于 [Lexical](https://lexical.dev/) 的富文本编辑器核心包。
4
+ 负责 `编辑态` 运行时、节点注册、插件装配、Markdown 快捷键与主题系统。
5
+
6
+ ## 包定位
7
+
8
+ - `@haklex/rich-editor`:编辑器核心(`RichEditor`)
9
+ - `@haklex/rich-renderer`:只读渲染引擎(`RichRenderer`)
10
+ - `@haklex/rich-renderers`:静态增强渲染器聚合(codeblock、image、video、mermaid...)
11
+ - `@haklex/rich-renderers-edit`:编辑增强渲染器与扩展插件聚合
12
+ - `@haklex/rich-kit-shiro`:一站式生产组合(编辑 + 渲染 + 常用扩展)
4
13
 
5
14
  ## 安装
6
15
 
7
16
  ```bash
8
- pnpm add @haklex/rich-editor lexical @lexical/react
17
+ pnpm add @haklex/rich-editor lexical @lexical/react react react-dom
9
18
  ```
10
19
 
11
- ## 使用
20
+ 可选依赖(按功能开启):
21
+
22
+ - `katex`:数学公式渲染
23
+ - `shiki`:代码高亮
12
24
 
13
- ### 编辑器模式
25
+ ## 快速开始
14
26
 
15
27
  ```tsx
16
28
  import { RichEditor } from '@haklex/rich-editor'
17
29
  import type { SerializedEditorState } from 'lexical'
30
+ import { useState } from 'react'
31
+ import '@haklex/rich-editor/style.css'
18
32
 
19
- function Editor() {
20
- const [state, setState] = useState<SerializedEditorState | undefined>()
33
+ export function DemoEditor() {
34
+ const [value, setValue] = useState<SerializedEditorState | undefined>()
21
35
 
22
36
  return (
23
37
  <RichEditor
24
- initialValue={state}
25
- onChange={setState}
38
+ initialValue={value}
39
+ onChange={setValue}
26
40
  variant="article"
27
- placeholder="开始写作..."
41
+ theme="light"
42
+ placeholder="Write something..."
43
+ onSubmit={() => {
44
+ // Cmd/Ctrl + Enter
45
+ }}
28
46
  />
29
47
  )
30
48
  }
31
49
  ```
32
50
 
33
- ### 渲染器模式(只读)
51
+ 如果要渲染只读内容:
34
52
 
35
53
  ```tsx
36
- import { RichRenderer } from '@haklex/rich-editor/renderer'
37
- import type { SerializedEditorState } from 'lexical'
54
+ import { RichRenderer } from '@haklex/rich-renderer'
38
55
 
39
- function Renderer({ content }: { content: SerializedEditorState }) {
40
- return (
41
- <RichRenderer
42
- value={content}
43
- variant="article"
44
- />
45
- )
46
- }
56
+ <RichRenderer value={value} />
47
57
  ```
48
58
 
49
- ## 导出入口
59
+ ## 数据格式
60
+
61
+ `onChange` 返回的是 `SerializedEditorState`(Lexical JSON),推荐直接落库。
62
+ `initialValue` 直接喂回这个 JSON,可以做到无损回显。
50
63
 
51
- | 路径 | 说明 |
52
- |------|------|
53
- | `@haklex/rich-editor` | 完整导出 |
54
- | `@haklex/rich-editor/editor` | 仅 `RichEditor` |
55
- | `@haklex/rich-editor/renderer` | 仅 `RichRenderer` |
56
- | `@haklex/rich-editor/style.css` | 样式文件 |
64
+ ## 导出入口
57
65
 
58
- ## API
66
+ | 入口 | 说明 |
67
+ | --- | --- |
68
+ | `@haklex/rich-editor` | 完整导出(组件、节点、插件命令、上下文、类型) |
69
+ | `@haklex/rich-editor/editor` | 轻入口:`RichEditor` + 节点配置 |
70
+ | `@haklex/rich-editor/static` | 渲染/SSR 相关静态工具(供 `@haklex/rich-renderer` 与扩展包使用) |
71
+ | `@haklex/rich-editor/styles` | 仅样式变量/variant class 导出(不含 Lexical theme 对象) |
72
+ | `@haklex/rich-editor/style.css` | 打包后的编辑器样式 |
59
73
 
60
- ### RichEditorProps
74
+ ## RichEditor API
61
75
 
62
76
  ```ts
77
+ type RichEditorVariant = 'article' | 'comment' | 'note'
78
+
63
79
  interface RichEditorProps {
64
- initialValue?: SerializedEditorState // 初始状态
80
+ initialValue?: SerializedEditorState
65
81
  onChange?: (value: SerializedEditorState) => void
66
- variant?: 'article' | 'comment' | 'note' // 显示变体
82
+ variant?: RichEditorVariant
67
83
  theme?: 'light' | 'dark'
68
84
  placeholder?: string
69
85
  onSubmit?: () => void
70
86
  autoFocus?: boolean
71
87
  className?: string
72
88
  contentClassName?: string
89
+ style?: React.CSSProperties
73
90
  actions?: ReactNode
74
91
  onEditorReady?: (editor: LexicalEditor | null) => void
75
92
  extraNodes?: Array<Klass<LexicalNode>>
76
93
  rendererConfig?: RendererConfig
94
+ imageUpload?: ImageUploadFn
77
95
  debounceMs?: number
78
96
  children?: ReactNode
79
97
  }
80
98
  ```
81
99
 
82
- ### RichRendererProps
100
+ 关键字段说明:
101
+
102
+ - `extraNodes`:注册你自己的 Lexical 节点(扩展节点必配)
103
+ - `rendererConfig`:覆写默认渲染器(如 Image/CodeBlock/LinkCard)
104
+ - `actions`:插在编辑器底部 actions 区域(常用于 Slash 菜单)
105
+ - `children`:插件插槽(如 `EmbedPlugin`、`TldrawPlugin`)
106
+ - `imageUpload`:替换默认图片上传逻辑
107
+ - `debounceMs`:`onChange` 的防抖间隔(毫秒)
108
+
109
+ ## RendererConfig API
83
110
 
84
111
  ```ts
85
- interface RichRendererProps {
86
- value: SerializedEditorState
87
- variant?: 'article' | 'comment' | 'note'
88
- theme?: 'light' | 'dark'
89
- className?: string
90
- as?: React.ElementType
91
- rendererConfig?: RendererConfig
92
- extraNodes?: Array<Klass<LexicalNode>>
112
+ interface RendererConfig {
113
+ Alert?: ComponentType<AlertRendererProps>
114
+ Banner?: ComponentType<BannerRendererProps>
115
+ CodeBlock?: ComponentType<CodeBlockRendererProps>
116
+ CodeSnippet?: ComponentType<CodeSnippetRendererProps>
117
+ Footnote?: ComponentType<FootnoteRendererProps>
118
+ FootnoteSection?: ComponentType<FootnoteSectionRendererProps>
119
+ Gallery?: ComponentType<GalleryRendererProps>
120
+ Image?: ComponentType<ImageRendererProps>
121
+ KaTeX?: ComponentType<KaTeXRendererProps>
122
+ LinkCard?: ComponentType<LinkCardRendererProps>
123
+ Mermaid?: ComponentType<MermaidRendererProps>
124
+ Mention?: ComponentType<MentionRendererProps>
125
+ Video?: ComponentType<VideoRendererProps>
126
+ }
127
+ ```
128
+
129
+ 示例:覆写图片与代码块渲染器
130
+
131
+ ```tsx
132
+ import type { RendererConfig } from '@haklex/rich-editor'
133
+
134
+ const rendererConfig: RendererConfig = {
135
+ Image: MyImageRenderer,
136
+ CodeBlock: MyCodeBlockRenderer,
93
137
  }
138
+
139
+ <RichEditor rendererConfig={rendererConfig} />
94
140
  ```
95
141
 
96
- ## 变体
142
+ ## 内置节点与插件
143
+
144
+ 默认内置节点(`config.ts` / `config-edit.ts`):
145
+
146
+ - 内置 Lexical 节点:Heading、Quote、List、Link、Table、Code、HorizontalRule...
147
+ - 自定义节点:Spoiler、Mention、KaTeXInline、KaTeXBlock、Image、Alert、CodeBlock、Footnote、FootnoteSection、Video、LinkCard、Details、Grid、Banner、Mermaid
97
148
 
98
- | 变体 | 字体 | 字号 | 行高 | 用途 |
99
- |------|------|------|------|------|
100
- | `article` | 系统无衬线 | 16px | 1.7 | 博客文章 |
101
- | `note` | 思源宋体 | 16px | 1.8 | 个人笔记 |
102
- | `comment` | 系统无衬线 | 14px | 1.5 | 评论 |
149
+ `RichEditor` 默认挂载插件:
103
150
 
104
- ## 自定义渲染器
151
+ - 历史/列表/链接/表格:`HistoryPlugin`、`ListPlugin`、`LinkPlugin`、`TablePlugin`
152
+ - 编辑增强:`MarkdownShortcutsPlugin`、`CheckListPlugin`、`AutoLinkPlugin`
153
+ - 内容能力:`ImagePlugin`、`ImageUploadPlugin`、`KaTeXPlugin`、`AlertPlugin`、`MermaidPlugin`、`HorizontalRulePlugin`
154
+ - 生命周期:`OnChangePlugin`、`EditorRefPlugin`、`SubmitShortcutPlugin`(Cmd/Ctrl+Enter)、`AutoFocusPlugin`
155
+
156
+ ## 常用命令导出
157
+
158
+ ```ts
159
+ INSERT_ALERT_COMMAND
160
+ INSERT_IMAGE_COMMAND
161
+ OPEN_IMAGE_UPLOAD_DIALOG_COMMAND
162
+ INSERT_KATEX_INLINE_COMMAND
163
+ INSERT_KATEX_BLOCK_COMMAND
164
+ INSERT_MERMAID_COMMAND
165
+ ```
166
+
167
+ 可在自定义插件中 `editor.dispatchCommand(...)` 触发。
168
+
169
+ ## Markdown Transformer
170
+
171
+ 导出:`ALL_TRANSFORMERS`(`src/transformers`)
172
+
173
+ 覆盖能力包括:
174
+
175
+ - Inline:Spoiler、Mention、Footnote、`++underline++`、上/下标、KaTeX inline
176
+ - Block:FootnoteSection、Container(Banner/Details)、Git Alert、KaTeX block、Image/Video/CodeBlock/LinkCard/Mermaid/Grid/Table/HR + Lexical 默认 transformers
177
+
178
+ 参考:`docs/export-format-summary.md`
179
+
180
+ ## 设计模式
181
+
182
+ ### 1) Static / Edit 节点分离
183
+
184
+ 对“编辑态很重”的节点采用双类拆分:
185
+
186
+ - 静态节点:用于只读渲染,依赖轻
187
+ - 编辑节点:继承静态节点,覆盖 `decorate()` 挂重 UI(Popover、Dialog、Nested editor 等)
188
+
189
+ 典型:`AlertQuoteNode` / `AlertQuoteEditNode`、`CodeBlockNode` / `CodeBlockEditNode`。
190
+
191
+ ### 2) RendererWrapper 注入模式
192
+
193
+ 节点内部通过 `createRendererDecoration(...)` 把渲染职责交给 `RendererConfig`,实现:
194
+
195
+ - 默认渲染器可直接用
196
+ - 业务侧可按 key 定向覆写
197
+ - 保持节点协议不变
198
+
199
+ ### 3) Command + Plugin 模式
200
+
201
+ 扩展插入动作统一走 `createCommand` + `registerCommand`,避免 UI 与文档结构硬耦合。
202
+
203
+ ## 扩展编写与接入
204
+
205
+ 下面是可直接复用的扩展落地流程。
206
+
207
+ ### 步骤 1:定义 Node(静态)
105
208
 
106
209
  ```tsx
107
- import type { RendererConfig } from '@haklex/rich-editor'
210
+ import type {
211
+ EditorConfig,
212
+ LexicalEditor,
213
+ LexicalNode,
214
+ NodeKey,
215
+ SerializedLexicalNode,
216
+ Spread,
217
+ } from 'lexical'
218
+ import { DecoratorNode } from 'lexical'
219
+ import type { ReactElement } from 'react'
220
+ import { createElement } from 'react'
221
+
222
+ import { PollRenderer } from './PollRenderer'
223
+
224
+ type SerializedPollNode = Spread<
225
+ { question: string },
226
+ SerializedLexicalNode
227
+ >
228
+
229
+ export class PollNode extends DecoratorNode<ReactElement> {
230
+ __question: string
231
+
232
+ static getType() {
233
+ return 'poll'
234
+ }
235
+
236
+ static clone(node: PollNode) {
237
+ return new PollNode(node.__question, node.__key)
238
+ }
239
+
240
+ constructor(question: string, key?: NodeKey) {
241
+ super(key)
242
+ this.__question = question
243
+ }
244
+
245
+ createDOM(_config: EditorConfig) {
246
+ const el = document.createElement('div')
247
+ el.className = 'rich-poll-wrapper'
248
+ return el
249
+ }
250
+
251
+ updateDOM() {
252
+ return false
253
+ }
254
+
255
+ isInline() {
256
+ return false
257
+ }
258
+
259
+ static importJSON(json: SerializedPollNode) {
260
+ return new PollNode(json.question)
261
+ }
262
+
263
+ exportJSON(): SerializedPollNode {
264
+ return {
265
+ ...super.exportJSON(),
266
+ type: 'poll',
267
+ question: this.__question,
268
+ version: 1,
269
+ }
270
+ }
271
+
272
+ decorate(_editor: LexicalEditor, _config: EditorConfig): ReactElement {
273
+ return createElement(PollRenderer, {
274
+ question: this.__question,
275
+ })
276
+ }
277
+ }
108
278
 
109
- const customConfig: RendererConfig = {
110
- Image: CustomImageRenderer,
111
- CodeBlock: CustomCodeRenderer,
279
+ export function $isPollNode(
280
+ node: LexicalNode | null | undefined,
281
+ ): node is PollNode {
282
+ return node instanceof PollNode
112
283
  }
284
+ ```
285
+
286
+ 说明:`RendererConfig` 只能覆写预定义 key。
287
+ 像 `Poll` 这种全新节点,一般采用“节点内直接渲染组件”或自定义 context 的方式。
288
+
289
+ ### 步骤 2:可选编辑态 Node
113
290
 
114
- <RichRenderer value={content} rendererConfig={customConfig} />
291
+ 如果编辑态需要重 UI,可以 `class PollEditNode extends PollNode` 并覆写 `decorate()`。
292
+
293
+ 还可以定义 slash 菜单项:
294
+
295
+ ```ts
296
+ static slashMenuItems = [
297
+ {
298
+ title: 'Poll',
299
+ description: 'Insert a poll block',
300
+ section: 'MEDIA',
301
+ onSelect: (editor) => { ... },
302
+ },
303
+ ]
115
304
  ```
116
305
 
117
- ## 自定义节点
306
+ ### 步骤 3:注册到编辑器与渲染器
118
307
 
119
308
  ```tsx
120
309
  import { RichEditor } from '@haklex/rich-editor'
121
- import { MyCustomNode } from './nodes'
310
+ import { RichRenderer } from '@haklex/rich-renderer'
311
+
312
+ <RichEditor extraNodes={[PollEditNode]}>{/* <PollPlugin /> */}</RichEditor>
122
313
 
123
- <RichEditor extraNodes={[MyCustomNode]} />
314
+ <RichRenderer value={value} extraNodes={[PollNode]} />
124
315
  ```
125
316
 
126
- ## 主要导出
317
+ ### 步骤 4:插件化插入命令(推荐)
127
318
 
128
319
  ```ts
129
- // 组件
130
- export { RichEditor } from './components/RichEditor'
131
- export { RichRenderer } from './components/RichRenderer'
132
-
133
- // 节点配置
134
- export { allNodes, builtinNodes, customNodes } from './config'
135
- export { allEditNodes, customEditNodes } from './config-edit'
136
-
137
- // Context
138
- export { ColorSchemeProvider, useColorScheme } from './context/ColorSchemeContext'
139
- export { useRendererConfig } from './context/RendererConfigContext'
140
-
141
- // 类型
142
- export type { RichEditorProps, RichRendererProps, RichEditorVariant } from './types'
143
- export type { RendererConfig } from './types/renderer-config'
144
- export type { SlashMenuItemConfig } from './types/slash-menu'
145
-
146
- // 工具函数
147
- export { createRendererDecoration } from './components/RendererWrapper'
148
- export { getVariantClass } from './components/utils'
320
+ import { createCommand } from 'lexical'
321
+
322
+ export const INSERT_POLL_COMMAND = createCommand<{ question: string }>()
149
323
  ```
150
324
 
151
- ## 依赖
325
+ 在插件中注册命令,所有 UI(按钮、slash、快捷键)统一 dispatch 该命令。
152
326
 
153
- ```json
154
- {
155
- "lexical": "^0.40.0",
156
- "@lexical/react": "^0.40.0",
157
- "react": ">=19",
158
- "react-dom": ">=19"
159
- }
160
- ```
327
+ ## 与聚合包配合
328
+
329
+ - 只想增强只读渲染:`@haklex/rich-renderers`
330
+ - 要增强编辑态:`@haklex/rich-renderers-edit`
331
+ - 想直接开箱即用:`@haklex/rich-kit-shiro`
161
332
 
162
333
  ## License
163
334