@kaitify/core 0.0.1-beta.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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/examples/App.vue +342 -0
  4. package/examples/content.js +1 -0
  5. package/examples/main.ts +4 -0
  6. package/examples/test.html +23 -0
  7. package/lib/extensions/Extension.d.ts +172 -0
  8. package/lib/extensions/align/index.d.ts +10 -0
  9. package/lib/extensions/attachment/index.d.ts +29 -0
  10. package/lib/extensions/back-color/index.d.ts +9 -0
  11. package/lib/extensions/blockquote/index.d.ts +12 -0
  12. package/lib/extensions/bold/index.d.ts +9 -0
  13. package/lib/extensions/code/index.d.ts +12 -0
  14. package/lib/extensions/code-block/hljs.d.ts +12 -0
  15. package/lib/extensions/code-block/index.d.ts +15 -0
  16. package/lib/extensions/color/index.d.ts +9 -0
  17. package/lib/extensions/font-family/index.d.ts +9 -0
  18. package/lib/extensions/font-size/index.d.ts +9 -0
  19. package/lib/extensions/heading/index.d.ts +13 -0
  20. package/lib/extensions/history/index.d.ts +10 -0
  21. package/lib/extensions/horizontal/index.d.ts +7 -0
  22. package/lib/extensions/image/index.d.ts +26 -0
  23. package/lib/extensions/indent/index.d.ts +8 -0
  24. package/lib/extensions/index.d.ts +29 -0
  25. package/lib/extensions/italic/index.d.ts +9 -0
  26. package/lib/extensions/line-height/index.d.ts +9 -0
  27. package/lib/extensions/link/index.d.ts +27 -0
  28. package/lib/extensions/list/index.d.ts +18 -0
  29. package/lib/extensions/math/index.d.ts +11 -0
  30. package/lib/extensions/strikethrough/index.d.ts +9 -0
  31. package/lib/extensions/subscript/index.d.ts +9 -0
  32. package/lib/extensions/superscript/index.d.ts +9 -0
  33. package/lib/extensions/table/index.d.ts +21 -0
  34. package/lib/extensions/task/index.d.ts +12 -0
  35. package/lib/extensions/text/index.d.ts +14 -0
  36. package/lib/extensions/underline/index.d.ts +9 -0
  37. package/lib/extensions/video/index.d.ts +27 -0
  38. package/lib/index.d.ts +3 -0
  39. package/lib/kaitify-core.es.js +38337 -0
  40. package/lib/kaitify-core.umd.js +2 -0
  41. package/lib/model/Editor.d.ts +504 -0
  42. package/lib/model/History.d.ts +42 -0
  43. package/lib/model/KNode.d.ts +258 -0
  44. package/lib/model/Selection.d.ts +29 -0
  45. package/lib/model/config/dom-observe.d.ts +10 -0
  46. package/lib/model/config/event-handler.d.ts +33 -0
  47. package/lib/model/config/format-patch.d.ts +25 -0
  48. package/lib/model/config/format-rules.d.ts +37 -0
  49. package/lib/model/config/function.d.ts +84 -0
  50. package/lib/model/index.d.ts +6 -0
  51. package/lib/tools/index.d.ts +49 -0
  52. package/lib/view/index.d.ts +21 -0
  53. package/lib/view/js-render/dom-patch.d.ts +65 -0
  54. package/lib/view/js-render/index.d.ts +5 -0
  55. package/package.json +52 -0
  56. package/src/css/style.less +56 -0
  57. package/src/css/var.less +45 -0
  58. package/src/extensions/Extension.ts +200 -0
  59. package/src/extensions/align/index.ts +115 -0
  60. package/src/extensions/attachment/icon.svg +1 -0
  61. package/src/extensions/attachment/index.ts +293 -0
  62. package/src/extensions/attachment/style.less +25 -0
  63. package/src/extensions/back-color/index.ts +56 -0
  64. package/src/extensions/blockquote/index.ts +144 -0
  65. package/src/extensions/blockquote/style.less +16 -0
  66. package/src/extensions/bold/index.ts +77 -0
  67. package/src/extensions/code/index.ts +295 -0
  68. package/src/extensions/code/style.less +14 -0
  69. package/src/extensions/code-block/hljs.less +183 -0
  70. package/src/extensions/code-block/hljs.ts +95 -0
  71. package/src/extensions/code-block/index.ts +308 -0
  72. package/src/extensions/code-block/style.less +20 -0
  73. package/src/extensions/color/index.ts +56 -0
  74. package/src/extensions/font-family/index.ts +80 -0
  75. package/src/extensions/font-size/index.ts +56 -0
  76. package/src/extensions/heading/index.ts +164 -0
  77. package/src/extensions/heading/style.less +42 -0
  78. package/src/extensions/history/index.ts +96 -0
  79. package/src/extensions/horizontal/index.ts +45 -0
  80. package/src/extensions/horizontal/style.less +13 -0
  81. package/src/extensions/image/index.ts +242 -0
  82. package/src/extensions/image/style.less +8 -0
  83. package/src/extensions/indent/index.ts +98 -0
  84. package/src/extensions/index.ts +29 -0
  85. package/src/extensions/italic/index.ts +77 -0
  86. package/src/extensions/line-height/index.ts +113 -0
  87. package/src/extensions/link/index.ts +184 -0
  88. package/src/extensions/link/style.less +19 -0
  89. package/src/extensions/list/index.ts +410 -0
  90. package/src/extensions/list/style.less +19 -0
  91. package/src/extensions/math/index.ts +233 -0
  92. package/src/extensions/math/style.less +21 -0
  93. package/src/extensions/strikethrough/index.ts +78 -0
  94. package/src/extensions/subscript/index.ts +77 -0
  95. package/src/extensions/superscript/index.ts +77 -0
  96. package/src/extensions/table/index.ts +1148 -0
  97. package/src/extensions/table/style.less +71 -0
  98. package/src/extensions/task/index.ts +243 -0
  99. package/src/extensions/task/style.less +59 -0
  100. package/src/extensions/text/index.ts +359 -0
  101. package/src/extensions/underline/index.ts +78 -0
  102. package/src/extensions/video/index.ts +273 -0
  103. package/src/extensions/video/style.less +8 -0
  104. package/src/index.ts +9 -0
  105. package/src/model/Editor.ts +1963 -0
  106. package/src/model/History.ts +115 -0
  107. package/src/model/KNode.ts +677 -0
  108. package/src/model/Selection.ts +39 -0
  109. package/src/model/config/dom-observe.ts +184 -0
  110. package/src/model/config/event-handler.ts +237 -0
  111. package/src/model/config/format-patch.ts +215 -0
  112. package/src/model/config/format-rules.ts +218 -0
  113. package/src/model/config/function.ts +1018 -0
  114. package/src/model/index.ts +6 -0
  115. package/src/tools/index.ts +156 -0
  116. package/src/view/index.ts +46 -0
  117. package/src/view/js-render/dom-patch.ts +324 -0
  118. package/src/view/js-render/index.ts +210 -0
  119. package/vite-env.d.ts +2 -0
