@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,677 @@
1
+ import { common as DapCommon } from 'dap-util'
2
+ import { createUniqueKey, getZeroWidthText, isZeroWidthText } from '@/tools'
3
+ import * as CSS from 'csstype'
4
+ /**
5
+ * 节点类型
6
+ */
7
+ export type KNodeType = 'text' | 'closed' | 'inline' | 'block'
8
+
9
+ /**
10
+ * 标记集合类型
11
+ */
12
+ export type KNodeMarksType = {
13
+ [mark: string]: string | number
14
+ }
15
+ /**
16
+ * 样式集合类型
17
+ */
18
+ export type KNodeStylesType = CSS.Properties<string | number> & {
19
+ [style: string]: string | number
20
+ }
21
+
22
+ /**
23
+ * 节点匹配入参类型
24
+ */
25
+ export type KNodeMatchOptionType = {
26
+ tag?: string
27
+ marks?:
28
+ | KNodeMarksType
29
+ | {
30
+ [mark: string]: boolean
31
+ }
32
+ styles?:
33
+ | KNodeStylesType
34
+ | {
35
+ [style: string]: boolean
36
+ }
37
+ }
38
+
39
+ /**
40
+ * 创建节点的入参类型
41
+ */
42
+ export type KNodeCreateOptionType = {
43
+ type: KNodeType
44
+ tag?: string
45
+ marks?: KNodeMarksType
46
+ styles?: KNodeStylesType
47
+ namespace?: string
48
+ textContent?: string
49
+ locked?: boolean
50
+ fixed?: boolean
51
+ nested?: boolean
52
+ void?: boolean
53
+ children?: KNodeCreateOptionType[]
54
+ }
55
+
56
+ /**
57
+ * 创建零宽度无断空白文本节点的入参类型
58
+ */
59
+ export type ZeroWidthTextKNodeCreateOptionType = {
60
+ marks?: KNodeMarksType
61
+ styles?: KNodeStylesType
62
+ namespace?: string
63
+ locked?: boolean
64
+ }
65
+
66
+ /**
67
+ * 基本节点
68
+ */
69
+ export class KNode {
70
+ /**
71
+ * 唯一key【不可修改】
72
+ */
73
+ key: number = createUniqueKey()
74
+ /**
75
+ * 类型【可以修改】
76
+ */
77
+ type?: KNodeType
78
+ /**
79
+ * 渲染标签【可以修改】
80
+ */
81
+ tag?: string
82
+ /**
83
+ * 文本值【可以修改】
84
+ */
85
+ textContent?: string
86
+ /**
87
+ * 标记集合【可以修改】
88
+ */
89
+ marks?: KNodeMarksType
90
+ /**
91
+ * 样式集合,样式名称请使用驼峰写法,虽然在渲染时兼容处理了中划线格式的样式名称,但是在其他地方可能会出现问题并且编辑器内部在样式相关的判断都是以驼峰写法为主【可以修改】
92
+ */
93
+ styles?: KNodeStylesType
94
+ /**
95
+ * 是否锁定节点【可以修改】:
96
+ * 针对块节点,在符合合并条件的情况下是否允许编辑器将其与父节点或者子节点进行合并;
97
+ * 针对行内节点,在符合合并条件的情况下是否允许编辑器将其与相邻节点或者父节点或者子节点进行合并;
98
+ * 针对文本节点,在符合合并的条件下是否允许编辑器将其与相邻节点或者父节点进行合并。
99
+ */
100
+ locked: boolean = false
101
+ /**
102
+ * 是否为固定块节点,值为true时:当光标在节点起始处或者光标在节点内只有占位符时,执行删除操作不会删除此节点,会再次创建一个占位符进行处理;当光标在节点内且节点不是代码块样式,不会进行换行【可以修改】
103
+ */
104
+ fixed: boolean = false
105
+ /**
106
+ * 是否为固定格式的内嵌块节点,如li、tr、td等【可以修改】
107
+ */
108
+ nested: boolean = false
109
+ /**
110
+ * 是否为不可见节点,意味着此类节点在编辑器内视图内无法看到
111
+ */
112
+ void?: boolean = false
113
+ /**
114
+ * 命名空间【可以修改】
115
+ */
116
+ namespace?: string
117
+ /**
118
+ * 子节点数组【可以修改】
119
+ */
120
+ children?: KNode[]
121
+ /**
122
+ * 父节点【可以修改】
123
+ */
124
+ parent?: KNode
125
+
126
+ /**
127
+ * 【API】是否块节点
128
+ */
129
+ isBlock() {
130
+ return this.type == 'block'
131
+ }
132
+
133
+ /**
134
+ * 【API】是否行内节点
135
+ */
136
+ isInline() {
137
+ return this.type == 'inline'
138
+ }
139
+
140
+ /**
141
+ * 【API】是否闭合节点
142
+ */
143
+ isClosed() {
144
+ return this.type == 'closed'
145
+ }
146
+
147
+ /**
148
+ * 【API】是否文本节点
149
+ */
150
+ isText() {
151
+ return this.type == 'text'
152
+ }
153
+
154
+ /**
155
+ * 【API】获取所在的根级块节点
156
+ */
157
+ getRootBlock(): KNode {
158
+ if (this.isBlock() && !this.parent) {
159
+ return this
160
+ }
161
+ return this.parent!.getRootBlock()
162
+ }
163
+
164
+ /**
165
+ * 【API】获取所在块级节点
166
+ */
167
+ getBlock(): KNode {
168
+ if (this.isBlock()) {
169
+ return this
170
+ }
171
+ return this.parent!.getBlock()
172
+ }
173
+
174
+ /**
175
+ * 【API】获取所在行内节点
176
+ */
177
+ getInline(): KNode | null {
178
+ if (this.isInline()) {
179
+ return this
180
+ }
181
+ if (!this.parent) {
182
+ return null
183
+ }
184
+ return this.parent.getInline()
185
+ }
186
+
187
+ /**
188
+ * 【API】是否有子节点
189
+ */
190
+ hasChildren() {
191
+ if (this.isText() || this.isClosed()) {
192
+ return false
193
+ }
194
+ return Array.isArray(this.children) && !!this.children.length
195
+ }
196
+
197
+ /**
198
+ * 【API】是否空节点
199
+ */
200
+ isEmpty(): boolean {
201
+ if (this.isText()) {
202
+ return !this.textContent
203
+ }
204
+ if (this.isInline() || this.isBlock()) {
205
+ return (
206
+ !this.hasChildren() ||
207
+ this.children!.every(item => {
208
+ return item.isEmpty()
209
+ })
210
+ )
211
+ }
212
+ return false
213
+ }
214
+
215
+ /**
216
+ * 【API】是否零宽度无断空白文本节点
217
+ */
218
+ isZeroWidthText() {
219
+ return this.isText() && !this.isEmpty() && isZeroWidthText(this.textContent!)
220
+ }
221
+
222
+ /**
223
+ * 【API】是否占位符
224
+ */
225
+ isPlaceholder() {
226
+ return this.isClosed() && this.tag == 'br'
227
+ }
228
+
229
+ /**
230
+ * 【API】是否含有标记
231
+ */
232
+ hasMarks() {
233
+ if (!this.marks) {
234
+ return false
235
+ }
236
+ if (DapCommon.isObject(this.marks)) {
237
+ return !DapCommon.isEmptyObject(this.marks)
238
+ }
239
+ return false
240
+ }
241
+
242
+ /**
243
+ * 【API】是否含有样式
244
+ */
245
+ hasStyles() {
246
+ if (!this.styles) {
247
+ return false
248
+ }
249
+ if (DapCommon.isObject(this.styles)) {
250
+ return !DapCommon.isEmptyObject(this.styles)
251
+ }
252
+ return false
253
+ }
254
+
255
+ /**
256
+ * 【API】判断节点是否不可编辑的,如果是返回设置不可编辑的那个节点,否则返回null
257
+ */
258
+ getUneditable(): KNode | null {
259
+ if (this.hasMarks() && this.marks!['contenteditable'] == 'false') {
260
+ return this
261
+ }
262
+ if (!this.parent) {
263
+ return null
264
+ }
265
+ return this.parent.getUneditable()
266
+ }
267
+
268
+ /**
269
+ * 【API】当前节点是否只包含占位符
270
+ */
271
+ allIsPlaceholder() {
272
+ if (this.hasChildren()) {
273
+ const nodes = this.children!.filter(item => !item.isEmpty())
274
+ return !!nodes.length && nodes.every(el => el.isPlaceholder())
275
+ }
276
+ return false
277
+ }
278
+
279
+ /**
280
+ * 【API】设置为空节点
281
+ */
282
+ toEmpty() {
283
+ if (this.isEmpty()) {
284
+ return
285
+ }
286
+ if (this.isText()) {
287
+ this.textContent = undefined
288
+ return
289
+ }
290
+ if (this.isClosed()) {
291
+ this.type = 'text'
292
+ this.textContent = undefined
293
+ return
294
+ }
295
+ if (this.hasChildren()) {
296
+ this.children!.forEach(item => {
297
+ item.toEmpty()
298
+ })
299
+ }
300
+ }
301
+
302
+ /**
303
+ * 【API】比较当前节点和另一个节点的styles是否一致
304
+ */
305
+ isEqualStyles(node: KNode) {
306
+ if (!this.hasStyles() && !node.hasStyles()) {
307
+ return true
308
+ }
309
+ if (this.hasStyles() && node.hasStyles() && DapCommon.equal(this.styles, node.styles)) {
310
+ return true
311
+ }
312
+ return false
313
+ }
314
+
315
+ /**
316
+ * 【API】比较当前节点和另一个节点的marks是否一致
317
+ */
318
+ isEqualMarks(node: KNode) {
319
+ if (!this.hasMarks() && !node.hasMarks()) {
320
+ return true
321
+ }
322
+ if (this.hasMarks() && node.hasMarks() && DapCommon.equal(this.marks, node.marks)) {
323
+ return true
324
+ }
325
+ return false
326
+ }
327
+
328
+ /**
329
+ * 【API】判断当前节点是否在拥有代码块样式的块级节点内(包括自身)
330
+ */
331
+ isInCodeBlockStyle(): KNode | null {
332
+ const block = this.getBlock()
333
+ if (block.tag == 'pre') {
334
+ return block
335
+ }
336
+ const whiteSpace = block.hasStyles() ? block.styles!.whiteSpace || '' : ''
337
+ if (['pre', 'pre-wrap'].includes(whiteSpace)) {
338
+ return block
339
+ }
340
+ return block.parent ? block.parent.isInCodeBlockStyle() : null
341
+ }
342
+
343
+ /**
344
+ * 【API】判断当前节点是否与另一个节点相同
345
+ */
346
+ isEqual(node: KNode) {
347
+ if (!KNode.isKNode(node)) {
348
+ return false
349
+ }
350
+ return this.key == node.key
351
+ }
352
+
353
+ /**
354
+ * 【API】判断当前节点是否包含指定节点
355
+ */
356
+ isContains(node: KNode): boolean {
357
+ if (this.isEqual(node)) {
358
+ return true
359
+ }
360
+ if (this.isClosed() || this.isText()) {
361
+ return false
362
+ }
363
+ if (!node.parent) {
364
+ return false
365
+ }
366
+ return this.isContains(node.parent)
367
+ }
368
+
369
+ /**
370
+ * 【API】复制节点,deep 为true表示深度复制,即复制子节点,否则只会复制自身
371
+ */
372
+ clone = (deep: boolean | undefined = true) => {
373
+ const newNode = KNode.create({
374
+ type: this.type!,
375
+ tag: this.tag,
376
+ marks: DapCommon.clone(this.marks),
377
+ styles: DapCommon.clone(this.styles),
378
+ namespace: this.namespace,
379
+ textContent: this.textContent,
380
+ locked: this.locked,
381
+ fixed: this.fixed,
382
+ nested: this.nested,
383
+ void: this.void
384
+ })
385
+ if (deep && this.hasChildren()) {
386
+ this.children!.forEach(child => {
387
+ const newChild = child.clone(deep)
388
+ if (newNode.hasChildren()) {
389
+ newNode.children!.push(newChild)
390
+ } else {
391
+ newNode.children = [newChild]
392
+ }
393
+ newChild.parent = newNode
394
+ })
395
+ }
396
+ return newNode
397
+ }
398
+
399
+ /**
400
+ * 完全复制节点,涵盖每个属性
401
+ */
402
+ fullClone() {
403
+ const newNode = KNode.create({
404
+ type: this.type!,
405
+ tag: this.tag,
406
+ marks: DapCommon.clone(this.marks),
407
+ styles: DapCommon.clone(this.styles),
408
+ namespace: this.namespace,
409
+ textContent: this.textContent,
410
+ locked: this.locked,
411
+ fixed: this.fixed,
412
+ nested: this.nested,
413
+ void: this.void
414
+ })
415
+ newNode.key = this.key
416
+ if (this.hasChildren()) {
417
+ this.children!.forEach(child => {
418
+ const newChild = child.fullClone()
419
+ if (newNode.hasChildren()) {
420
+ newNode.children!.push(newChild)
421
+ } else {
422
+ newNode.children = [newChild]
423
+ }
424
+ newChild.parent = newNode
425
+ })
426
+ }
427
+ return newNode
428
+ }
429
+
430
+ /**
431
+ * 【API】如果当前节点是文本节点或者闭合节点,则判断是不是指定节点后代中所有文本节点和闭合节点中的第一个
432
+ */
433
+ firstTextClosedInNode = (node: KNode): boolean => {
434
+ //不是闭合节点和文本节点
435
+ if (!this.isText() && !this.isClosed()) {
436
+ return false
437
+ }
438
+ //是同一个节点
439
+ if (this.isEqual(node)) {
440
+ return true
441
+ }
442
+ //目标节点包含当前节点
443
+ if (node.isContains(this) && node.hasChildren()) {
444
+ //获取第一个子节点
445
+ const firstChild = node.children![0]
446
+ return this.firstTextClosedInNode(firstChild)
447
+ }
448
+ return false
449
+ }
450
+
451
+ /**
452
+ * 【API】如果当前节点是文本节点或者闭合节点,则判断是不是指定节点后代中所有文本节点和闭合节点中的最后一个
453
+ */
454
+ lastTextClosedInNode(node: KNode): boolean {
455
+ //不是闭合节点和文本节点
456
+ if (!this.isText() && !this.isClosed()) {
457
+ return false
458
+ }
459
+ //是同一个节点
460
+ if (this.isEqual(node)) {
461
+ return true
462
+ }
463
+ //目标节点包含当前节点
464
+ if (node.isContains(this) && node.hasChildren()) {
465
+ //获取最后一个子节点
466
+ const lastChild = node.children![node.children!.length - 1]
467
+ return this.lastTextClosedInNode(lastChild)
468
+ }
469
+ return false
470
+ }
471
+
472
+ /**
473
+ * 【API】获取当前节点在某个节点数组中的前一个非空节点
474
+ */
475
+ getPrevious(nodes: KNode[]): KNode | null {
476
+ const index = nodes.findIndex(item => item.isEqual(this))
477
+ //排除不存在和第一个的情况
478
+ if (index <= 0) {
479
+ return null
480
+ }
481
+ //获取前一个节点
482
+ const previousNode = nodes[index - 1]
483
+ //前一个节点是空节点
484
+ if (previousNode.isEmpty()) {
485
+ //如果是已经是第一个了则返回null,否则继续查找前一个
486
+ return index - 1 == 0 ? null : previousNode.getPrevious(nodes)
487
+ }
488
+ return previousNode
489
+ }
490
+
491
+ /**
492
+ * 【API】获取当前节点在某个节点数组中的后一个非空节点
493
+ */
494
+ getNext(nodes: KNode[]): KNode | null {
495
+ const index = nodes.findIndex(item => item.isEqual(this))
496
+ //排除不存在和最后一个的情况
497
+ if (index < 0 || index == nodes.length - 1) {
498
+ return null
499
+ }
500
+ //获取后一个节点
501
+ const nextNode = nodes[index + 1]
502
+ //后一个节点是空节点
503
+ if (nextNode.isEmpty()) {
504
+ //如果是最后一个则返回null,否则继续查找后一个
505
+ return index + 1 == nodes.length - 1 ? null : nextNode.getNext(nodes)
506
+ }
507
+ return nextNode
508
+ }
509
+
510
+ /**
511
+ * 【API】判断当前节点是否符合指定的条件,marks和styles参数中的属性值可以是true表示只判断是否拥有该标记或者样式,而不关心是什么值
512
+ */
513
+ isMatch(options: KNodeMatchOptionType) {
514
+ //如果存在tag判断并且tag不一样
515
+ if (options.tag && (this.isText() || options.tag != this.tag)) {
516
+ return false
517
+ }
518
+ //如果存在marks判断
519
+ if (options.marks) {
520
+ const hasMarks = Object.keys(options.marks).every(key => {
521
+ if (this.hasMarks()) {
522
+ if (options.marks![key] === true) {
523
+ return this.marks!.hasOwnProperty(key)
524
+ }
525
+ return this.marks![key] == options.marks![key]
526
+ }
527
+ return false
528
+ })
529
+ //如果不是所有的mark都有
530
+ if (!hasMarks) {
531
+ return false
532
+ }
533
+ }
534
+ //如果存在styles判断
535
+ if (options.styles) {
536
+ const hasStyles = Object.keys(options.styles).every(key => {
537
+ if (this.hasStyles()) {
538
+ if (options.styles![key] === true) {
539
+ return this.styles!.hasOwnProperty(key)
540
+ }
541
+ return this.styles![key] == options.styles![key]
542
+ }
543
+ return false
544
+ })
545
+ //如果不是所有的styles都有
546
+ if (!hasStyles) {
547
+ return false
548
+ }
549
+ }
550
+ return true
551
+ }
552
+
553
+ /**
554
+ * 【API】判断当前节点是否存在于符合条件的节点内,包含自身,如果是返回符合条件的节点,否则返回null
555
+ */
556
+ getMatchNode(options: KNodeMatchOptionType): KNode | null {
557
+ if (this.isMatch(options)) {
558
+ return this
559
+ }
560
+ if (this.parent) {
561
+ return this.parent.getMatchNode(options)
562
+ }
563
+ return null
564
+ }
565
+
566
+ /**
567
+ * 【API】获取当前节点下的所有可聚焦的节点,如果自身符合也会包括在内,type是all获取闭合节点和文本节点,type是closed获取闭合节点,type是text获取文本节点
568
+ */
569
+ getFocusNodes = (type: 'all' | 'closed' | 'text' | undefined = 'all') => {
570
+ const nodes: KNode[] = []
571
+ if (this.isClosed() && (type == 'all' || type == 'closed')) {
572
+ nodes.push(this)
573
+ }
574
+ if (this.isText() && (type == 'all' || type == 'text')) {
575
+ nodes.push(this)
576
+ }
577
+ if (this.hasChildren()) {
578
+ this.children!.forEach(item => {
579
+ nodes.push(...item.getFocusNodes(type))
580
+ })
581
+ }
582
+ return nodes
583
+ }
584
+
585
+ /**
586
+ * 【API】创建节点
587
+ */
588
+ static create(options: KNodeCreateOptionType) {
589
+ const knode = new KNode()
590
+ knode.type = options.type
591
+ knode.tag = options.tag
592
+ knode.textContent = options.textContent
593
+ knode.fixed = options.fixed || false
594
+ knode.locked = options.locked || false
595
+ knode.nested = options.nested || false
596
+ knode.void = options.void || false
597
+ knode.marks = DapCommon.clone(options.marks)
598
+ knode.styles = DapCommon.clone(options.styles)
599
+ knode.namespace = options.namespace
600
+ knode.children = options.children?.map(item => {
601
+ const childNode = KNode.create(item)
602
+ childNode.parent = knode
603
+ return childNode
604
+ })
605
+ return knode
606
+ }
607
+
608
+ /**
609
+ * 【API】创建零宽度无断空白文本节点
610
+ */
611
+ static createZeroWidthText(options?: ZeroWidthTextKNodeCreateOptionType) {
612
+ return KNode.create({
613
+ type: 'text',
614
+ textContent: getZeroWidthText(),
615
+ marks: options?.marks,
616
+ styles: options?.styles,
617
+ namespace: options?.namespace,
618
+ locked: options?.locked
619
+ })
620
+ }
621
+
622
+ /**
623
+ * 【API】创建占位符
624
+ */
625
+ static createPlaceholder() {
626
+ return KNode.create({
627
+ type: 'closed',
628
+ tag: 'br'
629
+ })
630
+ }
631
+
632
+ /**
633
+ * 【API】判断参数是否节点
634
+ */
635
+ static isKNode(val: any): boolean {
636
+ return val instanceof KNode
637
+ }
638
+
639
+ /**
640
+ * 【API】将某个节点数组扁平化处理后返回
641
+ */
642
+ static flat(nodes: KNode[]) {
643
+ const newNodes: KNode[] = []
644
+ const length = nodes.length
645
+ for (let i = 0; i < length; i++) {
646
+ newNodes.push(nodes[i])
647
+ if (nodes[i].hasChildren()) {
648
+ const childResult = KNode.flat(nodes[i].children!)
649
+ newNodes.push(...childResult)
650
+ }
651
+ }
652
+ return newNodes
653
+ }
654
+
655
+ /**
656
+ * 【API】在指定的节点数组中根据key查找节点
657
+ */
658
+ static searchByKey(key: string | number, nodes: KNode[]): KNode | null {
659
+ let node: KNode | null = null
660
+ const length = nodes.length
661
+ for (let i = 0; i < length; i++) {
662
+ const item = nodes[i]
663
+ if (item && item.key == Number(key)) {
664
+ node = item
665
+ break
666
+ }
667
+ if (item && item.hasChildren()) {
668
+ const n = KNode.searchByKey(key, item.children!)
669
+ if (n) {
670
+ node = n
671
+ break
672
+ }
673
+ }
674
+ }
675
+ return node
676
+ }
677
+ }
@@ -0,0 +1,39 @@
1
+ import { KNode } from './KNode'
2
+ /**
3
+ * 光标点位类型,node仅支持文本节点和闭合节点
4
+ */
5
+ export type SelectionPointType = {
6
+ node: KNode
7
+ offset: number
8
+ }
9
+
10
+ /**
11
+ * 光标选区
12
+ */
13
+ export class Selection {
14
+ /**
15
+ * 起点
16
+ */
17
+ start?: SelectionPointType
18
+ /**
19
+ * 终点
20
+ */
21
+ end?: SelectionPointType
22
+
23
+ /**
24
+ * 是否已经初始化设置光标位置
25
+ */
26
+ focused() {
27
+ return !!this.start && !!this.end
28
+ }
29
+
30
+ /**
31
+ * 光标是否折叠
32
+ */
33
+ collapsed() {
34
+ if (!this.focused()) {
35
+ return false
36
+ }
37
+ return this.start!.node.isEqual(this.end!.node) && this.start!.offset == this.end!.offset
38
+ }
39
+ }