gzkx-editor 0.0.0 → 0.0.2

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,80 +1,789 @@
1
- **This repository has moved to https://code.haverbeke.berlin/prosemirror/prosemirror**
2
-
3
- # prosemirror
4
-
5
- [ [**WEBSITE**](https://prosemirror.net) | [**ISSUES**](https://code.haverbeke.berlin/prosemirror/prosemirror/issues) | [**FORUM**](https://discuss.prosemirror.net) ]
6
-
7
- ProseMirror is a well-behaved rich semantic content editor based on
8
- contentEditable, with support for collaborative editing and custom
9
- document schemas.
10
-
11
- The ProseMirror library consists of a number of separate
12
- [modules](https://code.haverbeke.berlin/prosemirror/). This repository just
13
- serves as a central issue tracker, and holds a script to help easily
14
- check out all the core modules for development.
15
-
16
- The [project page](https://prosemirror.net) has more information, a
17
- number of [examples](https://prosemirror.net/examples/) and the
18
- [documentation](https://prosemirror.net/docs/).
19
-
20
- This code is released under an
21
- [MIT license](https://code.haverbeke.berlin/prosemirror/prosemirror/src/branch/main/LICENSE).
22
- There's a [forum](http://discuss.prosemirror.net) for general
23
- discussion and support requests, and the
24
- [bug tracker](https://code.haverbeke.berlin/prosemirror/prosemirror/issues)
25
- is the place to report issues.
26
-
27
- **STOP READING HERE IF YOU'RE SIMPLY _USING_ PROSEMIRROR. YOU CAN
28
- INSTALL THE SEPARATE [NPM
29
- MODULES](https://www.npmjs.com/search?q=prosemirror-) FOR THAT. THE
30
- INSTRUCTIONS BELOW ONLY APPLY WHEN _DEVELOPING_ PROSEMIRROR!**
31
-
32
- ## Setting up a dev environment
33
-
34
- Clone this repository, and make sure you have
35
- [node](https://nodejs.org/en/) installed.
36
- Next, from the cloned directory run:
37
-
38
- bin/pm install
39
-
40
- This will fetch the submodules, install their dependencies, and build
41
- them.
42
-
43
- The `bin/pm` script in this repository provides functionality for
44
- working with the repositories:
45
-
46
- * `bin/pm build` rebuilds all the modules
47
-
48
- * `bin/pm watch` sets up a process that automatically rebuilds the
49
- modules when they change
50
-
51
- * `bin/pm status` prints the git status of all submodules
52
-
53
- * `bin/pm commit <args>` runs `git commit` with the given arguments
54
- in all submodules that have pending changes
55
-
56
- * `bin/pm test` runs the (non-browser) tests in all modules
57
-
58
- * `bin/pm push` runs `git push` in all modules
59
-
60
- * `bin/pm grep <pattern>` greps through the source code for the
61
- modules for the given pattern
62
-
63
- * `bin/pm dev-start` starts a server that rebuilds the packages
64
- whenever their sources change, and exposes the demo (`demo/*`)
65
- under a webserver on port 8080
66
-
67
- (Functionality for managing releases will be added in the future.)
68
-
69
- ## Community
70
-
71
- Development of ProseMirror happens in the various repositories exposed
72
- under the [ProseMirror](https://code.haverbeke.berlin/prosemirror/) organization
73
- on code.haverbeke.berlin. Bugs for core packages are tracked in the [bug
74
- tracker](https://code.haverbeke.berlin/prosemirror/prosemirror/issues) for the
75
- meta repository.
76
-
77
- We aim to be an inclusive, welcoming community. To make that explicit,
78
- we have a [code of
79
- conduct](http://contributor-covenant.org/version/1/1/0/) that applies
80
- to communication around the project.
1
+ # GZKX Editor 使用指南
2
+
3
+ ## 简介
4
+
5
+ GZKX Editor 是一个基于 ProseMirror 的富文本编辑器,提供了模块化的编辑组件,支持协同编辑、自定义文档结构等功能。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm install gzkx-editor-model gzkx-editor-state gzkx-editor-view gzkx-editor-commands gzkx-editor-schema-basic gzkx-editor-schema-list gzkx-editor-example-setup
11
+ ```
12
+
13
+ 或使用完整套装:
14
+
15
+ ```bash
16
+ npm install gzkx-editor-example-setup
17
+ ```
18
+
19
+ ## 核心模块
20
+
21
+ ### 1. gzkx-editor-model
22
+ 文档模型层,定义了文档的数据结构。
23
+
24
+ **主要导出:**
25
+ - `Node` - 文档节点
26
+ - `Fragment` - 文档片段
27
+ - `Slice` - 文档切片(用于跨编辑器传递内容)
28
+ - `Mark` - 标记(如加粗、斜体等)
29
+ - `Schema` - 文档模式,定义可用的节点和标记类型
30
+ - `DOMParser` - DOM 解析为文档
31
+ - `DOMSerializer` - 将文档序列化为 DOM
32
+
33
+ ### 2. gzkx-editor-state
34
+ 编辑器状态管理层。
35
+
36
+ **主要导出:**
37
+ - `EditorState` - 编辑器状态
38
+ - `Transaction` - 状态变更事务
39
+ - `Plugin` - 编辑器插件
40
+
41
+ ### 3. gzkx-editor-view
42
+ 编辑器视图层,负责渲染和交互。
43
+
44
+ **主要导出:**
45
+ - `EditorView` - 编辑器视图组件
46
+
47
+ ### 4. gzkx-editor-commands
48
+ 编辑命令集。
49
+
50
+ **主要导出:**
51
+ - `toggleMark` - 切换标记
52
+ - `setBlockType` - 设置块类型
53
+ - `wrapIn` - 包裹节点
54
+ - `baseKeymap` - 基础快捷键绑定
55
+
56
+ ### 5. gzkx-editor-schema-basic
57
+ 基础文档模式,提供常用节点和标记。
58
+
59
+ **包含:**
60
+ - 段落 (paragraph)
61
+ - 标题 (heading, 1-6级)
62
+ - 代码块 (code_block)
63
+ - 引用 (blockquote)
64
+ - 水平线 (horizontal_rule)
65
+ - 图片 (image)
66
+ - 链接 (link)
67
+ - 加粗 (strong)
68
+ - 斜体 (em)
69
+ - 代码 (code)
70
+
71
+ ### 6. gzkx-editor-schema-list
72
+ 列表相关节点和命令。
73
+
74
+ **包含:**
75
+ - 有序列表 (ordered_list)
76
+ - 无序列表 (bullet_list)
77
+ - 列表项 (list_item)
78
+
79
+ ## 快速开始
80
+
81
+ ### 方式一:使用 exampleSetup(推荐)
82
+
83
+ ```javascript
84
+ import { Schema, DOMParser } from 'gzkx-editor-model';
85
+ import { EditorView } from 'gzkx-editor-view';
86
+ import { EditorState } from 'gzkx-editor-state';
87
+ import { schema } from 'gzkx-editor-schema-basic';
88
+ import { addListNodes } from 'gzkx-editor-schema-list';
89
+ import { exampleSetup } from 'gzkx-editor-example-setup';
90
+
91
+ // 创建带列表支持的模式
92
+ const demoSchema = new Schema({
93
+ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
94
+ marks: schema.spec.marks
95
+ });
96
+
97
+ // 创建初始状态
98
+ const state = EditorState.create({
99
+ doc: DOMParser.fromSchema(demoSchema).parse(document.querySelector("#content")),
100
+ plugins: exampleSetup({ schema: demoSchema })
101
+ });
102
+
103
+ // 创建编辑器视图
104
+ const view = new EditorView(document.querySelector(".editor"), {
105
+ state
106
+ });
107
+ ```
108
+
109
+ ### 方式二:手动配置
110
+
111
+ ```javascript
112
+ import { keymap } from 'gzkx-editor-keymap';
113
+ import { history } from 'gzkx-editor-history';
114
+ import { dropCursor } from 'gzkx-editor-dropcursor';
115
+ import { gapCursor } from 'gzkx-editor-gapcursor';
116
+ import { baseKeymap, toggleMark, setBlockType } from 'gzkx-editor-commands';
117
+ import { buildInputRules } from 'gzkx-editor-inputrules';
118
+ import { EditorView } from 'gzkx-editor-view';
119
+ import { EditorState } from 'gzkx-editor-state';
120
+ import { Schema, DOMParser } from 'gzkx-editor-model';
121
+
122
+ const mySchema = new Schema({
123
+ nodes: {
124
+ doc: { content: 'block+' },
125
+ paragraph: { group: 'block', parseDOM: [{ tag: 'p' }] },
126
+ text: { group: 'inline' },
127
+ strong: { parseDOM: [{ tag: 'strong' }, { tag: 'b' }] },
128
+ em: { parseDOM: [{ tag: 'em' }, { tag: 'i' }] }
129
+ }
130
+ });
131
+
132
+ const state = EditorState.create({
133
+ doc: DOMParser.fromSchema(mySchema).parse(document.getElementById('content')),
134
+ plugins: [
135
+ buildInputRules(mySchema),
136
+ keymap(buildKeymap(mySchema)),
137
+ keymap(baseKeymap),
138
+ dropCursor(),
139
+ gapCursor(),
140
+ history()
141
+ ]
142
+ });
143
+
144
+ const view = new EditorView(document.querySelector('.editor'), { state });
145
+ ```
146
+
147
+ ## 常用功能
148
+
149
+ ### 获取编辑器内容
150
+
151
+ ```javascript
152
+ // 获取 JSON 格式的文档
153
+ const doc = view.state.doc.toJSON();
154
+
155
+ // 获取 HTML 字符串
156
+ import { DOMSerializer } from 'gzkx-editor-model';
157
+ const div = document.createElement('div');
158
+ const fragment = DOMSerializer.fromSchema(view.state.schema).serializeFragment(view.state.doc.content);
159
+ div.appendChild(fragment);
160
+ const html = div.innerHTML;
161
+ ```
162
+
163
+ ### 修改编辑器内容
164
+
165
+ ```javascript
166
+ // 通过事务修改
167
+ const tr = view.state.tr;
168
+ tr.insertText('Hello, GZKX Editor!', 10);
169
+ view.dispatch(tr);
170
+ ```
171
+
172
+ ### 监听变化
173
+
174
+ ```javascript
175
+ const view = new EditorView(document.querySelector('.editor'), {
176
+ state: initialState,
177
+ dispatchTransaction(transaction) {
178
+ const newState = this.state.apply(transaction);
179
+ this.updateState(newState);
180
+
181
+ // 检测文档变化
182
+ if (transaction.docChanged) {
183
+ console.log('文档已更新');
184
+ }
185
+ }
186
+ });
187
+ ```
188
+
189
+ ### 快捷键
190
+
191
+ | 快捷键 | 功能 |
192
+ |--------|------|
193
+ | Ctrl+B / Cmd+B | 加粗 |
194
+ | Ctrl+I / Cmd+I | 斜体 |
195
+ | Ctrl+Shift+X | 行内代码 |
196
+ | Ctrl+Z | 撤销 |
197
+ | Ctrl+Shift+Z | 重做 |
198
+ | Tab | 缩进 |
199
+ | Shift+Tab | 取消缩进 |
200
+
201
+ ### 自定义菜单
202
+
203
+ ```javascript
204
+ import { buildMenuItems } from 'gzkx-editor-example-setup';
205
+
206
+ const menuItems = buildMenuItems(schema);
207
+
208
+ // 自定义菜单内容
209
+ const plugins = exampleSetup({
210
+ schema,
211
+ menuContent: menuItems.fullMenu
212
+ });
213
+ ```
214
+
215
+ ### 自定义样式
216
+
217
+ 引入 CSS:
218
+
219
+ ```html
220
+ <link rel="stylesheet" href="gzkx-editor-view/style/prosemirror.css">
221
+ <link rel="stylesheet" href="gzkx-editor-menu/style/menu.css">
222
+ <link rel="stylesheet" href="gzkx-editor-gapcursor/style/gapcursor.css">
223
+ ```
224
+
225
+ ## React 使用方法
226
+
227
+ GZKX Editor 可以通过 React 封装组件来使用,下面介绍两种封装方式。
228
+
229
+ ### 方式一:使用 ref 和 useEffect(类组件风格)
230
+
231
+ ```jsx
232
+ import React, { useEffect, useRef, useState } from 'react';
233
+ import { Schema, DOMParser, DOMSerializer } from 'gzkx-editor-model';
234
+ import { EditorView } from 'gzkx-editor-view';
235
+ import { EditorState } from 'gzkx-editor-state';
236
+ import { schema } from 'gzkx-editor-schema-basic';
237
+ import { addListNodes } from 'gzkx-editor-schema-list';
238
+ import { exampleSetup } from 'gzkx-editor-example-setup';
239
+
240
+ import 'gzkx-editor-view/style/prosemirror.css';
241
+ import 'gzkx-editor-menu/style/menu.css';
242
+
243
+ const Editor = ({ initialContent = '', onChange }) => {
244
+ const editorRef = useRef(null);
245
+ const viewRef = useRef(null);
246
+
247
+ useEffect(() => {
248
+ if (!editorRef.current) return;
249
+
250
+ // 创建 Schema
251
+ const mySchema = new Schema({
252
+ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
253
+ marks: schema.spec.marks
254
+ });
255
+
256
+ // 解析初始内容
257
+ const content = document.createElement('div');
258
+ content.innerHTML = initialContent || '<p></p>';
259
+
260
+ // 创建初始状态
261
+ const state = EditorState.create({
262
+ doc: DOMParser.fromSchema(mySchema).parse(content),
263
+ plugins: exampleSetup({ schema: mySchema })
264
+ });
265
+
266
+ // 创建编辑器视图
267
+ const view = new EditorView(editorRef.current, {
268
+ state,
269
+ dispatchTransaction(transaction) {
270
+ const newState = view.state.apply(transaction);
271
+ view.updateState(newState);
272
+
273
+ // 触发内容变化回调
274
+ if (transaction.docChanged && onChange) {
275
+ const serializer = DOMSerializer.fromSchema(mySchema);
276
+ const div = document.createElement('div');
277
+ serializer.serializeFragment(newState.doc.content, { document: div });
278
+ onChange(div.innerHTML, newState.doc.toJSON());
279
+ }
280
+ }
281
+ });
282
+
283
+ viewRef.current = view;
284
+
285
+ // 清理函数
286
+ return () => {
287
+ view.destroy();
288
+ };
289
+ }, []);
290
+
291
+ return <div ref={editorRef} className="gzkx-editor" />;
292
+ };
293
+
294
+ // 使用示例
295
+ const App = () => {
296
+ const handleChange = (html, json) => {
297
+ console.log('HTML:', html);
298
+ console.log('JSON:', json);
299
+ };
300
+
301
+ return (
302
+ <div>
303
+ <h1>我的编辑器</h1>
304
+ <Editor
305
+ initialContent="<p>初始内容</p>"
306
+ onChange={handleChange}
307
+ />
308
+ </div>
309
+ );
310
+ };
311
+
312
+ export default App;
313
+ ```
314
+
315
+ ### 方式二:封装为 React 组件(Hooks 版本)
316
+
317
+ ```jsx
318
+ import React, { useEffect, useRef, useCallback, useMemo } from 'react';
319
+ import { Schema, DOMParser, DOMSerializer } from 'gzkx-editor-model';
320
+ import { EditorView } from 'gzkx-editor-view';
321
+ import { EditorState } from 'gzkx-editor-state';
322
+ import { schema } from 'gzkx-editor-schema-basic';
323
+ import { addListNodes } from 'gzkx-editor-schema-list';
324
+ import { exampleSetup } from 'gzkx-editor-example-setup';
325
+
326
+ import 'gzkx-editor-view/style/prosemirror.css';
327
+ import 'gzkx-editor-menu/style/menu.css';
328
+
329
+ const GZKXEditor = ({
330
+ value = '',
331
+ onChange,
332
+ placeholder = '请输入内容...',
333
+ readOnly = false,
334
+ className = ''
335
+ }) => {
336
+ const editorRef = useRef(null);
337
+ const viewRef = useRef(null);
338
+
339
+ // 创建 Schema
340
+ const editorSchema = useMemo(() => {
341
+ return new Schema({
342
+ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
343
+ marks: schema.spec.marks
344
+ });
345
+ }, []);
346
+
347
+ // 获取 HTML 内容
348
+ const getContentHTML = useCallback((doc) => {
349
+ const div = document.createElement('div');
350
+ const serializer = DOMSerializer.fromSchema(editorSchema);
351
+ serializer.serializeFragment(doc.content, { document: div });
352
+ return div.innerHTML;
353
+ }, [editorSchema]);
354
+
355
+ // 获取 JSON 内容
356
+ const getContentJSON = useCallback((doc) => {
357
+ return doc.toJSON();
358
+ }, []);
359
+
360
+ useEffect(() => {
361
+ if (!editorRef.current) return;
362
+
363
+ // 解析初始内容
364
+ const content = document.createElement('div');
365
+ content.innerHTML = value || '<p></p>';
366
+
367
+ // 创建初始状态
368
+ const state = EditorState.create({
369
+ doc: DOMParser.fromSchema(editorSchema).parse(content),
370
+ plugins: exampleSetup({ schema: editorSchema })
371
+ });
372
+
373
+ // 创建编辑器视图
374
+ const view = new EditorView(editorRef.current, {
375
+ state,
376
+ editable() {
377
+ return !readOnly;
378
+ },
379
+ dispatchTransaction(transaction) {
380
+ const newState = view.state.apply(transaction);
381
+ view.updateState(newState);
382
+
383
+ if (transaction.docChanged && onChange) {
384
+ onChange({
385
+ html: getContentHTML(newState.doc),
386
+ json: getContentJSON(newState.doc)
387
+ });
388
+ }
389
+ }
390
+ });
391
+
392
+ viewRef.current = view;
393
+
394
+ return () => {
395
+ view.destroy();
396
+ };
397
+ }, [editorSchema]);
398
+
399
+ // 更新内容(外部控制)
400
+ useEffect(() => {
401
+ if (!viewRef.current) return;
402
+
403
+ const currentHTML = getContentHTML(viewRef.current.state.doc);
404
+ if (value !== currentHTML && value !== undefined) {
405
+ const content = document.createElement('div');
406
+ content.innerHTML = value;
407
+ const newDoc = DOMParser.fromSchema(editorSchema).parse(content);
408
+ const tr = viewRef.current.state.tr.replaceWith(0, viewRef.current.state.doc.content.size, newDoc.content);
409
+ viewRef.current.dispatch(tr);
410
+ }
411
+ }, [value, editorSchema, getContentHTML]);
412
+
413
+ return (
414
+ <div
415
+ ref={editorRef}
416
+ className={`gzkx-editor ${className}`}
417
+ data-placeholder={placeholder}
418
+ />
419
+ );
420
+ };
421
+
422
+ export default GZKXEditor;
423
+ ```
424
+
425
+ ### 使用示例
426
+
427
+ ```jsx
428
+ import React, { useState } from 'react';
429
+ import GZKXEditor from './GZKXEditor';
430
+
431
+ const App = () => {
432
+ const [content, setContent] = useState({
433
+ html: '<p>初始内容</p>',
434
+ json: null
435
+ });
436
+
437
+ return (
438
+ <div className="app">
439
+ <h1>GZKX Editor in React</h1>
440
+
441
+ <GZKXEditor
442
+ value={content.html}
443
+ onChange={setContent}
444
+ placeholder="输入文章内容..."
445
+ />
446
+
447
+ <div className="preview">
448
+ <h2>预览</h2>
449
+ <div dangerouslySetInnerHTML={{ __html: content.html }} />
450
+ </div>
451
+
452
+ <div className="json-view">
453
+ <h2>JSON 数据</h2>
454
+ <pre>{JSON.stringify(content.json, null, 2)}</pre>
455
+ </div>
456
+ </div>
457
+ );
458
+ };
459
+
460
+ export default App;
461
+ ```
462
+
463
+ ### 样式调整
464
+
465
+ 添加自定义样式:
466
+
467
+ ```css
468
+ /* Editor.css */
469
+ .gzkx-editor {
470
+ border: 1px solid #d1d5db;
471
+ border-radius: 8px;
472
+ min-height: 300px;
473
+ padding: 16px;
474
+ }
475
+
476
+ .gzkx-editor:focus {
477
+ outline: none;
478
+ border-color: #3b82f6;
479
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
480
+ }
481
+
482
+ /* 占位符样式 */
483
+ .gzkx-editor:empty::before {
484
+ content: attr(data-placeholder);
485
+ color: #9ca3af;
486
+ pointer-events: none;
487
+ }
488
+
489
+ /* ProseMirror 样式覆盖 */
490
+ .ProseMirror {
491
+ outline: none;
492
+ min-height: 200px;
493
+ }
494
+
495
+ .ProseMirror p.is-empty::before {
496
+ content: attr(data-placeholder);
497
+ color: #9ca3af;
498
+ float: left;
499
+ height: 0;
500
+ pointer-events: none;
501
+ }
502
+ ```
503
+
504
+ ### 完整 React 项目示例
505
+
506
+ ```jsx
507
+ // EditorComponent.jsx
508
+ import React, { useEffect, useRef, useState } from 'react';
509
+ import {
510
+ Schema,
511
+ DOMParser,
512
+ DOMSerializer
513
+ } from 'gzkx-editor-model';
514
+ import { EditorView } from 'gzkx-editor-view';
515
+ import { EditorState } from 'gzkx-editor-state';
516
+ import { schema } from 'gzkx-editor-schema-basic';
517
+ import { addListNodes } from 'gzkx-editor-schema-list';
518
+ import { exampleSetup } from 'gzkx-editor-example-setup';
519
+
520
+ import 'gzkx-editor-view/style/prosemirror.css';
521
+ import 'gzkx-editor-menu/style/menu.css';
522
+ import './EditorComponent.css';
523
+
524
+ const EditorComponent = ({
525
+ initialValue = '<p></p>',
526
+ onChange,
527
+ editable = true
528
+ }) => {
529
+ const containerRef = useRef(null);
530
+ const viewRef = useRef(null);
531
+ const schemaRef = useRef(null);
532
+
533
+ // 初始化 Schema
534
+ if (!schemaRef.current) {
535
+ schemaRef.current = new Schema({
536
+ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
537
+ marks: schema.spec.marks
538
+ });
539
+ }
540
+
541
+ useEffect(() => {
542
+ if (!containerRef.current) return;
543
+
544
+ // 解析初始内容
545
+ const tempDiv = document.createElement('div');
546
+ tempDiv.innerHTML = initialValue;
547
+
548
+ // 创建状态
549
+ const state = EditorState.create({
550
+ doc: DOMParser.fromSchema(schemaRef.current).parse(tempDiv),
551
+ plugins: exampleSetup({ schema: schemaRef.current })
552
+ });
553
+
554
+ // 创建视图
555
+ viewRef.current = new EditorView(containerRef.current, {
556
+ state,
557
+ editable() {
558
+ return editable;
559
+ },
560
+ dispatchTransaction(transaction) {
561
+ const newState = viewRef.current.state.apply(transaction);
562
+ viewRef.current.updateState(newState);
563
+
564
+ if (transaction.docChanged && onChange) {
565
+ const div = document.createElement('div');
566
+ const serializer = DOMSerializer.fromSchema(schemaRef.current);
567
+ serializer.serializeFragment(newState.doc.content, {
568
+ document: div
569
+ });
570
+ onChange({
571
+ html: div.innerHTML,
572
+ json: newState.doc.toJSON()
573
+ });
574
+ }
575
+ }
576
+ });
577
+
578
+ return () => {
579
+ if (viewRef.current) {
580
+ viewRef.current.destroy();
581
+ }
582
+ };
583
+ }, [initialValue, editable]);
584
+
585
+ return <div ref={containerRef} className="gzkx-editor-container" />;
586
+ };
587
+
588
+ export default EditorComponent;
589
+ ```
590
+
591
+ ### Next.js中使用
592
+
593
+ 在 Next.js 中使用时,需要注意动态导入避免 SSR 问题:
594
+
595
+ ```jsx
596
+ // components/Editor.jsx
597
+ import dynamic from 'next/dynamic';
598
+
599
+ const EditorComponent = dynamic(() => import('./EditorComponent'), {
600
+ ssr: false,
601
+ loading: () => <div>加载中...</div>
602
+ });
603
+
604
+ export default function Editor({ initialValue, onChange }) {
605
+ return (
606
+ <EditorComponent
607
+ initialValue={initialValue}
608
+ onChange={onChange}
609
+ />
610
+ );
611
+ }
612
+ ```
613
+
614
+ ### 在 TypeScript 中使用
615
+
616
+ ```tsx
617
+ // types/editor.ts
618
+ import { Node as ProseMirrorNode, Schema } from 'gzkx-editor-model';
619
+
620
+ export interface EditorContent {
621
+ html: string;
622
+ json: ProseMirrorNode;
623
+ }
624
+
625
+ export interface EditorProps {
626
+ value?: string;
627
+ onChange?: (content: EditorContent) => void;
628
+ placeholder?: string;
629
+ readOnly?: boolean;
630
+ className?: string;
631
+ }
632
+
633
+ // Editor.tsx
634
+ import React, { useEffect, useRef } from 'react';
635
+ import { Schema, DOMParser, DOMSerializer, Node } from 'gzkx-editor-model';
636
+ import { EditorView } from 'gzkx-editor-view';
637
+ import { EditorState } from 'gzkx-editor-state';
638
+ import { schema } from 'gzkx-editor-schema-basic';
639
+ import { addListNodes } from 'gzkx-editor-schema-list';
640
+ import { exampleSetup } from 'gzkx-editor-example-setup';
641
+
642
+ import { EditorProps, EditorContent } from '../types/editor';
643
+
644
+ export const GZKXEditor: React.FC<EditorProps> = ({
645
+ value = '<p></p>',
646
+ onChange,
647
+ placeholder = '请输入内容...',
648
+ readOnly = false,
649
+ className = ''
650
+ }) => {
651
+ const editorRef = useRef<HTMLDivElement>(null);
652
+ const viewRef = useRef<EditorView | null>(null);
653
+
654
+ const editorSchema = React.useMemo(() => {
655
+ return new Schema({
656
+ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
657
+ marks: schema.spec.marks
658
+ });
659
+ }, []);
660
+
661
+ useEffect(() => {
662
+ if (!editorRef.current) return;
663
+
664
+ const content = document.createElement('div');
665
+ content.innerHTML = value;
666
+
667
+ const state = EditorState.create({
668
+ doc: DOMParser.fromSchema(editorSchema).parse(content),
669
+ plugins: exampleSetup({ schema: editorSchema })
670
+ });
671
+
672
+ viewRef.current = new EditorView(editorRef.current, {
673
+ state,
674
+ editable() {
675
+ return !readOnly;
676
+ },
677
+ dispatchTransaction(transaction) {
678
+ const newState = viewRef.current!.state.apply(transaction);
679
+ viewRef.current!.updateState(newState);
680
+
681
+ if (transaction.docChanged && onChange) {
682
+ const div = document.createElement('div');
683
+ const serializer = DOMSerializer.fromSchema(editorSchema);
684
+ serializer.serializeFragment(newState.doc.content, {
685
+ document: div
686
+ });
687
+ onChange({
688
+ html: div.innerHTML,
689
+ json: newState.doc.toJSON()
690
+ });
691
+ }
692
+ }
693
+ });
694
+
695
+ return () => {
696
+ viewRef.current?.destroy();
697
+ };
698
+ }, [editorSchema, value, readOnly]);
699
+
700
+ return (
701
+ <div
702
+ ref={editorRef}
703
+ className={`gzkx-editor ${className}`}
704
+ data-placeholder={placeholder}
705
+ />
706
+ );
707
+ };
708
+ ```
709
+
710
+ ## 完整示例(原生 JS)
711
+
712
+ ```html
713
+ <!DOCTYPE html>
714
+ <html lang="zh-CN">
715
+ <head>
716
+ <meta charset="UTF-8">
717
+ <title>GZKX Editor 示例</title>
718
+ <link rel="stylesheet" href="node_modules/gzkx-editor-view/style/prosemirror.css">
719
+ <link rel="stylesheet" href="node_modules/gzkx-editor-menu/style/menu.css">
720
+ <style>
721
+ .editor { border: 1px solid #ccc; min-height: 300px; }
722
+ </style>
723
+ </head>
724
+ <body>
725
+ <div id="editor" class="editor"></div>
726
+
727
+ <script type="module">
728
+ import { Schema, DOMParser } from 'gzkx-editor-model';
729
+ import { EditorView } from 'gzkx-editor-view';
730
+ import { EditorState } from 'gzkx-editor-state';
731
+ import { schema } from 'gzkx-editor-schema-basic';
732
+ import { addListNodes } from 'gzkx-editor-schema-list';
733
+ import { exampleSetup } from 'gzkx-editor-example-setup';
734
+
735
+ const mySchema = new Schema({
736
+ nodes: addListNodes(schema.spec.nodes, "paragraph block*", "block"),
737
+ marks: schema.spec.marks
738
+ });
739
+
740
+ const content = document.createElement('div');
741
+ content.innerHTML = '<p>欢迎使用 GZKX Editor!</p>';
742
+
743
+ const state = EditorState.create({
744
+ doc: DOMParser.fromSchema(mySchema).parse(content),
745
+ plugins: exampleSetup({ schema: mySchema })
746
+ });
747
+
748
+ const view = new EditorView(document.getElementById('editor'), { state });
749
+
750
+ // 获取内容
751
+ function getContent() {
752
+ const div = document.createElement('div');
753
+ const serializer = DOMSerializer.fromSchema(mySchema);
754
+ serializer.serializeFragment(view.state.doc.content, { document: div });
755
+ return div.innerHTML;
756
+ }
757
+ </script>
758
+ </body>
759
+ </html>
760
+ ```
761
+
762
+ ## 模块列表
763
+
764
+ | 包名 | 说明 |
765
+ |------|------|
766
+ | gzkx-editor-model | 文档模型 |
767
+ | gzkx-editor-state | 状态管理 |
768
+ | gzkx-editor-view | 视图渲染 |
769
+ | gzkx-editor-commands | 编辑命令 |
770
+ | gzkx-editor-keymap | 快捷键绑定 |
771
+ | gzkx-editor-inputrules | 输入规则 |
772
+ | gzkx-editor-history | 历史记录(撤销/重做) |
773
+ | gzkx-editor-collab | 协同编辑 |
774
+ | gzkx-editor-gapcursor | 空隙光标 |
775
+ | gzkx-editor-schema-basic | 基础模式 |
776
+ | gzkx-editor-schema-list | 列表模式 |
777
+ | gzkx-editor-menu | 菜单组件 |
778
+ | gzkx-editor-example-setup | 快速设置 |
779
+ | gzkx-editor-markdown | Markdown 转换 |
780
+ | gzkx-editor-dropcursor | 拖拽光标 |
781
+ | gzkx-editor-search | 搜索功能 |
782
+ | gzkx-editor-changeset | 变更集 |
783
+
784
+ ## 注意事项
785
+
786
+ 1. **事务处理**:每次内容修改都需要通过 `dispatch` 方法提交事务
787
+ 2. **Schema 定义**:确保所有节点和标记在 Schema 中正确定义
788
+ 3. **CSS 样式**:需要引入对应的 CSS 文件才能正常显示
789
+ 4. **依赖顺序**:部分模块依赖其他模块,安装时注意包之间的依赖关系
package/core/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @gzkx-editor/core
2
+
3
+ GZKX Editor — all ProseMirror modules bundled into a single package.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @gzkx-editor/core
9
+ ```
10
+
11
+ Requires `orderedmap` as a peer dependency:
12
+
13
+ ```bash
14
+ npm install orderedmap
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```js
20
+ import { EditorState } from '@gzkx-editor/core';
21
+ import { EditorView } from '@gzkx-editor/core';
22
+ import { Schema, DOMParser } from '@gzkx-editor/core';
23
+ import { exampleSetup } from '@gzkx-editor/core';
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### Creating an Editor
29
+
30
+ ```js
31
+ import { EditorState } from '@gzkx-editor/core';
32
+ import { EditorView } from '@gzkx-editor/core';
33
+ import { schemaBasic } from '@gzkx-editor/core';
34
+ import { exampleSetupProvider } from '@gzkx-editor/core';
35
+
36
+ const state = EditorState.create({
37
+ doc: DOMParser.fromSchema(schemaBasic).parse('<p>Hello world</p>'),
38
+ plugins: exampleSetupProvider({ schema: schemaBasic })
39
+ });
40
+
41
+ const view = new EditorView(document.body, { state });
42
+ ```
43
+
44
+ ### Schema
45
+
46
+ ```js
47
+ import { Schema } from '@gzkx-editor/core';
48
+
49
+ // Define a custom schema
50
+ const mySchema = new Schema({
51
+ nodes: {
52
+ doc: { content: 'block+' },
53
+ paragraph: { group: 'block', content: 'inline*' },
54
+ text: { group: 'inline' },
55
+ },
56
+ marks: {
57
+ strong: {},
58
+ em: {},
59
+ }
60
+ });
61
+ ```
62
+
63
+ ### Editor State & Transactions
64
+
65
+ ```js
66
+ import { EditorState } from '@gzkx-editor/core';
67
+ import { Selection, TextSelection } from '@gzkx-editor/core';
68
+ import { Transaction } from '@gzkx-editor/core';
69
+
70
+ // Create state
71
+ const state = EditorState.create({ schema });
72
+
73
+ // Dispatch a transaction
74
+ view.dispatch(state.tr.insertText('Hello'));
75
+ ```
76
+
77
+ ### Commands
78
+
79
+ ```js
80
+ import { deleteSelection, joinBackward, wrapIn } from '@gzkx-editor/core';
81
+
82
+ // Run a command
83
+ if (deleteSelection(state, dispatch)) {
84
+ // ...
85
+ }
86
+ ```
87
+
88
+ ### Key Modules
89
+
90
+ This package includes all the following modules:
91
+
92
+ | Module | Exports |
93
+ |--------|---------|
94
+ | **model** | `Node`, `Fragment`, `Slice`, `Mark`, `Schema`, `DOMParser`, `DOMSerializer` |
95
+ | **transform** | `Step`, `Transform`, `ReplaceStep`, `AddMarkStep`, `ReplaceAroundStep` |
96
+ | **state** | `EditorState`, `Transaction`, `Selection`, `Plugin` |
97
+ | **view** | `EditorView`, `Decoration`, `DecorationSet` |
98
+ | **commands** | `deleteSelection`, `joinBackward`, `wrapIn`, `setBlockType`, `toggleMark` |
99
+ | **keymap** | `keymap`, `baseKeymap` |
100
+ | **inputrules** | `inputRules`, `smartQuotes`, `wrappingInputRule` |
101
+ | **history** | `history`, `undo`, `redo`, `historyKeymap` |
102
+ | **dropcursor** | `dropCursor` |
103
+ | **gapcursor** | `gapCursor` |
104
+ | **schema-basic** | `schemaBasic`, `listSchema` |
105
+ | **schema-list** | `orderedList`, `bulletList`, `listItem` |
106
+ | **menu** | `menuBar`, `dropdown`, `menuItem`, `icons` |
107
+ | **collab** | `collab`, `sendable`, `receive` |
108
+ | **example-setup** | `exampleSetup`, `buildMenuItems`, `buildKeymap`, `buildInputRules` |
109
+ | **markdown** | `parser`, `serializer`, `toMarkdown` |
110
+ | **search** | `searchKeymap`, `findDialog` |
111
+ | **changeset** | `Change`, `ChangeSet` |
112
+ | **test-builder** | `builder`, `doc`, `paragraph`, `text`, `blockquote` |
113
+
114
+ ## TypeScript
115
+
116
+ This package ships TypeScript declarations for all exports.
117
+
118
+ ```ts
119
+ import { Node, Schema, EditorState } from '@gzkx-editor/core';
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
package/core/package.json CHANGED
@@ -13,7 +13,8 @@
13
13
  "sideEffects": false,
14
14
  "license": "MIT",
15
15
  "files": [
16
- "dist/"
16
+ "dist/",
17
+ "README.md"
17
18
  ],
18
19
  "dependencies": {
19
20
  "orderedmap": "^2.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gzkx-editor",
3
- "version": "0.0.0",
3
+ "version": "0.0.2",
4
4
  "description": "Structured WYSIWYM editor",
5
5
  "license": "MIT",
6
6
  "maintainers": [
@@ -27,7 +27,6 @@
27
27
  "workspaces": [
28
28
  "*"
29
29
  ],
30
-
31
30
  "devDependencies": {
32
31
  "@rollup/plugin-typescript": "^12.3.0",
33
32
  "esbuild": "^0.28.0",
@@ -1,19 +0,0 @@
1
- export * from "./model/src/index.ts";
2
- export * from "./transform/src/index.ts";
3
- export * from "./state/src/index.ts";
4
- export * from "./view/src/index.ts";
5
- export * from "./commands/src/commands.ts";
6
- export * from "./keymap/src/keymap.ts";
7
- export * from "./inputrules/src/index.ts";
8
- export * from "./history/src/history.ts";
9
- export * from "./dropcursor/src/dropcursor.ts";
10
- export * from "./gapcursor/src/index.ts";
11
- export * from "./schema-basic/src/schema-basic.ts";
12
- export * from "./schema-list/src/schema-list.ts";
13
- export * from "./menu/src/index.ts";
14
- export * from "./collab/src/collab.ts";
15
- export * from "./example-setup/src/index.ts";
16
- export * from "./markdown/src/index.ts";
17
- export * from "./search/src/search.ts";
18
- export * from "./changeset/src/changeset.ts";
19
- export * from "./test-builder/src/index.ts";