@@ -0,0 +1,1963 @@
1
+ import { event as DapEvent, element as DapElement } from 'dap-util'
2
+ import { KNode, KNodeCreateOptionType, KNodeMarksType, KNodeMatchOptionType, KNodeStylesType } from './KNode'
3
+ import { createGuid, delay, getDomAttributes, getDomStyles, initEditorDom, isContains, isZeroWidthText } from '../tools'
4
+ import { Selection } from './Selection'
5
+ import { History } from './History'
6
+ import { formatSiblingNodesMerge, formatPlaceholderMerge, formatZeroWidthTextMerge, RuleFunctionType, formatParentNodeMerge, formatUneditableNoodes, formatBlockInChildren, fomratBlockTagParse } from './config/format-rules'
7
+ import { patchNodes } from './config/format-patch'
8
+ import { onBeforeInput, onBlur, onComposition, onCopy, onCut, onFocus, onKeyboard, onSelectionChange } from './config/event-handler'
9
+ import { removeDomObserve, setDomObserve } from './config/dom-observe'
10
+ import { Extension, HistoryExtension, ImageExtension, TextExtension, BoldExtension, ItalicExtension, StrikethroughExtension, UnderlineExtension, SuperscriptExtension, SubscriptExtension, CodeExtension, FontSizeExtension, VideoExtension, FontFamilyExtension, ColorExtension, BackColorExtension, LinkExtension, AlignExtension, LineHeightExtension, IndentExtension, HorizontalExtension, BlockquoteExtension, HeadingExtension, ListExtension, TaskExtension, MathExtension, CodeBlockExtension, AttachmentExtension, TableExtension } from '@/extensions'
11
+ import { NODE_MARK } from '@/view'
12
+ import { defaultUpdateView } from '@/view/js-render'
13
+ import { checkNodes, emptyFixedBlock, formatNodes, handlerForNormalInsertParagraph, mergeBlock, mergeExtensions, redressSelection, registerExtension, removeBlockFromParentToSameLevel, setPlaceholder } from './config/function'
14
+
15
+ /**
16
+ * 编辑器获取光标范围内节点数据的类型
17
+ */
18
+ export type EditorSelectedType = {
19
+ node: KNode
20
+ offset: number[] | false
21
+ }
22
+
23
+ /**
24
+ * 编辑器命令集合类型
25
+ */
26
+ export interface EditorCommandsType {
27
+ [name: string]: ((...args: any[]) => any | void) | undefined
28
+ }
29
+
30
+ /**
31
+ * 编辑器配置入参类型
32
+ */
33
+ export type EditorConfigureOptionType = {
34
+ /**
35
+ * 编辑器渲染的dom或者选择器
36
+ */
37
+ el: HTMLElement | string
38
+ /**
39
+ * 是否允许复制
40
+ */
41
+ allowCopy?: boolean
42
+ /**
43
+ * 是否允许粘贴
44
+ */
45
+ allowPaste?: boolean
46
+ /**
47
+ * 是否允许剪切
48
+ */
49
+ allowCut?: boolean
50
+ /**
51
+ * 是否允许粘贴html
52
+ */
53
+ allowPasteHtml?: boolean
54
+ /**
55
+ * 剪切板同时存在文件和html/text时,是否优先粘贴文件
56
+ */
57
+ priorityPasteFiles?: boolean
58
+ /**
59
+ * 自定义编辑器内渲染文本节点的真实标签
60
+ */
61
+ textRenderTag?: string
62
+ /**
63
+ * 自定义编辑内渲染默认块级节点的真实标签,即段落标签
64
+ */
65
+ blockRenderTag?: string
66
+ /**
67
+ * 自定义编辑器内定义需要置空的标签
68
+ */
69
+ emptyRenderTags?: string[]
70
+ /**
71
+ * 自定义编辑器内额外保留的标签
72
+ */
73
+ extraKeepTags?: string[]
74
+ /**
75
+ * 自定义扩展数组
76
+ */
77
+ extensions?: Extension[]
78
+ /**
79
+ * 自定义节点数组格式化规则
80
+ */
81
+ formatRules?: RuleFunctionType[]
82
+ /**
83
+ * 自定义dom转为非文本节点的后续处理
84
+ */
85
+ domParseNodeCallback?: (this: Editor, node: KNode) => KNode
86
+ /**
87
+ * 视图渲染时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要自定义渲染视图
88
+ */
89
+ onUpdateView?: (this: Editor, init: boolean) => boolean | Promise<boolean>
90
+ /**
91
+ * 编辑器粘贴纯文本时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要进行自定义处理
92
+ */
93
+ onPasteText?: (this: Editor, text: string) => boolean | Promise<boolean>
94
+ /**
95
+ * 编辑器粘贴html内容时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要进行自定义处理
96
+ */
97
+ onPasteHtml?: (this: Editor, nodes: KNode[], html: string) => boolean | Promise<boolean>
98
+ /**
99
+ * 编辑器粘贴图片时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要进行自定义处理
100
+ */
101
+ onPasteImage?: (this: Editor, file: File) => boolean | Promise<boolean>
102
+ /**
103
+ * 编辑器粘贴视频时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要进行自定义处理
104
+ */
105
+ onPasteVideo?: (this: Editor, file: File) => boolean | Promise<boolean>
106
+ /**
107
+ * 编辑器粘贴除了图片和视频以外的文件时触发,需要自定义处理
108
+ */
109
+ onPasteFile?: (this: Editor, file: File) => void | Promise<void>
110
+ /**
111
+ * 编辑器内容改变时触发
112
+ */
113
+ onChange?: (this: Editor, newVal: string, oldVal: string) => void
114
+ /**
115
+ * 编辑器光标发生变化时触发
116
+ */
117
+ onSelectionUpdate?: (this: Editor, selection: Selection) => void
118
+ /**
119
+ * 换行时触发,参数为换行操作后光标所在的块节点
120
+ */
121
+ onInsertParagraph?: (this: Editor, node: KNode) => void
122
+ /**
123
+ * 完成删除时触发
124
+ */
125
+ onDeleteComplete?: (this: Editor) => void
126
+ /**
127
+ * 光标在编辑器内时键盘按下触发
128
+ */
129
+ onKeydown?: (this: Editor, event: KeyboardEvent) => void
130
+ /**
131
+ * 光标在编辑器内时键盘松开触发
132
+ */
133
+ onKeyup?: (this: Editor, event: KeyboardEvent) => void
134
+ /**
135
+ * 编辑器聚焦时触发
136
+ */
137
+ onFocus?: (this: Editor, event: FocusEvent) => void
138
+ /**
139
+ * 编辑器失焦时触发
140
+ */
141
+ onBlur?: (this: Editor, event: FocusEvent) => void
142
+ /**
143
+ * 节点粘贴保留标记的自定义方法
144
+ */
145
+ pasteKeepMarks?: (this: Editor, node: KNode) => KNodeMarksType
146
+ /**
147
+ * 节点粘贴保留样式的自定义方法
148
+ */
149
+ pasteKeepStyles?: (this: Editor, node: KNode) => KNodeStylesType
150
+ /**
151
+ * 视图更新前回调方法
152
+ */
153
+ beforeUpdateView?: (this: Editor) => void
154
+ /**
155
+ * 视图更新后回调方法
156
+ */
157
+ afterUpdateView?: (this: Editor) => void
158
+ /**
159
+ * 在删除和换行操作中块节点从其父节点中抽离出去成为与父节点同级的节点后触发,如果返回true则表示继续使用默认逻辑,会将该节点转为段落,返回false则不走默认逻辑,需要自定义处理
160
+ */
161
+ onDetachMentBlockFromParentCallback?: (this: Editor, node: KNode) => boolean
162
+ /**
163
+ * 编辑器updateView执行时,通过比对新旧节点数组获取需要格式化的节点,在这些节点被格式化前,触发此方法,回调参数即当前需要被格式化的节点,该方法返回一个节点,返回的节点将会被格式化,如果你不需要任何特殊处理,返回入参提供的节点即可
164
+ */
165
+ beforePatchNodeToFormat?: (this: Editor, node: KNode) => KNode
166
+
167
+ /*--------------------------以下不作为编辑器内部属性-------------------------------*/
168
+
169
+ /**
170
+ * 编辑器的初始默认值
171
+ */
172
+ value: string
173
+ /**
174
+ * 编辑器初始是否可编辑,默认true
175
+ */
176
+ editable?: boolean
177
+ /**
178
+ * 是否自动聚焦
179
+ */
180
+ autofocus?: boolean
181
+ /**
182
+ * 编辑器内容只有一个段落时的占位符内容
183
+ */
184
+ placeholder?: string
185
+ /**
186
+ * 是否深色模式
187
+ */
188
+ dark?: boolean
189
+ }
190
+
191
+ /**
192
+ * 编辑器核心类
193
+ */
194
+ export class Editor {
195
+ /**
196
+ * 编辑器的真实dom【初始化后不可修改】【open】
197
+ */
198
+ $el?: HTMLElement
199
+ /**
200
+ * 是否允许复制【初始化后可以修改】【open】
201
+ */
202
+ allowCopy: boolean = true
203
+ /**
204
+ * 是否允许粘贴【初始化后可以修改】【open】
205
+ */
206
+ allowPaste: boolean = true
207
+ /**
208
+ * 是否允许剪切【初始化后可以修改】【open】
209
+ */
210
+ allowCut: boolean = true
211
+ /**
212
+ * 是否允许粘贴html【初始化后可以修改】【open】
213
+ */
214
+ allowPasteHtml: boolean = false
215
+ /**
216
+ * 剪切板同时存在文件和html/text时,是否优先粘贴文件【初始化后可以修改】【open】
217
+ */
218
+ priorityPasteFiles: boolean = false
219
+ /**
220
+ * 编辑器内渲染文本节点的真实标签【初始化后不建议修改】【open】
221
+ */
222
+ textRenderTag: string = 'span'
223
+ /**
224
+ * 编辑内渲染默认块级节点的真实标签,即段落标签【初始化后不建议修改】【open】
225
+ */
226
+ blockRenderTag: string = 'p'
227
+ /**
228
+ * 编辑器内定义需要置空的标签【初始化后不建议修改】【open】
229
+ */
230
+ emptyRenderTags: string[] = ['meta', 'link', 'style', 'script', 'title', 'base', 'noscript', 'template', 'annotation', 'input', 'form', 'button']
231
+ /**
232
+ * 编辑器内额外保留的标签【初始化后不建议修改】【open】
233
+ */
234
+ extraKeepTags: string[] = []
235
+ /**
236
+ * 扩展数组【初始化后不可修改】【open】
237
+ */
238
+ extensions: Extension[] = [TextExtension(), ImageExtension(), VideoExtension(), HistoryExtension(), BoldExtension(), ItalicExtension(), StrikethroughExtension(), UnderlineExtension(), SuperscriptExtension(), SubscriptExtension(), CodeExtension(), FontSizeExtension(), FontFamilyExtension(), ColorExtension(), BackColorExtension(), LinkExtension(), AlignExtension(), LineHeightExtension(), IndentExtension(), HorizontalExtension(), BlockquoteExtension(), HeadingExtension(), ListExtension(), TaskExtension(), MathExtension(), CodeBlockExtension(), AttachmentExtension(), TableExtension()]
239
+ /**
240
+ * 编辑器的节点数组格式化规则【初始化后不可修改】【open】
241
+ */
242
+ formatRules: RuleFunctionType[] = [fomratBlockTagParse, formatBlockInChildren, formatUneditableNoodes, formatPlaceholderMerge, formatZeroWidthTextMerge, formatSiblingNodesMerge, formatParentNodeMerge]
243
+ /**
244
+ * 自定义dom转为非文本节点的后续处理【初始化后不可修改】
245
+ */
246
+ domParseNodeCallback?: (this: Editor, node: KNode) => KNode
247
+ /**
248
+ * 视图渲染时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要自定义渲染视图【初始化后不可修改】
249
+ */
250
+ onUpdateView?: (this: Editor, init: boolean) => boolean | Promise<boolean>
251
+ /**
252
+ * 编辑器粘贴纯文本时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要进行自定义处理【初始化后不可修改】
253
+ */
254
+ onPasteText?: (this: Editor, text: string) => boolean | Promise<boolean>
255
+ /**
256
+ * 编辑器粘贴html内容时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要进行自定义处理【初始化后不可修改】
257
+ */
258
+ onPasteHtml?: (this: Editor, nodes: KNode[], html: string) => boolean | Promise<boolean>
259
+ /**
260
+ * 编辑器粘贴图片时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要进行自定义处理【初始化后不可修改】
261
+ */
262
+ onPasteImage?: (this: Editor, file: File) => boolean | Promise<boolean>
263
+ /**
264
+ * 编辑器粘贴视频时触发,如果返回true则表示继续使用默认逻辑,返回false则不走默认逻辑,需要进行自定义处理【初始化后不可修改】
265
+ */
266
+ onPasteVideo?: (this: Editor, file: File) => boolean | Promise<boolean>
267
+ /**
268
+ * 编辑器粘贴除了图片和视频以外的文件时触发,需要自定义处理【初始化后不可修改】
269
+ */
270
+ onPasteFile?: (this: Editor, file: File) => void | Promise<void>
271
+ /**
272
+ * 编辑器内容改变触发【初始化后不可修改】
273
+ */
274
+ onChange?: (this: Editor, newVal: string, oldVal: string) => void
275
+ /**
276
+ * 编辑器光标发生变化【初始化后不可修改】
277
+ */
278
+ onSelectionUpdate?: (this: Editor, selection: Selection) => void
279
+ /**
280
+ * 换行时触发,参数为换行操作后光标所在的块节点
281
+ */
282
+ onInsertParagraph?: (this: Editor, node: KNode) => void
283
+ /**
284
+ * 完成删除时触发【初始化后不可修改】
285
+ */
286
+ onDeleteComplete?: (this: Editor) => void
287
+ /**
288
+ * 光标在编辑器内时键盘按下触发【初始化后不可修改】
289
+ */
290
+ onKeydown?: (this: Editor, event: KeyboardEvent) => void
291
+ /**
292
+ * 光标在编辑器内时键盘松开触发【初始化后不可修改】
293
+ */
294
+ onKeyup?: (this: Editor, event: KeyboardEvent) => void
295
+ /**
296
+ * 编辑器聚焦时触发【初始化后不可修改】
297
+ */
298
+ onFocus?: (this: Editor, event: FocusEvent) => void
299
+ /**
300
+ * 编辑器失焦时触发【初始化后不可修改】
301
+ */
302
+ onBlur?: (this: Editor, event: FocusEvent) => void
303
+ /**
304
+ * 节点粘贴保留标记的自定义方法【初始化后不可修改】
305
+ */
306
+ pasteKeepMarks?: (this: Editor, node: KNode) => KNodeMarksType
307
+ /**
308
+ * 节点粘贴保留样式的自定义方法【初始化后不可修改】
309
+ */
310
+ pasteKeepStyles?: (this: Editor, node: KNode) => KNodeStylesType
311
+ /**
312
+ * 视图更新前回调方法【初始化后不可修改】
313
+ */
314
+ beforeUpdateView?: (this: Editor) => void
315
+ /**
316
+ * 视图更新后回调方法【初始化后不可修改】
317
+ */
318
+ afterUpdateView?: (this: Editor) => void
319
+ /**
320
+ * 在删除和换行操作中块节点节点从其父节点中抽离出去成为与父节点同级的节点后触发,如果返回true则表示继续使用默认逻辑,会将该节点转为段落,返回false则不走默认逻辑,需要自定义处理【初始化后不可修改】
321
+ */
322
+ onDetachMentBlockFromParentCallback?: (this: Editor, node: KNode) => boolean
323
+ /**
324
+ * 编辑器updateView执行时,通过比对新旧节点数组获取需要格式化的节点,在这些节点被格式化前,触发此方法,回调参数即当前需要被格式化的节点,该方法返回一个节点,返回的节点将会被格式化,如果你不需要任何特殊处理,返回入参提供的节点即可【初始化后不可修改】
325
+ */
326
+ beforePatchNodeToFormat?: (this: Editor, node: KNode) => KNode
327
+
328
+ /*---------------------下面的属性都是不属于创建编辑器的参数---------------------------*/
329
+
330
+ /**
331
+ * 唯一id【不可修改】【open】
332
+ */
333
+ guid: number = createGuid()
334
+ /**
335
+ * 虚拟光标【不建议修改】【open】
336
+ */
337
+ selection: Selection = new Selection()
338
+ /**
339
+ * 历史记录【不建议修改】【open】
340
+ */
341
+ history: History = new History()
342
+ /**
343
+ * 命令集合【不可修改】【open】
344
+ */
345
+ commands: EditorCommandsType = {}
346
+ /**
347
+ * 节点数组【不建议修改】【open】
348
+ */
349
+ stackNodes: KNode[] = []
350
+ /**
351
+ * 旧节点数组【不可修改】
352
+ */
353
+ oldStackNodes: KNode[] = []
354
+ /**
355
+ * 是否在输入中文【不可修改】
356
+ */
357
+ isComposition: boolean = false
358
+ /**
359
+ * 是否编辑器内部渲染真实光标引起selctionChange事件【不可修改】
360
+ */
361
+ internalCauseSelectionChange: boolean = false
362
+ /**
363
+ * 是否用户操作的删除行为,如果是用户操作的删除行为,则在处理不可编辑的节点是会删除该节点,如果是API调用的删除方法则走正常的删除逻辑【不可修改】
364
+ */
365
+ isUserDelection: boolean = false
366
+ /**
367
+ * dom监听【不可修改】
368
+ */
369
+ domObserver: MutationObserver | null = null
370
+
371
+ /**
372
+ * 如果编辑器内有滚动条,滚动编辑器到光标可视范围
373
+ */
374
+ scrollViewToSelection() {
375
+ if (this.selection.focused()) {
376
+ const focusDom = this.findDom(this.selection.end!.node)
377
+ const scrollFunction = async (scrollEl: HTMLElement) => {
378
+ const scrollHeight = DapElement.getScrollHeight(scrollEl)
379
+ const scrollWidth = DapElement.getScrollWidth(scrollEl)
380
+ //存在横向或者垂直滚动条
381
+ if (scrollEl.clientHeight < scrollHeight || scrollEl.clientWidth < scrollWidth) {
382
+ const selection = window.getSelection()!
383
+ const range = selection.getRangeAt(0)
384
+ const rects = range.getClientRects()
385
+ let target: Range | HTMLElement = range
386
+ if (rects.length == 0) {
387
+ target = focusDom
388
+ }
389
+ const childRect = target.getBoundingClientRect()
390
+ const parentRect = scrollEl.getBoundingClientRect()
391
+ //存在垂直滚动条
392
+ if (scrollEl.clientHeight < scrollHeight) {
393
+ //如果光标所在节点不在视图内则滚动到视图内
394
+ if (childRect.top < parentRect.top) {
395
+ await DapElement.setScrollTop({
396
+ el: scrollEl,
397
+ number: 0
398
+ })
399
+ const tempChildRect = target.getBoundingClientRect()
400
+ const tempParentRect = scrollEl.getBoundingClientRect()
401
+ DapElement.setScrollTop({
402
+ el: scrollEl,
403
+ number: tempChildRect.top - tempParentRect.top
404
+ })
405
+ } else if (childRect.bottom > parentRect.bottom) {
406
+ await DapElement.setScrollTop({
407
+ el: scrollEl,
408
+ number: 0
409
+ })
410
+ const tempChildRect = target.getBoundingClientRect()
411
+ const tempParentRect = scrollEl.getBoundingClientRect()
412
+ DapElement.setScrollTop({
413
+ el: scrollEl,
414
+ number: tempChildRect.bottom - tempParentRect.bottom
415
+ })
416
+ }
417
+ }
418
+ //存在横向滚动条
419
+ if (scrollEl.clientWidth < scrollWidth) {
420
+ //如果光标所在节点不在视图内则滚动到视图内
421
+ if (childRect.left < parentRect.left) {
422
+ await DapElement.setScrollLeft({
423
+ el: scrollEl,
424
+ number: 0
425
+ })
426
+ const tempChildRect = target.getBoundingClientRect()
427
+ const tempParentRect = scrollEl.getBoundingClientRect()
428
+ DapElement.setScrollLeft({
429
+ el: scrollEl,
430
+ number: tempChildRect.left - tempParentRect.left + 20
431
+ })
432
+ } else if (childRect.right > parentRect.right) {
433
+ await DapElement.setScrollLeft({
434
+ el: scrollEl,
435
+ number: 0
436
+ })
437
+ const tempChildRect = target.getBoundingClientRect()
438
+ const tempParentRect = scrollEl.getBoundingClientRect()
439
+ DapElement.setScrollLeft({
440
+ el: scrollEl,
441
+ number: tempChildRect.right - tempParentRect.right + 20
442
+ })
443
+ }
444
+ }
445
+ }
446
+ }
447
+ let dom = focusDom
448
+ while (DapElement.isElement(dom) && dom != document.documentElement) {
449
+ scrollFunction(dom)
450
+ dom = dom.parentNode as HTMLElement
451
+ }
452
+ }
453
+ }
454
+
455
+ /**
456
+ * 根据dom查找到编辑内的对应节点
457
+ */
458
+ findNode(dom: HTMLElement) {
459
+ if (!isContains(this.$el!, dom)) {
460
+ throw new Error(`The dom should be in the editor area, but what you provide is not`)
461
+ }
462
+ const key = dom.getAttribute(NODE_MARK)
463
+ if (!key) {
464
+ throw new Error(`The dom generated by editor should all have a ${NODE_MARK} attribute, but your dom does not. Check for "updateView" related issues`)
465
+ }
466
+ const node = KNode.searchByKey(key, this.stackNodes)
467
+ if (!node) {
468
+ throw new Error(`Unexpected error occurred: the knode was not found in the editor`)
469
+ }
470
+ return node
471
+ }
472
+
473
+ /**
474
+ * 根据编辑器内的node查找真实dom
475
+ */
476
+ findDom(node: KNode) {
477
+ //获取所有的符合选择器的元素
478
+ const doms = this.$el!.querySelectorAll(`[${NODE_MARK}="${node.key}"]`)
479
+ //如果没有则抛出异常
480
+ if (doms.length == 0) {
481
+ throw new Error(`Unexpected error occurred: the dom was not found in the editor`)
482
+ }
483
+ //查找父元素匹配的元素
484
+ const dom = Array.from(doms).find(item => (node.parent ? item.parentElement?.getAttribute(`${NODE_MARK}`) == `${node.parent.key}` : item.parentElement === this.$el))
485
+ //不存在则抛出异常
486
+ if (!dom) {
487
+ throw new Error(`Unexpected error occurred: the dom was not found in the editor`)
488
+ }
489
+ return dom as HTMLElement
490
+ }
491
+
492
+ /**
493
+ * 设置编辑器是否可编辑
494
+ */
495
+ setEditable(editable: boolean) {
496
+ if (editable) {
497
+ this.$el?.setAttribute('contenteditable', 'true')
498
+ } else {
499
+ this.$el?.removeAttribute('contenteditable')
500
+ }
501
+ this.$el?.setAttribute('spellcheck', 'false')
502
+ }
503
+
504
+ /**
505
+ * 判断编辑器是否可编辑
506
+ */
507
+ isEditable() {
508
+ return this.$el?.getAttribute('contenteditable') == 'true'
509
+ }
510
+
511
+ /**
512
+ * 设置编辑器是否深色模式
513
+ */
514
+ setDark(dark: boolean) {
515
+ if (dark) {
516
+ document.documentElement.setAttribute('kaitify-dark', '')
517
+ } else {
518
+ document.documentElement.removeAttribute('kaitify-dark')
519
+ }
520
+ }
521
+
522
+ /**
523
+ * 是否深色模式
524
+ */
525
+ isDark() {
526
+ return document.documentElement.hasAttribute('kaitify-dark')
527
+ }
528
+
529
+ /**
530
+ * dom转KNode
531
+ */
532
+ domParseNode(dom: Node) {
533
+ if (dom.nodeType != 1 && dom.nodeType != 3) {
534
+ throw new Error('The argument must be an element node or text node')
535
+ }
536
+ //文本节点
537
+ if (dom.nodeType == 3) {
538
+ return KNode.create({
539
+ type: 'text',
540
+ textContent: dom.textContent || ''
541
+ })
542
+ }
543
+ //元素节点
544
+ const marks = getDomAttributes(dom as HTMLElement) //标记
545
+ const styles = getDomStyles(dom as HTMLElement) //样式
546
+ const tag = dom.nodeName.toLocaleLowerCase() //标签名称
547
+ const namespace = (dom as HTMLElement).namespaceURI //命名空间
548
+ //如果是需要置为空的标签返回空文本节点
549
+ if (this.emptyRenderTags.includes(tag)) {
550
+ return KNode.create({
551
+ type: 'text'
552
+ })
553
+ }
554
+ //如果是默认的文本节点标签并且内部只有文本,则返回文本节点
555
+ if (tag == this.textRenderTag && dom.childNodes.length && Array.from(dom.childNodes).every(childNode => childNode.nodeType == 3)) {
556
+ return KNode.create({
557
+ type: 'text',
558
+ marks,
559
+ styles,
560
+ textContent: dom.textContent || ''
561
+ })
562
+ }
563
+ //构造参数
564
+ const config: KNodeCreateOptionType = {
565
+ type: 'inline',
566
+ tag,
567
+ marks,
568
+ styles,
569
+ namespace: namespace || ''
570
+ }
571
+ //默认的块节点
572
+ if (['p', 'div', 'address', 'article', 'aside', 'nav', 'section'].includes(tag)) {
573
+ config.type = 'block'
574
+ config.children = []
575
+ }
576
+ //默认的行内节点
577
+ else if (['span', 'label'].includes(tag)) {
578
+ config.type = 'inline'
579
+ config.children = []
580
+ }
581
+ //默认的自闭合节点
582
+ else if (['br'].includes(tag)) {
583
+ config.type = 'closed'
584
+ }
585
+ //其余元素如果不在extraKeepTags范围内则默认转为行内的默认文本节点标签
586
+ else if (!this.extraKeepTags.includes(tag)) {
587
+ config.type = 'inline'
588
+ config.tag = this.textRenderTag
589
+ config.namespace = ''
590
+ config.children = []
591
+ }
592
+ let node = KNode.create(config)
593
+ //如果不是闭合节点则设置子节点
594
+ if (!closed) {
595
+ Array.from(dom.childNodes).forEach(child => {
596
+ if (child.nodeType == 1 || child.nodeType == 3) {
597
+ const childNode = this.domParseNode(child)
598
+ childNode.parent = node
599
+ if (node.hasChildren()) {
600
+ node.children!.push(childNode)
601
+ } else {
602
+ node.children = [childNode]
603
+ }
604
+ }
605
+ })
606
+ }
607
+ //转换后的回调处理,在这里可以自定义处理节点
608
+ if (typeof this.domParseNodeCallback == 'function') {
609
+ node = this.domParseNodeCallback.apply(this, [node])
610
+ }
611
+ return node
612
+ }
613
+
614
+ /**
615
+ * html转KNode
616
+ */
617
+ htmlParseNode(html: string) {
618
+ const template = document.createElement('template')
619
+ template.innerHTML = html
620
+ const nodes: KNode[] = []
621
+ template.content.childNodes.forEach(item => {
622
+ if (item.nodeType == 1 || item.nodeType == 3) {
623
+ const node = this.domParseNode(item)
624
+ nodes.push(node)
625
+ }
626
+ })
627
+ return nodes
628
+ }
629
+
630
+ /**
631
+ * 将指定节点所在的块节点转为段落
632
+ */
633
+ toParagraph(node: KNode) {
634
+ if (!node.isBlock()) {
635
+ node = node.getBlock()
636
+ }
637
+ node.tag = this.blockRenderTag
638
+ node.marks = {}
639
+ node.styles = {}
640
+ node.fixed = false
641
+ node.nested = false
642
+ node.locked = false
643
+ node.namespace = ''
644
+ }
645
+
646
+ /**
647
+ * 指定的块节点是否是一个段落
648
+ */
649
+ isParagraph(node: KNode) {
650
+ if (!node.isBlock()) {
651
+ return false
652
+ }
653
+ return node.isMatch({ tag: this.blockRenderTag }) && !node.hasMarks() && !node.hasStyles()
654
+ }
655
+
656
+ /**
657
+ * 将指定节点添加到某个节点的子节点数组里
658
+ */
659
+ addNode(node: KNode, parentNode: KNode, index: number | undefined = 0) {
660
+ //排除空节点
661
+ if (node.isEmpty()) {
662
+ return
663
+ }
664
+ //父节点不能是文本节点或者闭合节点
665
+ if (parentNode.isText() || parentNode.isClosed()) {
666
+ return
667
+ }
668
+ //不存在子节点,初始为空数组
669
+ if (!parentNode.hasChildren()) {
670
+ parentNode.children = []
671
+ }
672
+ if (index >= parentNode.children!.length) {
673
+ parentNode.children!.push(node)
674
+ } else {
675
+ parentNode.children!.splice(index, 0, node)
676
+ }
677
+ node.parent = parentNode
678
+ }
679
+
680
+ /**
681
+ * 将指定节点添加到某个节点前面
682
+ */
683
+ addNodeBefore(node: KNode, target: KNode) {
684
+ if (target.parent) {
685
+ const index = target.parent!.children!.findIndex(item => {
686
+ return target.isEqual(item)
687
+ })
688
+ this.addNode(node, target.parent!, index)
689
+ } else {
690
+ const index = this.stackNodes.findIndex(item => {
691
+ return target.isEqual(item)
692
+ })
693
+ this.stackNodes.splice(index, 0, node)
694
+ node.parent = undefined
695
+ }
696
+ }
697
+
698
+ /**
699
+ * 将指定节点添加到某个节点后面
700
+ */
701
+ addNodeAfter(node: KNode, target: KNode) {
702
+ if (target.parent) {
703
+ const index = target.parent!.children!.findIndex(item => {
704
+ return target.isEqual(item)
705
+ })
706
+ this.addNode(node, target.parent!, index + 1)
707
+ } else {
708
+ const index = this.stackNodes.findIndex(item => {
709
+ return target.isEqual(item)
710
+ })
711
+ this.stackNodes.splice(index + 1, 0, node)
712
+ node.parent = undefined
713
+ }
714
+ }
715
+
716
+ /**
717
+ * 获取某个节点内的最后一个可以设置光标点的节点,包括自身
718
+ */
719
+ getLastSelectionNodeInChildren(node: KNode): KNode | null {
720
+ //空节点
721
+ if (node.isEmpty()) {
722
+ return null
723
+ }
724
+ //子节点是不可见节点
725
+ if (node.void) {
726
+ return null
727
+ }
728
+ //文本节点和闭合节点返回自身
729
+ if (node.isText() || node.isClosed()) {
730
+ return node
731
+ }
732
+ let selectionNode = null
733
+ const length = node.children!.length
734
+ //遍历子节点
735
+ for (let i = length - 1; i >= 0; i--) {
736
+ const child = node.children![i]
737
+ selectionNode = this.getLastSelectionNodeInChildren(child)
738
+ //这里如果在子节点中找到了可以设置光标点的节点,一定要break直接终止for循环的执行
739
+ if (selectionNode) {
740
+ break
741
+ }
742
+ }
743
+ return selectionNode
744
+ }
745
+
746
+ /**
747
+ * 获取某个节点内的第一个可以设置光标点的节点,包括自身
748
+ */
749
+ getFirstSelectionNodeInChildren(node: KNode): KNode | null {
750
+ //空节点
751
+ if (node.isEmpty()) {
752
+ return null
753
+ }
754
+ //子节点是不可见节点
755
+ if (node.void) {
756
+ return null
757
+ }
758
+ //文本节点和闭合节点返回自身
759
+ if (node.isText() || node.isClosed()) {
760
+ return node
761
+ }
762
+ let selectionNode = null
763
+ const length = node.children!.length
764
+ //遍历子节点
765
+ for (let i = 0; i < length; i++) {
766
+ const child = node.children![i]
767
+ selectionNode = this.getFirstSelectionNodeInChildren(child)
768
+ //这里如果在子节点中找到了可以设置光标点的节点,一定要break直接终止for循环的执行
769
+ if (selectionNode) {
770
+ break
771
+ }
772
+ }
773
+ return selectionNode
774
+ }
775
+
776
+ /**
777
+ * 查找指定节点之前可以设置为光标点的非空节点,不包括自身
778
+ */
779
+ getPreviousSelectionNode(node: KNode): KNode | null {
780
+ const nodes = node.parent ? node.parent.children! : this.stackNodes
781
+ //获取前一个节点
782
+ const previousNode = node.getPrevious(nodes)
783
+ //前一个节点存在
784
+ if (previousNode) {
785
+ //是空节点,则跳过继续向前
786
+ if (previousNode.isEmpty()) {
787
+ return this.getPreviousSelectionNode(previousNode)
788
+ }
789
+ //是不可见节点,则跳过继续向前
790
+ if (previousNode.void) {
791
+ return this.getPreviousSelectionNode(previousNode)
792
+ }
793
+ //是文本节点或者闭合节点
794
+ if (previousNode.isText() || previousNode.isClosed()) {
795
+ return previousNode
796
+ }
797
+ //其他节点:查找子节点中的最后一个可以设置光标点的节点
798
+ return this.getLastSelectionNodeInChildren(previousNode)
799
+ }
800
+ //前一个节点不存在的情况,说明该节点是节点数组中的第一个节点
801
+ return node.parent ? this.getPreviousSelectionNode(node.parent) : null
802
+ }
803
+
804
+ /**
805
+ * 查找指定节点之后可以设置为光标点的非空节点,不包括自身
806
+ */
807
+ getNextSelectionNode(node: KNode): KNode | null {
808
+ const nodes = node.parent ? node.parent.children! : this.stackNodes
809
+ //获取后一个节点
810
+ const nextNode = node.getNext(nodes)
811
+ //后一个节点存在
812
+ if (nextNode) {
813
+ //是空节点,则跳过继续向后
814
+ if (nextNode.isEmpty()) {
815
+ return this.getNextSelectionNode(nextNode)
816
+ }
817
+ //是不可见节点,则跳过继续向后
818
+ if (nextNode.void) {
819
+ return this.getNextSelectionNode(nextNode)
820
+ }
821
+ //是文本节点或者闭合节点
822
+ if (nextNode.isText() || nextNode.isClosed()) {
823
+ return nextNode
824
+ }
825
+ //其他节点:查找子节点中的第一个可以设置光标点的节点
826
+ return this.getFirstSelectionNodeInChildren(nextNode)
827
+ }
828
+ //后一个节点不存在的情况,说明该节点是节点数组中的最后一个节点
829
+ return node.parent ? this.getNextSelectionNode(node.parent) : null
830
+ }
831
+
832
+ /**
833
+ * 设置光标到指定节点内部的起始处,如果没有指定节点则设置光标到编辑器起始处,start表示只设置起点,end表示只设置终点,all表示起点和终点都设置
834
+ */
835
+ setSelectionBefore(node?: KNode, type: 'all' | 'start' | 'end' | undefined = 'all') {
836
+ //指定到某个节点
837
+ if (node) {
838
+ const selectionNode = this.getFirstSelectionNodeInChildren(node)
839
+ if (selectionNode) {
840
+ if (type == 'start' || type == 'all') {
841
+ this.selection.start = {
842
+ node: selectionNode,
843
+ offset: 0
844
+ }
845
+ }
846
+ if (type == 'end' || type == 'all') {
847
+ this.selection.end = {
848
+ node: selectionNode,
849
+ offset: 0
850
+ }
851
+ }
852
+ }
853
+ }
854
+ //指定到文档前面
855
+ else {
856
+ //获取第一个节点
857
+ const firstNode = this.stackNodes[0]
858
+ //获取firstNode中的第一个可以设置光标点的节点
859
+ let selectionNode = this.getFirstSelectionNodeInChildren(firstNode)
860
+ //如果firstNode不能设置光标点,则向后查询
861
+ if (!selectionNode) selectionNode = this.getNextSelectionNode(firstNode)
862
+ //如果firstNode可以设置光标点
863
+ if (selectionNode) {
864
+ if (type == 'start' || type == 'all') {
865
+ this.selection.start = {
866
+ node: selectionNode,
867
+ offset: 0
868
+ }
869
+ }
870
+ if (type == 'end' || type == 'all') {
871
+ this.selection.end = {
872
+ node: selectionNode,
873
+ offset: 0
874
+ }
875
+ }
876
+ }
877
+ }
878
+ }
879
+
880
+ /**
881
+ * 设置光标到指定节点内部的末尾处,如果没有指定节点则设置光标到编辑器末尾处,start表示只设置起点,end表示只设置终点,all表示起点和终点都设置
882
+ */
883
+ setSelectionAfter(node?: KNode, type: 'all' | 'start' | 'end' | undefined = 'all') {
884
+ //指定到某个节点
885
+ if (node) {
886
+ const selectionNode = this.getLastSelectionNodeInChildren(node)
887
+ if (selectionNode) {
888
+ if (type == 'start' || type == 'all') {
889
+ this.selection.start = {
890
+ node: selectionNode,
891
+ offset: selectionNode.isText() ? selectionNode.textContent!.length : 1
892
+ }
893
+ }
894
+ if (type == 'end' || type == 'all') {
895
+ this.selection.end = {
896
+ node: selectionNode,
897
+ offset: selectionNode.isText() ? selectionNode.textContent!.length : 1
898
+ }
899
+ }
900
+ }
901
+ }
902
+ //指定到文档前面
903
+ else {
904
+ //获取最后一个节点
905
+ const lastNode = this.stackNodes[this.stackNodes.length - 1]
906
+ //获取lastNode中的最后一个可以设置光标点的节点
907
+ let selectionNode = this.getLastSelectionNodeInChildren(lastNode)
908
+ //如果lastNode不能设置光标点,则向前查询
909
+ if (!selectionNode) selectionNode = this.getPreviousSelectionNode(lastNode)
910
+ //如果lastNode可以设置光标点
911
+ if (selectionNode) {
912
+ if (type == 'start' || type == 'all') {
913
+ this.selection.start = {
914
+ node: selectionNode,
915
+ offset: selectionNode.isText() ? selectionNode.textContent!.length : 1
916
+ }
917
+ }
918
+ if (type == 'end' || type == 'all') {
919
+ this.selection.end = {
920
+ node: selectionNode,
921
+ offset: selectionNode.isText() ? selectionNode.textContent!.length : 1
922
+ }
923
+ }
924
+ }
925
+ }
926
+ }
927
+
928
+ /**
929
+ * 更新指定光标到离当前光标点最近的节点上,start表示只更新起点,end表示只更新终点,all表示起点和终点都更新,不包括当前光标所在节点
930
+ */
931
+ updateSelectionRecently(type: 'all' | 'start' | 'end' | undefined = 'all') {
932
+ if (!this.selection.focused()) {
933
+ return
934
+ }
935
+ if (type == 'start' || type == 'all') {
936
+ const previousNode = this.getPreviousSelectionNode(this.selection.start!.node)
937
+ const nextNode = this.getNextSelectionNode(this.selection.start!.node)
938
+ const blockNode = this.selection.start!.node.getBlock()
939
+ if (previousNode && blockNode.isContains(previousNode)) {
940
+ this.selection.start!.node = previousNode
941
+ this.selection.start!.offset = previousNode.isText() ? previousNode.textContent!.length : 1
942
+ } else if (nextNode && blockNode.isContains(nextNode)) {
943
+ this.selection.start!.node = nextNode
944
+ this.selection.start!.offset = 0
945
+ } else if (previousNode) {
946
+ this.selection.start!.node = previousNode
947
+ this.selection.start!.offset = previousNode.isText() ? previousNode.textContent!.length : 1
948
+ } else if (nextNode) {
949
+ this.selection.start!.node = nextNode
950
+ this.selection.start!.offset = 0
951
+ }
952
+ }
953
+ if (type == 'end' || type == 'all') {
954
+ const previousNode = this.getPreviousSelectionNode(this.selection.end!.node)
955
+ const nextNode = this.getNextSelectionNode(this.selection.end!.node)
956
+ const blockNode = this.selection.end!.node.getBlock()
957
+ if (previousNode && blockNode.isContains(previousNode)) {
958
+ this.selection.end!.node = previousNode
959
+ this.selection.end!.offset = previousNode.isText() ? previousNode.textContent!.length : 1
960
+ } else if (nextNode && blockNode.isContains(nextNode)) {
961
+ this.selection.end!.node = nextNode
962
+ this.selection.end!.offset = 0
963
+ } else if (previousNode) {
964
+ this.selection.end!.node = previousNode
965
+ this.selection.end!.offset = previousNode.isText() ? previousNode.textContent!.length : 1
966
+ } else if (nextNode) {
967
+ this.selection.end!.node = nextNode
968
+ this.selection.end!.offset = 0
969
+ }
970
+ }
971
+ }
972
+
973
+ /**
974
+ * 判断光标是否在某个节点内,start表示只判断起点,end表示只判断终点,all表示起点和终点都判断
975
+ */
976
+ isSelectionInNode(node: KNode, type: 'all' | 'start' | 'end' | undefined = 'all') {
977
+ //没有初始化设置光标
978
+ if (!this.selection.focused()) {
979
+ return false
980
+ }
981
+ if (type == 'start') {
982
+ return node.isContains(this.selection.start!.node)
983
+ }
984
+ if (type == 'end') {
985
+ return node.isContains(this.selection.end!.node)
986
+ }
987
+ if (type == 'all') {
988
+ return node.isContains(this.selection.start!.node) && node.isContains(this.selection.end!.node)
989
+ }
990
+ return false
991
+ }
992
+
993
+ /**
994
+ * 获取光标选区内的节点数据
995
+ */
996
+ getSelectedNodes(): EditorSelectedType[] {
997
+ //没有聚焦或者没有选区
998
+ if (!this.selection.focused() || this.selection.collapsed()) {
999
+ return []
1000
+ }
1001
+ const startNode = this.selection.start!.node
1002
+ const endNode = this.selection.end!.node
1003
+ const startOffset = this.selection.start!.offset
1004
+ const endOffset = this.selection.end!.offset
1005
+
1006
+ //起点和终点在一个节点内
1007
+ if (startNode.isEqual(endNode)) {
1008
+ //闭合节点
1009
+ if (startNode.isClosed()) {
1010
+ return [
1011
+ {
1012
+ node: startNode,
1013
+ offset: false
1014
+ }
1015
+ ]
1016
+ }
1017
+ //文本节点
1018
+ return [
1019
+ {
1020
+ node: startNode,
1021
+ offset: startOffset == 0 && endOffset == startNode.textContent!.length ? false : [startOffset, endOffset]
1022
+ }
1023
+ ]
1024
+ }
1025
+
1026
+ //起点和终点不在一个节点内时
1027
+ const result: EditorSelectedType[] = []
1028
+ let node = startNode
1029
+ //遍历到终点所在节点时,循环结束
1030
+ while (true) {
1031
+ //是起点所在节点
1032
+ if (node.isEqual(startNode)) {
1033
+ if (startOffset == 0) {
1034
+ result.push({
1035
+ node: node,
1036
+ offset: false
1037
+ })
1038
+ } else if (node.isText() && startOffset < node.textContent!.length) {
1039
+ result.push({
1040
+ node: node,
1041
+ offset: [startOffset, node.textContent!.length]
1042
+ })
1043
+ }
1044
+ }
1045
+ //是终点所在节点
1046
+ else if (node.isEqual(endNode)) {
1047
+ if (endOffset == (node.isText() ? node.textContent!.length : 1)) {
1048
+ result.push({
1049
+ node: node,
1050
+ offset: false
1051
+ })
1052
+ } else if (node.isText() && endOffset > 0) {
1053
+ result.push({
1054
+ node: node,
1055
+ offset: [0, endOffset]
1056
+ })
1057
+ }
1058
+ break
1059
+ }
1060
+ //不是起点和终点所在节点
1061
+ else {
1062
+ //包含终点
1063
+ if (node.isContains(endNode)) {
1064
+ //获取该节点最后一个可以设置光标的节点
1065
+ const lastSelectionNode = this.getLastSelectionNodeInChildren(node)!
1066
+ //如果终点所在节点是它最后一个可以设置光标的节点,并且光标在末尾处
1067
+ if (endNode.isEqual(lastSelectionNode) && endOffset == (endNode.isText() ? endNode.textContent!.length : 1)) {
1068
+ result.push({
1069
+ node,
1070
+ offset: false
1071
+ })
1072
+ break
1073
+ }
1074
+ //只有部分在选区内,处理子节点
1075
+ node = node.children![0]
1076
+ continue
1077
+ }
1078
+ //不包含终点,则说明节点完全在选区内
1079
+ result.push({
1080
+ node: node,
1081
+ offset: false
1082
+ })
1083
+ }
1084
+ //下一个节点
1085
+ let tempNode: KNode = node
1086
+ let nextNode: KNode | null = null
1087
+ while (true) {
1088
+ //获取下一个节点
1089
+ nextNode = tempNode.getNext(tempNode.parent ? tempNode.parent!.children! : this.stackNodes)
1090
+ if (nextNode || !tempNode.parent) {
1091
+ break
1092
+ }
1093
+ tempNode = tempNode.parent
1094
+ }
1095
+ if (nextNode) {
1096
+ node = nextNode
1097
+ continue
1098
+ }
1099
+ break
1100
+ }
1101
+ return result
1102
+ }
1103
+
1104
+ /**
1105
+ * 判断光标范围内的可聚焦节点是否全都在同一个符合条件节点内,如果是返回那个符合条件的节点,否则返回null
1106
+ */
1107
+ getMatchNodeBySelection(options: KNodeMatchOptionType) {
1108
+ //没有聚焦
1109
+ if (!this.selection.focused()) {
1110
+ return null
1111
+ }
1112
+ //起点和终点在一起
1113
+ if (this.selection.collapsed()) {
1114
+ return this.selection.start!.node.getMatchNode(options)
1115
+ }
1116
+ //起点和终点不在一起的情况,获取所有可聚焦的节点
1117
+ const nodes = this.getFocusNodesBySelection('all')
1118
+ if (nodes.length == 0) {
1119
+ return null
1120
+ }
1121
+ //获取第一个可聚焦节点所在的符合条件的节点
1122
+ const matchNode = nodes[0].getMatchNode(options)
1123
+ //如果后续每个可聚焦节点都在该节点内,返回该节点
1124
+ if (matchNode && nodes.every(item => matchNode.isContains(item))) {
1125
+ return matchNode
1126
+ }
1127
+ return null
1128
+ }
1129
+
1130
+ /**
1131
+ * 判断光标范围内的可聚焦节点是否全都在符合条件的(不一定是同一个)节点内
1132
+ */
1133
+ isSelectionNodesAllMatch(options: KNodeMatchOptionType) {
1134
+ //没有聚焦
1135
+ if (!this.selection.focused()) {
1136
+ return false
1137
+ }
1138
+ //起点和终点在一起
1139
+ if (this.selection.collapsed()) {
1140
+ return !!this.selection.start!.node.getMatchNode(options)
1141
+ }
1142
+ //起点和终点不在一起的情况,获取所有可聚焦的节点进行判断
1143
+ const focusNodes = this.getFocusNodesBySelection('all')
1144
+ if (focusNodes.length == 0) {
1145
+ return false
1146
+ }
1147
+ return focusNodes.every(item => !!item.getMatchNode(options))
1148
+ }
1149
+
1150
+ /**
1151
+ * 判断光标范围内是否有可聚焦节点在符合条件的节点内
1152
+ */
1153
+ isSelectionNodesSomeMatch(options: KNodeMatchOptionType) {
1154
+ //没有聚焦
1155
+ if (!this.selection.focused()) {
1156
+ return false
1157
+ }
1158
+ //起点和终点在一起
1159
+ if (this.selection.collapsed()) {
1160
+ return !!this.selection.start!.node.getMatchNode(options)
1161
+ }
1162
+ //起点和终点不在一起的情况,获取所有可聚焦的节点进行判断
1163
+ const focusNodes = this.getFocusNodesBySelection('all')
1164
+ if (focusNodes.length == 0) {
1165
+ return false
1166
+ }
1167
+ return focusNodes.some(item => !!item.getMatchNode(options))
1168
+ }
1169
+
1170
+ /**
1171
+ * 获取所有在光标范围内的可聚焦节点,该方法拿到的可聚焦节点(文本)可能部分区域不在光标范围内
1172
+ */
1173
+ getFocusNodesBySelection(type: 'all' | 'closed' | 'text' | undefined = 'all') {
1174
+ if (!this.selection.focused() || this.selection.collapsed()) {
1175
+ return []
1176
+ }
1177
+ const nodes: KNode[] = []
1178
+ this.getSelectedNodes().forEach(item => {
1179
+ nodes.push(...item.node.getFocusNodes(type))
1180
+ })
1181
+ return nodes
1182
+ }
1183
+
1184
+ /**
1185
+ * 获取所有在光标范围内的可聚焦节点,该方法可能会切割部分文本节点,摒弃其不在光标范围内的部分,所以也可能会更新光标的位置
1186
+ */
1187
+ getFocusSplitNodesBySelection(type: 'all' | 'closed' | 'text' | undefined = 'all') {
1188
+ if (!this.selection.focused() || this.selection.collapsed()) {
1189
+ return []
1190
+ }
1191
+ const nodes: KNode[] = []
1192
+ this.getSelectedNodes().forEach(item => {
1193
+ //文本节点
1194
+ if (item.node.isText() && (type == 'all' || type == 'text')) {
1195
+ //选择部分文本
1196
+ if (item.offset) {
1197
+ const textContent = item.node.textContent!
1198
+ //选中了文本的前半段
1199
+ if (item.offset[0] == 0) {
1200
+ const newTextNode = item.node.clone(true)
1201
+ this.addNodeAfter(newTextNode, item.node)
1202
+ item.node.textContent = textContent.substring(0, item.offset[1])
1203
+ newTextNode.textContent = textContent.substring(item.offset[1])
1204
+ nodes.push(item.node)
1205
+ }
1206
+ //选中了文本的后半段
1207
+ else if (item.offset[1] == textContent.length) {
1208
+ const newTextNode = item.node.clone(true)
1209
+ this.addNodeBefore(newTextNode, item.node)
1210
+ newTextNode.textContent = textContent.substring(0, item.offset[0])
1211
+ item.node.textContent = textContent.substring(item.offset[0])
1212
+ nodes.push(item.node)
1213
+ }
1214
+ //选中文本中间部分
1215
+ else {
1216
+ const newBeforeTextNode = item.node.clone(true)
1217
+ const newAfterTextNode = item.node.clone(true)
1218
+ this.addNodeBefore(newBeforeTextNode, item.node)
1219
+ this.addNodeAfter(newAfterTextNode, item.node)
1220
+ newBeforeTextNode.textContent = textContent.substring(0, item.offset[0])
1221
+ item.node.textContent = textContent.substring(item.offset[0], item.offset[1])
1222
+ newAfterTextNode.textContent = textContent.substring(item.offset[1])
1223
+ nodes.push(item.node)
1224
+ }
1225
+ //重置光标位置
1226
+ if (this.isSelectionInNode(item.node, 'start')) {
1227
+ this.setSelectionBefore(item.node, 'start')
1228
+ }
1229
+ if (this.isSelectionInNode(item.node, 'end')) {
1230
+ this.setSelectionAfter(item.node, 'end')
1231
+ }
1232
+ }
1233
+ //选择整个文本
1234
+ else {
1235
+ nodes.push(item.node)
1236
+ }
1237
+ }
1238
+ //闭合节点
1239
+ else if (item.node.isClosed() && (type == 'all' || type == 'closed')) {
1240
+ nodes.push(item.node)
1241
+ }
1242
+ //非文本节点存在子节点数组
1243
+ else if (item.node.hasChildren()) {
1244
+ nodes.push(...item.node.getFocusNodes(type))
1245
+ }
1246
+ })
1247
+ return nodes
1248
+ }
1249
+
1250
+ /**
1251
+ * 向选区插入文本
1252
+ */
1253
+ insertText(text: string) {
1254
+ if (!text) {
1255
+ return
1256
+ }
1257
+ if (!this.selection.focused()) {
1258
+ return
1259
+ }
1260
+ //统一将\r\n换成\n,解决Windows兼容问题
1261
+ text = text.replace(/\r\n/g, '\n')
1262
+ //起点和终点在一个位置
1263
+ if (this.selection.collapsed()) {
1264
+ const node = this.selection.start!.node
1265
+ const offset = this.selection.start!.offset
1266
+ //不是在拥有代码块样式的块级节点内,则将空格转换成&nbsp;
1267
+ if (!node.isInCodeBlockStyle()) {
1268
+ text = text.replace(/\s/g, () => {
1269
+ const span = document.createElement('span')
1270
+ span.innerHTML = '&nbsp;'
1271
+ return span.innerText
1272
+ })
1273
+ }
1274
+ //光标所在节点是文本节点
1275
+ if (node.isText()) {
1276
+ node.textContent = node.textContent!.substring(0, offset) + text + node.textContent!.substring(offset)
1277
+ this.selection.start!.offset = this.selection.end!.offset = this.selection.start!.offset + text.length
1278
+ }
1279
+ //光标所在节点是闭合节点
1280
+ else {
1281
+ const textNode = KNode.create({
1282
+ type: 'text',
1283
+ textContent: text
1284
+ })
1285
+ offset == 0 ? this.addNodeBefore(textNode, node) : this.addNodeAfter(textNode, node)
1286
+ this.setSelectionAfter(textNode)
1287
+ }
1288
+ }
1289
+ //起点和终点不在一个位置,即存在选区
1290
+ else {
1291
+ this.delete()
1292
+ this.insertText(text)
1293
+ }
1294
+ }
1295
+
1296
+ /**
1297
+ * 向选区进行换行,如果所在块节点只有占位符并且块节点不是段落则会转为段落
1298
+ */
1299
+ insertParagraph() {
1300
+ if (!this.selection.focused()) {
1301
+ return
1302
+ }
1303
+ //起点和终点在一起
1304
+ if (this.selection.collapsed()) {
1305
+ //光标所在节点
1306
+ const node = this.selection.start!.node
1307
+ //光标所在块节点
1308
+ const blockNode = node.getBlock()
1309
+ //如果在代码块样式内
1310
+ if (!!node.isInCodeBlockStyle()) {
1311
+ this.insertText('\n')
1312
+ const zeroWidthText = KNode.createZeroWidthText()
1313
+ this.insertNode(zeroWidthText)
1314
+ this.setSelectionAfter(zeroWidthText, 'all')
1315
+ if (typeof this.onInsertParagraph == 'function') {
1316
+ this.onInsertParagraph.apply(this, [blockNode])
1317
+ }
1318
+ }
1319
+ //在非代码块样式内,且不是固定的块节点
1320
+ else if (!blockNode.fixed) {
1321
+ //块节点只有占位符,并且存在父节点,且父节点不是固定块节点
1322
+ if (blockNode.allIsPlaceholder() && blockNode.parent && !blockNode.parent.fixed) {
1323
+ //将块节点从父节点中抽离到父节点同级
1324
+ removeBlockFromParentToSameLevel.apply(this, [blockNode])
1325
+ //是否走默认逻辑
1326
+ const useDefault = typeof this.onDetachMentBlockFromParentCallback == 'function' ? this.onDetachMentBlockFromParentCallback.apply(this, [blockNode]) : true
1327
+ //走默认逻辑,将非段落的块节点转为段落
1328
+ if (useDefault && !this.isParagraph(blockNode)) {
1329
+ this.toParagraph(blockNode)
1330
+ }
1331
+ //触发换行事件
1332
+ if (typeof this.onInsertParagraph == 'function') {
1333
+ this.onInsertParagraph.apply(this, [blockNode])
1334
+ }
1335
+ }
1336
+ //块节点只有占位符,并且不存在父节点,且不是段落
1337
+ else if (blockNode.allIsPlaceholder() && !blockNode.parent && !this.isParagraph(blockNode)) {
1338
+ //转为段落
1339
+ this.toParagraph(blockNode)
1340
+ //触发换行事件
1341
+ if (typeof this.onInsertParagraph == 'function') {
1342
+ this.onInsertParagraph.apply(this, [blockNode])
1343
+ }
1344
+ }
1345
+ //其他情况正常换行
1346
+ else {
1347
+ handlerForNormalInsertParagraph.apply(this)
1348
+ }
1349
+ }
1350
+ }
1351
+ //起点和终点不在一起
1352
+ else {
1353
+ this.delete()
1354
+ this.insertParagraph()
1355
+ }
1356
+ }
1357
+
1358
+ /**
1359
+ * 向选区插入节点,cover为true表示当向某个只有占位符的非固定块节点插入另一个非固定块节点时是否覆盖此节点,而不是直接插入进去
1360
+ */
1361
+ insertNode(node: KNode, cover: boolean | undefined = false) {
1362
+ //未聚焦不处理
1363
+ if (!this.selection.focused()) {
1364
+ return
1365
+ }
1366
+ //空节点不处理
1367
+ if (node.isEmpty()) {
1368
+ return
1369
+ }
1370
+ //起点和终点在一起
1371
+ if (this.selection.collapsed()) {
1372
+ //光标所在节点
1373
+ const selectionNode = this.selection.start!.node
1374
+ //光标偏移值
1375
+ const offset = this.selection.start!.offset
1376
+ //光标所在节点的块节点
1377
+ const blockNode = selectionNode.getBlock()
1378
+ //块节点的第一个可设光标的节点
1379
+ const firstSelectionNode = this.getFirstSelectionNodeInChildren(blockNode)!
1380
+ //块节点的最后一个可设光标的节点
1381
+ const lastSelectionNode = this.getLastSelectionNodeInChildren(blockNode)!
1382
+ //光标所在块节点是非固定块节点,插入的也是非固定块节点
1383
+ if (!blockNode.fixed && node.isBlock() && !node.fixed) {
1384
+ //光标在代码块样式内则将该节点转为行内节点后重新执行插入
1385
+ if (!!selectionNode.isInCodeBlockStyle()) {
1386
+ node.type = 'inline'
1387
+ this.insertNode(node, cover)
1388
+ return
1389
+ }
1390
+ //光标所在块节点只有换行符且cover为true,则替换当前块节点
1391
+ if (blockNode.allIsPlaceholder() && cover) {
1392
+ this.addNodeBefore(node, blockNode)
1393
+ blockNode.toEmpty()
1394
+ }
1395
+ //光标在块节点的起始处,则在块节点之前插入
1396
+ else if (firstSelectionNode.isEqual(selectionNode) && offset == 0) {
1397
+ this.addNodeBefore(node, blockNode)
1398
+ }
1399
+ //光标在块节点的末尾处,则在块节点之后插入
1400
+ else if (lastSelectionNode.isEqual(selectionNode) && offset == (selectionNode.isText() ? selectionNode.textContent!.length : 1)) {
1401
+ this.addNodeAfter(node, blockNode)
1402
+ }
1403
+ //光标在块节点的中间部分则需要分割
1404
+ else {
1405
+ //执行换行
1406
+ this.insertParagraph()
1407
+ //获取换行后光标位置所在的块节点
1408
+ const newBlockNode = this.selection.start!.node.getBlock()
1409
+ //在新的块节点之前插入
1410
+ this.addNodeBefore(node, newBlockNode)
1411
+ }
1412
+ }
1413
+ //其他情况
1414
+ else {
1415
+ //光标在文本节点或者闭合节点的起始处
1416
+ if (offset == 0) {
1417
+ this.addNodeBefore(node, selectionNode)
1418
+ }
1419
+ //光标在文本节点或者闭合节点的末尾处
1420
+ else if (offset == (selectionNode.isText() ? selectionNode.textContent!.length : 1)) {
1421
+ this.addNodeAfter(node, selectionNode)
1422
+ }
1423
+ //光标在文本节点内
1424
+ else {
1425
+ const val = selectionNode.textContent!
1426
+ const newTextNode = selectionNode.clone()
1427
+ selectionNode.textContent = val.substring(0, offset)
1428
+ newTextNode.textContent = val.substring(offset)
1429
+ this.addNodeAfter(newTextNode, selectionNode)
1430
+ this.addNodeBefore(node, newTextNode)
1431
+ }
1432
+ }
1433
+ //重置光标
1434
+ this.setSelectionAfter(node, 'all')
1435
+ }
1436
+ //起点和终点不在一起
1437
+ else {
1438
+ this.delete()
1439
+ this.insertNode(node, cover)
1440
+ }
1441
+ }
1442
+
1443
+ /**
1444
+ * 对选区进行删除
1445
+ */
1446
+ delete() {
1447
+ if (!this.selection.focused()) {
1448
+ return
1449
+ }
1450
+ //起点和终点在一起
1451
+ if (this.selection.collapsed()) {
1452
+ const node = this.selection.start!.node
1453
+ const uneditableNode = node.getUneditable()
1454
+ //是用户操作的删除行为并且在不可编辑的节点里,则直接删除该不可编辑的节点
1455
+ if (this.isUserDelection && uneditableNode) {
1456
+ uneditableNode.toEmpty()
1457
+ }
1458
+ //否则走正常删除逻辑
1459
+ else {
1460
+ const offset = this.selection.start!.offset
1461
+ //前一个可设置光标的节点
1462
+ const previousSelectionNode = this.getPreviousSelectionNode(node)
1463
+ //光标所在的块节点
1464
+ const blockNode = node.getBlock()
1465
+ //光标在节点的起始处
1466
+ if (offset == 0) {
1467
+ //前一个可设置光标的节点存在
1468
+ if (previousSelectionNode) {
1469
+ //获取前一个可设置光标的节点所在的块节点
1470
+ const previousBlock = previousSelectionNode.getBlock()
1471
+ //前一个可设置光标的节点和光标所在节点都属于一个块节点,则表示当前光标所在节点不是块节点的起始处,则将光标移动到前一个可设置光标的节点的末尾处再执行一次删除
1472
+ if (previousBlock.isEqual(blockNode)) {
1473
+ this.setSelectionAfter(previousSelectionNode, 'all')
1474
+ this.delete()
1475
+ return
1476
+ }
1477
+ //光标在块节点的开始处并且块节点不是固定的
1478
+ else if (!blockNode.fixed) {
1479
+ //块节点的父节点存在,且父节点不包含前一个可设置光标的节点所在的块节点(块节点在父节点的第一个),且父节点不是固定的
1480
+ if (blockNode.parent && !blockNode.parent.isContains(previousBlock) && !blockNode.parent.fixed) {
1481
+ //将块节点从父节点中抽离到父节点同级
1482
+ removeBlockFromParentToSameLevel.apply(this, [blockNode])
1483
+ //是否走默认逻辑
1484
+ const useDefault = typeof this.onDetachMentBlockFromParentCallback == 'function' ? this.onDetachMentBlockFromParentCallback.apply(this, [blockNode]) : true
1485
+ //走默认逻辑,将非段落的块节点转为段落
1486
+ if (useDefault && !this.isParagraph(blockNode)) {
1487
+ this.toParagraph(blockNode)
1488
+ }
1489
+ }
1490
+ //块节点不存在父节点,或者父节点包含前一个可设置光标的节点所在的块节点(块节点不是父节点的第一个)
1491
+ else if (!blockNode.parent || blockNode.parent.isContains(previousBlock)) {
1492
+ mergeBlock.apply(this, [previousBlock, blockNode])
1493
+ }
1494
+ }
1495
+ //是固定的块节点就不用处理
1496
+ }
1497
+ //光标在编辑器开始处
1498
+ else {
1499
+ //光标所在的块节点存在非固定的父节点
1500
+ if (blockNode.parent && !blockNode.parent.fixed) {
1501
+ //将块节点从父节点中抽离到父节点同级
1502
+ removeBlockFromParentToSameLevel.apply(this, [blockNode])
1503
+ //是否走默认逻辑
1504
+ const useDefault = typeof this.onDetachMentBlockFromParentCallback == 'function' ? this.onDetachMentBlockFromParentCallback.apply(this, [blockNode]) : true
1505
+ //走默认逻辑,将非段落的块节点转为段落
1506
+ if (useDefault && !this.isParagraph(blockNode)) {
1507
+ this.toParagraph(blockNode)
1508
+ }
1509
+ }
1510
+ //光标所在的块节点非段落节点,且非固定块节点,且不存在父节点
1511
+ else if (!this.isParagraph(blockNode) && !blockNode.fixed && !blockNode.parent) {
1512
+ //转为段落
1513
+ this.toParagraph(blockNode)
1514
+ }
1515
+ }
1516
+ }
1517
+ //光标在所在节点的内部
1518
+ else {
1519
+ //在空白文本节点内
1520
+ if (node.isZeroWidthText()) {
1521
+ //置空节点
1522
+ node.toEmpty()
1523
+ //在清除空白文本节点后判断块节点是否为空,是则建立占位符
1524
+ if (blockNode.isEmpty()) {
1525
+ const placeholderNode = KNode.createPlaceholder()
1526
+ this.addNode(placeholderNode, blockNode)
1527
+ this.setSelectionBefore(placeholderNode)
1528
+ }
1529
+ //块节点不是空,将光标移动到该节点前面
1530
+ else {
1531
+ this.selection.start!.node = this.selection.end!.node = node
1532
+ this.selection.start!.offset = this.selection.end!.offset = 0
1533
+ }
1534
+ //再执行一次删除
1535
+ this.delete()
1536
+ return
1537
+ }
1538
+ //在文本节点内
1539
+ else if (node.isText()) {
1540
+ //获取删除的字符
1541
+ const deleteChart = node.textContent!.substring(offset - 1, offset)
1542
+ //进行删除
1543
+ node.textContent = node.textContent!.substring(0, offset - 1) + node.textContent!.substring(offset)
1544
+ //更新光标到删除后的位置
1545
+ this.selection.start!.offset = offset - 1
1546
+ this.selection.end!.offset = offset - 1
1547
+ //删除的是空白字符,再次删除
1548
+ if (isZeroWidthText(deleteChart)) {
1549
+ this.delete()
1550
+ return
1551
+ }
1552
+ //块节点为空,创建占位符
1553
+ if (blockNode.isEmpty()) {
1554
+ const placeholderNode = KNode.createPlaceholder()
1555
+ this.addNode(placeholderNode, blockNode)
1556
+ this.setSelectionBefore(placeholderNode)
1557
+ }
1558
+ }
1559
+ //在闭合节点内
1560
+ else if (node.isClosed()) {
1561
+ //是否占位符
1562
+ const isPlaceholder = node.isPlaceholder()
1563
+ //删除闭合节点
1564
+ node.toEmpty()
1565
+ //块节点为空
1566
+ if (blockNode.isEmpty()) {
1567
+ //删除的是占位符
1568
+ if (isPlaceholder) {
1569
+ //块节点是固定状态的,则创建占位符;
1570
+ if (blockNode.fixed) {
1571
+ const placeholderNode = KNode.createPlaceholder()
1572
+ this.addNode(placeholderNode, blockNode)
1573
+ this.setSelectionBefore(placeholderNode)
1574
+ }
1575
+ //块节点不是固定状态的,且前一个可获取光标的节点不存在则说明光标在编辑器起始处,创建占位符
1576
+ else if (!blockNode.fixed && !previousSelectionNode) {
1577
+ const placeholderNode = KNode.createPlaceholder()
1578
+ this.addNode(placeholderNode, blockNode)
1579
+ this.setSelectionBefore(placeholderNode)
1580
+ //转为段落
1581
+ blockNode.tag = this.blockRenderTag
1582
+ blockNode.marks = {}
1583
+ blockNode.styles = {}
1584
+ }
1585
+ //其余情况就是块节点被删除,光标自动更新到附近位置
1586
+ }
1587
+ //删除的不是占位符
1588
+ else {
1589
+ const placeholderNode = KNode.createPlaceholder()
1590
+ this.addNode(placeholderNode, blockNode)
1591
+ this.setSelectionBefore(placeholderNode)
1592
+ }
1593
+ }
1594
+ }
1595
+ }
1596
+ }
1597
+ }
1598
+ //起点和终点不在一起
1599
+ else {
1600
+ //获取选区内的节点信息
1601
+ const result = this.getSelectedNodes().filter(item => {
1602
+ //批量删除时需要过滤掉那些不显示的节点
1603
+ return !item.node.void
1604
+ })
1605
+ //起点所在块节点
1606
+ const startBlockNode = this.selection.start!.node.getBlock()
1607
+ //终点所在块节点
1608
+ const endBlockNode = this.selection.end!.node.getBlock()
1609
+ result.forEach(item => {
1610
+ const { node, offset } = item
1611
+ //是数组的情况,说明node是文本节点,需要进行裁剪
1612
+ if (offset) {
1613
+ node.textContent = node.textContent!.substring(0, offset[0]) + node.textContent!.substring(offset[1])
1614
+ }
1615
+ //说明节点都在选区内
1616
+ else {
1617
+ //固定状态的块节点,进行清空处理
1618
+ if (node.isBlock() && node.fixed) {
1619
+ emptyFixedBlock.apply(this, [node])
1620
+ }
1621
+ //其他节点置空进行删除
1622
+ else {
1623
+ node.toEmpty()
1624
+ }
1625
+ }
1626
+ })
1627
+ //起点和终点在同一个块节点下
1628
+ if (startBlockNode.isEqual(endBlockNode)) {
1629
+ //块节点为空创建占位符
1630
+ if (startBlockNode.isEmpty()) {
1631
+ const placeholder = KNode.createPlaceholder()
1632
+ this.addNode(placeholder, startBlockNode)
1633
+ this.setSelectionBefore(placeholder)
1634
+ }
1635
+ }
1636
+ //起点和终点不在一个块节点的情况下,需要考虑块节点合并
1637
+ else {
1638
+ //起点所在块节点为空,创建占位符
1639
+ if (startBlockNode.isEmpty()) {
1640
+ const placeholder = KNode.createPlaceholder()
1641
+ this.addNode(placeholder, startBlockNode)
1642
+ this.setSelectionBefore(placeholder, 'start')
1643
+ }
1644
+ //终点所在块节点为空,创建占位符
1645
+ if (endBlockNode.isEmpty()) {
1646
+ const placeholder = KNode.createPlaceholder()
1647
+ this.addNode(placeholder, endBlockNode)
1648
+ this.setSelectionBefore(placeholder, 'end')
1649
+ }
1650
+ //不是固定的块节点
1651
+ if (!endBlockNode.fixed) {
1652
+ mergeBlock.apply(this, [startBlockNode, endBlockNode])
1653
+ }
1654
+ }
1655
+ }
1656
+ //如果起点所在节点为空则更新起点
1657
+ if (this.selection.start!.node.isEmpty()) {
1658
+ this.updateSelectionRecently('start')
1659
+ }
1660
+ //合并起点和终点
1661
+ this.selection.end!.node = this.selection.start!.node
1662
+ this.selection.end!.offset = this.selection.start!.offset
1663
+ //触发事件
1664
+ if (typeof this.onDeleteComplete == 'function') {
1665
+ this.onDeleteComplete.apply(this)
1666
+ }
1667
+ }
1668
+
1669
+ /**
1670
+ * 更新编辑器视图
1671
+ */
1672
+ async updateView(updateRealSelection: boolean | undefined = true, unPushHistory: boolean | undefined = false) {
1673
+ if (!this.$el) {
1674
+ return
1675
+ }
1676
+ //视图更新前回调
1677
+ if (typeof this.beforeUpdateView == 'function') this.beforeUpdateView.apply(this)
1678
+ //克隆旧节点数组,防止在patch过程中旧节点数组中存在null,影响后续的视图更新
1679
+ const oldStackNodes = this.oldStackNodes.map(item => item.fullClone())
1680
+ //这里存放格式化过的节点数组,后续进行判断避免重复格式化造成资源浪费和性能问题
1681
+ const hasUpdateNodes: KNode[] = []
1682
+ //对编辑器的新旧节点数组进行比对,遍历比对的结果进行动态格式化
1683
+ patchNodes(this.stackNodes, oldStackNodes).forEach(item => {
1684
+ //需要进行格式化的节点
1685
+ let node: KNode | null = null
1686
+ //有新节点表示insert、update、replace、move和empty
1687
+ if (item.newNode) {
1688
+ //优先从父节点开始格式化
1689
+ node = item.newNode.parent ? item.newNode.parent : item.newNode
1690
+ }
1691
+ //没有新节点但是有旧节点,表示remove,如果是根级块节点remove就不用处理,非根级块节点需要对新数组中的父节点进行格式化
1692
+ else if (item.oldNode && item.oldNode.parent) {
1693
+ //获取新节点数组中的父节点
1694
+ const parentNode = KNode.searchByKey(item.oldNode.parent.key, this.stackNodes)
1695
+ //如果存在对父节点进行格式化
1696
+ node = parentNode ? parentNode : null
1697
+ }
1698
+ //是否有需要格式化的节点
1699
+ if (node) {
1700
+ //针对需要格式化的节点存在一些特殊处理
1701
+ if (typeof this.beforePatchNodeToFormat == 'function') {
1702
+ node = this.beforePatchNodeToFormat.apply(this, [node])
1703
+ }
1704
+ //判断该节点是否格式化过
1705
+ const hasUpdate = hasUpdateNodes.some(n => {
1706
+ //已经在hasUpdateNodes数组里了,说明格式化过了
1707
+ if (n.isContains(node!)) {
1708
+ return true
1709
+ }
1710
+ //需要格式化的节点有父节点,并且hasUpdateNodes也存在同父节点的节点
1711
+ if (node!.parent && n.parent && n.parent.isContains(node!.parent)) {
1712
+ return true
1713
+ }
1714
+ //其他情况说明没有格式化过
1715
+ return false
1716
+ })
1717
+ //没有格式化过则进行格式化
1718
+ if (!hasUpdate) {
1719
+ //加入到已经格式化的节点数组里
1720
+ hasUpdateNodes.push(node)
1721
+ //格式化
1722
+ this.formatRules.forEach(rule => {
1723
+ let nodes: KNode[] = []
1724
+ if (node!.parent) {
1725
+ nodes = node!.parent.children!
1726
+ } else {
1727
+ //没有父节点的根级节点默认格式化当前节点的前后加起来三个节点
1728
+ const previousNode = node!.getPrevious(this.stackNodes)
1729
+ const nextNode = node!.getNext(this.stackNodes)
1730
+ if (previousNode) {
1731
+ nodes.push(previousNode)
1732
+ }
1733
+ nodes.push(node!)
1734
+ if (nextNode) {
1735
+ nodes.push(nextNode)
1736
+ }
1737
+ }
1738
+ const sourceNodes = node!.parent ? node!.parent.children! : this.stackNodes
1739
+ //需要格式化的节点存在于源数组内才需要进行格式化
1740
+ if (sourceNodes.some(n => n.isEqual(node!))) {
1741
+ formatNodes.apply(this, [rule, nodes, sourceNodes])
1742
+ }
1743
+ })
1744
+ }
1745
+ }
1746
+ })
1747
+ //判断节点数组是否为空进行初始化
1748
+ checkNodes.apply(this)
1749
+ //设置placeholder
1750
+ setPlaceholder.apply(this)
1751
+ //旧的html值
1752
+ const oldHtml = this.$el.innerHTML
1753
+ //视图更新之前取消dom监听,以免干扰更新dom
1754
+ removeDomObserve(this)
1755
+ //此处进行视图的更新
1756
+ const useDefault = typeof this.onUpdateView == 'function' ? await this.onUpdateView.apply(this, [false]) : true
1757
+ //使用默认逻辑
1758
+ useDefault && defaultUpdateView.apply(this, [false])
1759
+ //视图更新完毕后重新设置dom监听
1760
+ setDomObserve(this)
1761
+ //新的html值
1762
+ const newHtml = this.$el.innerHTML
1763
+ //html值发生变化
1764
+ if (oldHtml != newHtml) {
1765
+ if (typeof this.onChange == 'function') {
1766
+ this.onChange.apply(this, [newHtml, oldHtml])
1767
+ }
1768
+ //如果unPushHistory为false,则加入历史记录
1769
+ if (!unPushHistory) {
1770
+ this.history.setState(this.stackNodes, this.selection)
1771
+ }
1772
+ }
1773
+ //更新旧节点数组
1774
+ this.oldStackNodes = this.stackNodes.map(item => item.fullClone())
1775
+ //此处进行光标的渲染
1776
+ if (updateRealSelection) await this.updateRealSelection()
1777
+ //视图更新后回调
1778
+ if (typeof this.afterUpdateView == 'function') this.afterUpdateView.apply(this)
1779
+ }
1780
+
1781
+ /**
1782
+ * 根据selection更新编辑器真实光标
1783
+ */
1784
+ async updateRealSelection() {
1785
+ const realSelection = window.getSelection()
1786
+ if (!realSelection) {
1787
+ return
1788
+ }
1789
+ this.internalCauseSelectionChange = true
1790
+ //聚焦情况下
1791
+ if (this.selection.focused()) {
1792
+ //先进行纠正
1793
+ redressSelection.apply(this)
1794
+ //更新真实光标
1795
+ const range = document.createRange()
1796
+ const startDom = this.findDom(this.selection.start!.node)
1797
+ const endDom = this.findDom(this.selection.end!.node)
1798
+ //起点所在节点是文本节点
1799
+ if (this.selection.start!.node.isText()) {
1800
+ range.setStart(startDom.childNodes[0], this.selection.start!.offset)
1801
+ }
1802
+ //起点所在节点是闭合节点
1803
+ else if (this.selection.start!.node.isClosed()) {
1804
+ const index = this.selection.start!.node.parent!.children!.findIndex(item => this.selection.start!.node.isEqual(item))
1805
+ range.setStart(startDom.parentNode!, this.selection.start!.offset + index)
1806
+ }
1807
+ //终点所在节点是文本节点
1808
+ if (this.selection.end!.node.isText()) {
1809
+ range.setEnd(endDom.childNodes[0], this.selection.end!.offset)
1810
+ }
1811
+ //终点所在节点是闭合节点
1812
+ else if (this.selection.end!.node.isClosed()) {
1813
+ const index = this.selection.end!.node.parent!.children!.findIndex(item => this.selection.end!.node.isEqual(item))
1814
+ range.setEnd(endDom.parentNode!, this.selection.end!.offset + index)
1815
+ }
1816
+ //设置真实光标
1817
+ realSelection.removeAllRanges()
1818
+ realSelection.addRange(range)
1819
+ }
1820
+ //没有聚焦
1821
+ else {
1822
+ realSelection.removeAllRanges()
1823
+ }
1824
+ await delay()
1825
+ this.internalCauseSelectionChange = false
1826
+ this.scrollViewToSelection()
1827
+ this.history.updateSelection(this.selection)
1828
+ if (typeof this.onSelectionUpdate == 'function') {
1829
+ this.onSelectionUpdate.apply(this, [this.selection])
1830
+ }
1831
+ }
1832
+
1833
+ /**
1834
+ * 重新渲染编辑器视图,不会触发onChange
1835
+ */
1836
+ async review(value: string) {
1837
+ //视图更新前回调
1838
+ if (typeof this.beforeUpdateView == 'function') this.beforeUpdateView.apply(this)
1839
+ //根据value设置节点数组
1840
+ this.stackNodes = this.htmlParseNode(value || '')
1841
+ //将节点数组进行格式化
1842
+ this.formatRules.forEach(rule => {
1843
+ formatNodes.apply(this, [rule, this.stackNodes, this.stackNodes])
1844
+ })
1845
+ //初始化检查节点数组
1846
+ checkNodes.apply(this)
1847
+ //设置placeholder
1848
+ setPlaceholder.apply(this)
1849
+ //视图更新之前取消dom监听,以免干扰更新dom
1850
+ removeDomObserve(this)
1851
+ //进行视图的渲染
1852
+ const useDefault = typeof this.onUpdateView == 'function' ? await this.onUpdateView.apply(this, [true]) : true
1853
+ //使用默认逻辑
1854
+ if (useDefault) defaultUpdateView.apply(this, [true])
1855
+ //视图更新完毕后重新设置dom监听
1856
+ setDomObserve(this)
1857
+ //设置历史记录
1858
+ this.history.setState(this.stackNodes, this.selection)
1859
+ //更新旧节点数组
1860
+ this.oldStackNodes = this.stackNodes.map(item => item.fullClone())
1861
+ //视图更新后回调
1862
+ if (typeof this.afterUpdateView == 'function') this.afterUpdateView.apply(this)
1863
+ }
1864
+
1865
+ /**
1866
+ * 销毁编辑器的方法
1867
+ */
1868
+ destroy() {
1869
+ //去除可编辑效果
1870
+ this.setEditable(false)
1871
+ //移除相关监听事件
1872
+ DapEvent.off(document, `selectionchange.kaitify_${this.guid}`)
1873
+ DapEvent.off(this.$el!, 'beforeinput.kaitify compositionstart.kaitify compositionupdate.kaitify compositionend.kaitify keydown.kaitify keyup.kaitify copy.kaitify focus.kaitify blur.kaitify')
1874
+ }
1875
+
1876
+ /**
1877
+ * 获取编辑器的纯文本内容
1878
+ */
1879
+ getText() {
1880
+ if (!this.$el) {
1881
+ return ''
1882
+ }
1883
+ return this.$el.innerText
1884
+ }
1885
+
1886
+ /**
1887
+ * 配置编辑器,返回创建的编辑器
1888
+ */
1889
+ static async configure(options: EditorConfigureOptionType) {
1890
+ //创建编辑器
1891
+ const editor = new Editor()
1892
+ //初始化编辑器dom
1893
+ editor.$el = initEditorDom(options.el)
1894
+ //初始化设置编辑器样式
1895
+ editor.$el.classList.add('Kaitify')
1896
+ //设置是否深色模式
1897
+ if (options.dark) editor.setDark(options.dark)
1898
+ //初始化设置编辑器默认提示文本
1899
+ if (options.placeholder) editor.$el.setAttribute('kaitify-placeholder', options.placeholder)
1900
+ //初始化内部属性
1901
+ if (typeof options.allowCopy == 'boolean') editor.allowCopy = options.allowCopy
1902
+ if (typeof options.allowCut == 'boolean') editor.allowCut = options.allowCut
1903
+ if (typeof options.allowPaste == 'boolean') editor.allowPaste = options.allowPaste
1904
+ if (typeof options.allowPasteHtml == 'boolean') editor.allowPasteHtml = options.allowPasteHtml
1905
+ if (typeof options.priorityPasteFiles == 'boolean') editor.priorityPasteFiles = options.priorityPasteFiles
1906
+ if (options.textRenderTag) editor.textRenderTag = options.textRenderTag
1907
+ if (options.blockRenderTag) editor.blockRenderTag = options.blockRenderTag
1908
+ if (options.emptyRenderTags) editor.emptyRenderTags = [...editor.emptyRenderTags, ...options.emptyRenderTags]
1909
+ if (options.extraKeepTags) editor.extraKeepTags = [...editor.extraKeepTags, ...options.extraKeepTags]
1910
+ if (options.extensions) editor.extensions = mergeExtensions.apply(editor, [options.extensions])
1911
+ if (options.formatRules) editor.formatRules = [...options.formatRules, ...editor.formatRules]
1912
+ if (options.domParseNodeCallback) editor.domParseNodeCallback = options.domParseNodeCallback
1913
+ if (options.onUpdateView) editor.onUpdateView = options.onUpdateView
1914
+ if (options.onPasteText) editor.onPasteText = options.onPasteText
1915
+ if (options.onPasteHtml) editor.onPasteHtml = options.onPasteHtml
1916
+ if (options.onPasteImage) editor.onPasteImage = options.onPasteImage
1917
+ if (options.onPasteVideo) editor.onPasteVideo = options.onPasteVideo
1918
+ if (options.onPasteFile) editor.onPasteFile = options.onPasteFile
1919
+ if (options.onChange) editor.onChange = options.onChange
1920
+ if (options.onSelectionUpdate) editor.onSelectionUpdate = options.onSelectionUpdate
1921
+ if (options.onInsertParagraph) editor.onInsertParagraph = options.onInsertParagraph
1922
+ if (options.onDeleteComplete) editor.onDeleteComplete = options.onDeleteComplete
1923
+ if (options.onKeydown) editor.onKeydown = options.onKeydown
1924
+ if (options.onKeyup) editor.onKeyup = options.onKeyup
1925
+ if (options.onFocus) editor.onFocus = options.onFocus
1926
+ if (options.onBlur) editor.onBlur = options.onBlur
1927
+ if (options.pasteKeepMarks) editor.pasteKeepMarks = options.pasteKeepMarks
1928
+ if (options.pasteKeepStyles) editor.pasteKeepStyles = options.pasteKeepStyles
1929
+ if (options.beforeUpdateView) editor.beforeUpdateView = options.beforeUpdateView
1930
+ if (options.afterUpdateView) editor.afterUpdateView = options.afterUpdateView
1931
+ if (options.onDetachMentBlockFromParentCallback) editor.onDetachMentBlockFromParentCallback = options.onDetachMentBlockFromParentCallback
1932
+ if (options.beforePatchNodeToFormat) editor.beforePatchNodeToFormat = options.beforePatchNodeToFormat
1933
+ //注册扩展
1934
+ editor.extensions.forEach(item => registerExtension.apply(editor, [item]))
1935
+ //设置编辑器是否可编辑
1936
+ editor.setEditable(typeof options.editable == 'boolean' ? options.editable : true)
1937
+ //视图渲染
1938
+ await editor.review(options.value || '')
1939
+ //自动聚焦
1940
+ if (options.autofocus) {
1941
+ editor.setSelectionAfter()
1942
+ await editor.updateRealSelection()
1943
+ }
1944
+ //监听js selection更新Selection
1945
+ DapEvent.on(document, `selectionchange.kaitify_${editor.guid}`, onSelectionChange.bind(editor))
1946
+ //监听内容输入
1947
+ DapEvent.on(editor.$el, 'beforeinput.kaitify', onBeforeInput.bind(editor))
1948
+ //监听中文输入
1949
+ DapEvent.on(editor.$el, 'compositionstart.kaitify compositionupdate.kaitify compositionend.kaitify', onComposition.bind(editor))
1950
+ //监听键盘事件
1951
+ DapEvent.on(editor.$el, 'keydown.kaitify keyup.kaitify', onKeyboard.bind(editor))
1952
+ //监听编辑器获取焦点
1953
+ DapEvent.on(editor.$el, 'focus.kaitify', onFocus.bind(editor))
1954
+ //监听编辑器失去焦点
1955
+ DapEvent.on(editor.$el, 'blur.kaitify', onBlur.bind(editor))
1956
+ //监听编辑器复制
1957
+ DapEvent.on(editor.$el, 'copy.kaitify', onCopy.bind(editor))
1958
+ //监听编辑器剪切
1959
+ DapEvent.on(editor.$el, 'cut.kaitify', onCut.bind(editor))
1960
+ //返回编辑器实例
1961
+ return editor
1962
+ }
1963
+ }