@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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/examples/App.vue +342 -0
- package/examples/content.js +1 -0
- package/examples/main.ts +4 -0
- package/examples/test.html +23 -0
- package/lib/extensions/Extension.d.ts +172 -0
- package/lib/extensions/align/index.d.ts +10 -0
- package/lib/extensions/attachment/index.d.ts +29 -0
- package/lib/extensions/back-color/index.d.ts +9 -0
- package/lib/extensions/blockquote/index.d.ts +12 -0
- package/lib/extensions/bold/index.d.ts +9 -0
- package/lib/extensions/code/index.d.ts +12 -0
- package/lib/extensions/code-block/hljs.d.ts +12 -0
- package/lib/extensions/code-block/index.d.ts +15 -0
- package/lib/extensions/color/index.d.ts +9 -0
- package/lib/extensions/font-family/index.d.ts +9 -0
- package/lib/extensions/font-size/index.d.ts +9 -0
- package/lib/extensions/heading/index.d.ts +13 -0
- package/lib/extensions/history/index.d.ts +10 -0
- package/lib/extensions/horizontal/index.d.ts +7 -0
- package/lib/extensions/image/index.d.ts +26 -0
- package/lib/extensions/indent/index.d.ts +8 -0
- package/lib/extensions/index.d.ts +29 -0
- package/lib/extensions/italic/index.d.ts +9 -0
- package/lib/extensions/line-height/index.d.ts +9 -0
- package/lib/extensions/link/index.d.ts +27 -0
- package/lib/extensions/list/index.d.ts +18 -0
- package/lib/extensions/math/index.d.ts +11 -0
- package/lib/extensions/strikethrough/index.d.ts +9 -0
- package/lib/extensions/subscript/index.d.ts +9 -0
- package/lib/extensions/superscript/index.d.ts +9 -0
- package/lib/extensions/table/index.d.ts +21 -0
- package/lib/extensions/task/index.d.ts +12 -0
- package/lib/extensions/text/index.d.ts +14 -0
- package/lib/extensions/underline/index.d.ts +9 -0
- package/lib/extensions/video/index.d.ts +27 -0
- package/lib/index.d.ts +3 -0
- package/lib/kaitify-core.es.js +38337 -0
- package/lib/kaitify-core.umd.js +2 -0
- package/lib/model/Editor.d.ts +504 -0
- package/lib/model/History.d.ts +42 -0
- package/lib/model/KNode.d.ts +258 -0
- package/lib/model/Selection.d.ts +29 -0
- package/lib/model/config/dom-observe.d.ts +10 -0
- package/lib/model/config/event-handler.d.ts +33 -0
- package/lib/model/config/format-patch.d.ts +25 -0
- package/lib/model/config/format-rules.d.ts +37 -0
- package/lib/model/config/function.d.ts +84 -0
- package/lib/model/index.d.ts +6 -0
- package/lib/tools/index.d.ts +49 -0
- package/lib/view/index.d.ts +21 -0
- package/lib/view/js-render/dom-patch.d.ts +65 -0
- package/lib/view/js-render/index.d.ts +5 -0
- package/package.json +52 -0
- package/src/css/style.less +56 -0
- package/src/css/var.less +45 -0
- package/src/extensions/Extension.ts +200 -0
- package/src/extensions/align/index.ts +115 -0
- package/src/extensions/attachment/icon.svg +1 -0
- package/src/extensions/attachment/index.ts +293 -0
- package/src/extensions/attachment/style.less +25 -0
- package/src/extensions/back-color/index.ts +56 -0
- package/src/extensions/blockquote/index.ts +144 -0
- package/src/extensions/blockquote/style.less +16 -0
- package/src/extensions/bold/index.ts +77 -0
- package/src/extensions/code/index.ts +295 -0
- package/src/extensions/code/style.less +14 -0
- package/src/extensions/code-block/hljs.less +183 -0
- package/src/extensions/code-block/hljs.ts +95 -0
- package/src/extensions/code-block/index.ts +308 -0
- package/src/extensions/code-block/style.less +20 -0
- package/src/extensions/color/index.ts +56 -0
- package/src/extensions/font-family/index.ts +80 -0
- package/src/extensions/font-size/index.ts +56 -0
- package/src/extensions/heading/index.ts +164 -0
- package/src/extensions/heading/style.less +42 -0
- package/src/extensions/history/index.ts +96 -0
- package/src/extensions/horizontal/index.ts +45 -0
- package/src/extensions/horizontal/style.less +13 -0
- package/src/extensions/image/index.ts +242 -0
- package/src/extensions/image/style.less +8 -0
- package/src/extensions/indent/index.ts +98 -0
- package/src/extensions/index.ts +29 -0
- package/src/extensions/italic/index.ts +77 -0
- package/src/extensions/line-height/index.ts +113 -0
- package/src/extensions/link/index.ts +184 -0
- package/src/extensions/link/style.less +19 -0
- package/src/extensions/list/index.ts +410 -0
- package/src/extensions/list/style.less +19 -0
- package/src/extensions/math/index.ts +233 -0
- package/src/extensions/math/style.less +21 -0
- package/src/extensions/strikethrough/index.ts +78 -0
- package/src/extensions/subscript/index.ts +77 -0
- package/src/extensions/superscript/index.ts +77 -0
- package/src/extensions/table/index.ts +1148 -0
- package/src/extensions/table/style.less +71 -0
- package/src/extensions/task/index.ts +243 -0
- package/src/extensions/task/style.less +59 -0
- package/src/extensions/text/index.ts +359 -0
- package/src/extensions/underline/index.ts +78 -0
- package/src/extensions/video/index.ts +273 -0
- package/src/extensions/video/style.less +8 -0
- package/src/index.ts +9 -0
- package/src/model/Editor.ts +1963 -0
- package/src/model/History.ts +115 -0
- package/src/model/KNode.ts +677 -0
- package/src/model/Selection.ts +39 -0
- package/src/model/config/dom-observe.ts +184 -0
- package/src/model/config/event-handler.ts +237 -0
- package/src/model/config/format-patch.ts +215 -0
- package/src/model/config/format-rules.ts +218 -0
- package/src/model/config/function.ts +1018 -0
- package/src/model/index.ts +6 -0
- package/src/tools/index.ts +156 -0
- package/src/view/index.ts +46 -0
- package/src/view/js-render/dom-patch.ts +324 -0
- package/src/view/js-render/index.ts +210 -0
- 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
|
+
}
|