@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,156 @@
|
|
|
1
|
+
import { data as DapData, element as DapElement } from 'dap-util'
|
|
2
|
+
import { KNodeMarksType, KNodeStylesType } from '@/model'
|
|
3
|
+
import { NODE_MARK } from '@/view'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 用于KNode生成唯一的key
|
|
7
|
+
*/
|
|
8
|
+
export const createUniqueKey = (): number => {
|
|
9
|
+
let key = DapData.get(window, 'kaitify-node-key') || 0
|
|
10
|
+
key++
|
|
11
|
+
DapData.set(window, 'kaitify-node-key', key)
|
|
12
|
+
return key
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 用于编辑器生成唯一的guid
|
|
17
|
+
*/
|
|
18
|
+
export const createGuid = function (): number {
|
|
19
|
+
//获取唯一id
|
|
20
|
+
let key = DapData.get(window, 'kaitify-guid') || 0
|
|
21
|
+
key++
|
|
22
|
+
DapData.set(window, 'kaitify-guid', key)
|
|
23
|
+
return key
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 判断字符串是否零宽度无断空白字符
|
|
28
|
+
*/
|
|
29
|
+
export const isZeroWidthText = (val: string) => {
|
|
30
|
+
return /^[\uFEFF]+$/g.test(val)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 获取一个零宽度无断空白字符
|
|
35
|
+
*/
|
|
36
|
+
export const getZeroWidthText = () => {
|
|
37
|
+
return '\uFEFF'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 驼峰转中划线
|
|
42
|
+
*/
|
|
43
|
+
export const camelToKebab = (val: string) => {
|
|
44
|
+
return val.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 中划线转驼峰
|
|
49
|
+
*/
|
|
50
|
+
export const kebabToCamel = (val: string) => {
|
|
51
|
+
return val.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 获取dom元素的属性集合
|
|
56
|
+
*/
|
|
57
|
+
export const getDomAttributes = (dom: HTMLElement) => {
|
|
58
|
+
let o: KNodeMarksType = {}
|
|
59
|
+
const length = dom.attributes.length
|
|
60
|
+
for (let i = 0; i < length; i++) {
|
|
61
|
+
const attribute = dom.attributes[i]
|
|
62
|
+
const regExp = new RegExp(`(^on)|(^style$)|(^${NODE_MARK}$)`, 'g')
|
|
63
|
+
//匹配事件、样式和face外的属性
|
|
64
|
+
if (!regExp.test(attribute.nodeName)) {
|
|
65
|
+
o[attribute.nodeName] = attribute.nodeValue || ''
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return o
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 获取dom元素的样式集合
|
|
73
|
+
*/
|
|
74
|
+
export const getDomStyles = (dom: HTMLElement) => {
|
|
75
|
+
let o: KNodeStylesType = {}
|
|
76
|
+
const styles = dom.getAttribute('style')
|
|
77
|
+
if (styles) {
|
|
78
|
+
let i = 0
|
|
79
|
+
let start = 0
|
|
80
|
+
let splitStyles = []
|
|
81
|
+
while (i < styles.length) {
|
|
82
|
+
if (styles[i] == ';' && styles.substring(i + 1, i + 8) != 'base64,') {
|
|
83
|
+
splitStyles.push(styles.substring(start, i))
|
|
84
|
+
start = i + 1
|
|
85
|
+
}
|
|
86
|
+
//到最后了,并且最后没有分号
|
|
87
|
+
if (i == styles.length - 1 && start < i + 1) {
|
|
88
|
+
splitStyles.push(styles.substring(start, i + 1))
|
|
89
|
+
}
|
|
90
|
+
i++
|
|
91
|
+
}
|
|
92
|
+
splitStyles.forEach(style => {
|
|
93
|
+
const index = style.indexOf(':')
|
|
94
|
+
const property = style.substring(0, index).trim()
|
|
95
|
+
const value = style.substring(index + 1).trim()
|
|
96
|
+
o[kebabToCamel(property)] = value
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
return o
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 初始化编辑器dom
|
|
104
|
+
*/
|
|
105
|
+
export const initEditorDom = (dom: HTMLElement | string) => {
|
|
106
|
+
//判断是否字符串,如果是字符串按照选择器来寻找元素
|
|
107
|
+
if (typeof dom == 'string' && dom) {
|
|
108
|
+
dom = document.body.querySelector(dom) as HTMLElement
|
|
109
|
+
}
|
|
110
|
+
dom = dom as HTMLElement
|
|
111
|
+
//如何node不是元素则抛出异常
|
|
112
|
+
if (!DapElement.isElement(dom)) {
|
|
113
|
+
throw new Error('You must specify a dom container to initialize the editor')
|
|
114
|
+
}
|
|
115
|
+
//如果已经初始化过了则抛出异常
|
|
116
|
+
if (DapData.get(dom, 'kaitify-init')) {
|
|
117
|
+
throw new Error('The element node has been initialized to the editor')
|
|
118
|
+
}
|
|
119
|
+
//添加初始化的标记
|
|
120
|
+
DapData.set(dom, 'kaitify-init', true)
|
|
121
|
+
return dom
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 判断某个dom是否包含另一个dom
|
|
126
|
+
*/
|
|
127
|
+
export const isContains = (parent: Node, child: Node) => {
|
|
128
|
+
if (child.nodeType == 3) {
|
|
129
|
+
return DapElement.isContains(parent as HTMLElement, child.parentNode as HTMLElement)
|
|
130
|
+
}
|
|
131
|
+
return DapElement.isContains(parent as HTMLElement, child as HTMLElement)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 延迟指定时间
|
|
136
|
+
*/
|
|
137
|
+
export const delay = (num: number | undefined = 0) => {
|
|
138
|
+
return new Promise<void>(resolve => {
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
resolve()
|
|
141
|
+
}, num)
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 删除对象的某个属性
|
|
147
|
+
*/
|
|
148
|
+
export const deleteProperty = <T>(val: any, propertyName: string) => {
|
|
149
|
+
const newObj: any = {}
|
|
150
|
+
Object.keys(val).forEach(key => {
|
|
151
|
+
if (key != propertyName) {
|
|
152
|
+
newObj[key] = val[key]
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
return newObj as T
|
|
156
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { common as DapCommon } from 'dap-util'
|
|
2
|
+
import { Editor, KNode, KNodeMarksType, KNodeStylesType } from '@/model'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 渲染参数类型
|
|
6
|
+
*/
|
|
7
|
+
export type KNodeRenderOptionType = {
|
|
8
|
+
key: number
|
|
9
|
+
tag: string
|
|
10
|
+
attrs: KNodeMarksType
|
|
11
|
+
styles: KNodeStylesType
|
|
12
|
+
namespace?: string
|
|
13
|
+
textContent?: string
|
|
14
|
+
children?: KNodeRenderOptionType[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 节点渲染成dom后在dom上生成的一个特殊标记名称,它的值是节点的key值
|
|
19
|
+
*/
|
|
20
|
+
export const NODE_MARK = 'kaitify-node'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 获取节点的渲染参数
|
|
24
|
+
*/
|
|
25
|
+
export const getNodeRenderOptions = (editor: Editor, node: KNode): KNodeRenderOptionType => {
|
|
26
|
+
//文本节点
|
|
27
|
+
if (node.isText()) {
|
|
28
|
+
return {
|
|
29
|
+
key: node.key,
|
|
30
|
+
tag: editor.textRenderTag,
|
|
31
|
+
namespace: node.namespace,
|
|
32
|
+
attrs: node.hasMarks() ? DapCommon.clone({ ...node.marks, [NODE_MARK]: node.key }) : { [NODE_MARK]: node.key },
|
|
33
|
+
styles: node.hasStyles() ? DapCommon.clone(node.styles) : {},
|
|
34
|
+
textContent: node.textContent
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//其他节点
|
|
38
|
+
return {
|
|
39
|
+
key: node.key,
|
|
40
|
+
tag: node.tag!,
|
|
41
|
+
namespace: node.namespace,
|
|
42
|
+
attrs: node.hasMarks() ? DapCommon.clone({ ...node.marks, [NODE_MARK]: node.key }) : { [NODE_MARK]: node.key },
|
|
43
|
+
styles: node.hasStyles() ? DapCommon.clone(node.styles) : {},
|
|
44
|
+
children: node.hasChildren() ? node.children!.map(item => getNodeRenderOptions(editor, item)) : []
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { KNode, KNodeMarksType, KNodeStylesType } from '@/model'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 节点数组比对结果类型
|
|
5
|
+
*/
|
|
6
|
+
type NodePatchResultType = {
|
|
7
|
+
/**
|
|
8
|
+
* 差异类型:insert:插入节点;remove:移除节点;update:节点更新;replace:节点被替换;move:节点同级位置移动
|
|
9
|
+
*/
|
|
10
|
+
type: 'insert' | 'remove' | 'update' | 'replace' | 'move'
|
|
11
|
+
/**
|
|
12
|
+
* 新节点
|
|
13
|
+
*/
|
|
14
|
+
newNode: KNode | null
|
|
15
|
+
/**
|
|
16
|
+
* 旧节点
|
|
17
|
+
*/
|
|
18
|
+
oldNode: KNode | null
|
|
19
|
+
/**
|
|
20
|
+
* 更新的字段
|
|
21
|
+
*/
|
|
22
|
+
update?: 'textContent' | 'styles' | 'marks'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* mark比对结果类型
|
|
27
|
+
*/
|
|
28
|
+
export type MarkPatchResultType = {
|
|
29
|
+
/**
|
|
30
|
+
* 新增和更新的标记
|
|
31
|
+
*/
|
|
32
|
+
addMarks: KNodeMarksType
|
|
33
|
+
/**
|
|
34
|
+
* 移除的标记
|
|
35
|
+
*/
|
|
36
|
+
removeMarks: KNodeMarksType
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* style比对结果类型
|
|
41
|
+
*/
|
|
42
|
+
export type StylePatchResultType = {
|
|
43
|
+
/**
|
|
44
|
+
* 新增和更新的样式
|
|
45
|
+
*/
|
|
46
|
+
addStyles: KNodeStylesType
|
|
47
|
+
/**
|
|
48
|
+
* 移除的样式
|
|
49
|
+
*/
|
|
50
|
+
removeStyles: KNodeStylesType
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 获取两个节点上不相同的marks
|
|
55
|
+
*/
|
|
56
|
+
export const getDifferentMarks = (newNode: KNode, oldNode: KNode): MarkPatchResultType => {
|
|
57
|
+
//新增的标记
|
|
58
|
+
const addMarks: KNodeMarksType = {}
|
|
59
|
+
//移除的标记
|
|
60
|
+
const removeMarks: KNodeMarksType = {}
|
|
61
|
+
|
|
62
|
+
if (newNode.hasMarks()) {
|
|
63
|
+
Object.keys(newNode.marks!).forEach(mark => {
|
|
64
|
+
if (oldNode.hasMarks() && oldNode.marks!.hasOwnProperty(mark) && oldNode.marks![mark] === newNode.marks![mark]) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
addMarks[mark] = newNode.marks![mark]
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
if (oldNode.hasMarks()) {
|
|
71
|
+
Object.keys(oldNode.marks!).forEach(mark => {
|
|
72
|
+
if (newNode.hasMarks() && newNode.marks!.hasOwnProperty(mark)) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
removeMarks[mark] = oldNode.marks![mark]
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
return { addMarks, removeMarks }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 获取两个节点上不相同的styles
|
|
83
|
+
*/
|
|
84
|
+
export const getDifferentStyles = (newNode: KNode, oldNode: KNode): StylePatchResultType => {
|
|
85
|
+
//新增的样式
|
|
86
|
+
const addStyles: KNodeStylesType = {}
|
|
87
|
+
//移除的样式
|
|
88
|
+
const removeStyles: KNodeStylesType = {}
|
|
89
|
+
|
|
90
|
+
if (newNode.hasStyles()) {
|
|
91
|
+
Object.keys(newNode.styles!).forEach(style => {
|
|
92
|
+
if (oldNode.hasStyles() && oldNode.styles!.hasOwnProperty(style) && oldNode.styles![style] === newNode.styles![style]) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
addStyles[style] = newNode.styles![style]
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
if (oldNode.hasStyles()) {
|
|
99
|
+
Object.keys(oldNode.styles!).forEach(style => {
|
|
100
|
+
if (newNode.hasStyles() && newNode.styles!.hasOwnProperty(style)) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
removeStyles[style] = oldNode.styles![style]
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
return { addStyles, removeStyles }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 对新旧两个节点数组进行比对
|
|
111
|
+
*/
|
|
112
|
+
export const patchNodes = (newNodes: KNode[], oldNodes: (KNode | null)[]) => {
|
|
113
|
+
//两个数组都为空,无需操作
|
|
114
|
+
if (newNodes.length == 0 && oldNodes.length == 0) {
|
|
115
|
+
return []
|
|
116
|
+
}
|
|
117
|
+
//旧节点全部移除
|
|
118
|
+
if (newNodes.length === 0) {
|
|
119
|
+
return oldNodes
|
|
120
|
+
.filter(node => !node)
|
|
121
|
+
.map(oldNode => ({
|
|
122
|
+
type: 'remove',
|
|
123
|
+
oldNode,
|
|
124
|
+
newNode: null
|
|
125
|
+
})) as NodePatchResultType[]
|
|
126
|
+
}
|
|
127
|
+
//新节点全部插入
|
|
128
|
+
if (oldNodes.length === 0) {
|
|
129
|
+
return newNodes.map(newNode => ({
|
|
130
|
+
type: 'insert',
|
|
131
|
+
newNode,
|
|
132
|
+
oldNode: null
|
|
133
|
+
})) as NodePatchResultType[]
|
|
134
|
+
}
|
|
135
|
+
//比对结果数组
|
|
136
|
+
const result: NodePatchResultType[] = []
|
|
137
|
+
// 创建一个 Map 存储旧节点 key 对应的索引,提升查找效率
|
|
138
|
+
const oldKeyMap = new Map<number, number>()
|
|
139
|
+
oldNodes.forEach((node, index) => {
|
|
140
|
+
if (node) {
|
|
141
|
+
oldKeyMap.set(node.key, index)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
// 双端遍历
|
|
145
|
+
let newStartIndex = 0
|
|
146
|
+
let oldStartIndex = 0
|
|
147
|
+
let newEndIndex = newNodes.length - 1
|
|
148
|
+
let oldEndIndex = oldNodes.length - 1
|
|
149
|
+
while (newStartIndex <= newEndIndex && oldStartIndex <= oldEndIndex) {
|
|
150
|
+
const newStartNode = newNodes[newStartIndex]
|
|
151
|
+
const oldStartNode = oldNodes[oldStartIndex]
|
|
152
|
+
const newEndNode = newNodes[newEndIndex]
|
|
153
|
+
const oldEndNode = oldNodes[oldEndIndex]
|
|
154
|
+
//跳过已被处理的旧节点
|
|
155
|
+
if (!oldStartNode) {
|
|
156
|
+
oldStartIndex++
|
|
157
|
+
}
|
|
158
|
+
//跳过已被处理的旧节点
|
|
159
|
+
else if (!oldEndNode) {
|
|
160
|
+
oldEndIndex--
|
|
161
|
+
}
|
|
162
|
+
//起始节点 key 匹配,进行比对
|
|
163
|
+
else if (newStartNode.key == oldStartNode!.key) {
|
|
164
|
+
result.push(...patchNode(newStartNode, oldStartNode!))
|
|
165
|
+
newStartIndex++
|
|
166
|
+
oldStartIndex++
|
|
167
|
+
}
|
|
168
|
+
//终点节点 key 匹配,进行比对
|
|
169
|
+
else if (newEndNode.key == oldEndNode!.key) {
|
|
170
|
+
result.push(...patchNode(newEndNode, oldEndNode!))
|
|
171
|
+
newEndIndex--
|
|
172
|
+
oldEndIndex--
|
|
173
|
+
}
|
|
174
|
+
//新起点和旧终点匹配,说明节点被移动
|
|
175
|
+
else if (newStartNode.key == oldEndNode!.key) {
|
|
176
|
+
result.push(
|
|
177
|
+
{
|
|
178
|
+
type: 'move',
|
|
179
|
+
newNode: newStartNode,
|
|
180
|
+
oldNode: oldEndNode
|
|
181
|
+
},
|
|
182
|
+
...patchNode(newStartNode, oldEndNode!)
|
|
183
|
+
)
|
|
184
|
+
newStartIndex++
|
|
185
|
+
oldEndIndex--
|
|
186
|
+
}
|
|
187
|
+
//新终点和旧起点匹配,说明节点被移动
|
|
188
|
+
else if (newEndNode.key == oldStartNode!.key) {
|
|
189
|
+
result.push(
|
|
190
|
+
{
|
|
191
|
+
type: 'move',
|
|
192
|
+
newNode: newEndNode,
|
|
193
|
+
oldNode: oldStartNode
|
|
194
|
+
},
|
|
195
|
+
...patchNode(newEndNode, oldStartNode!)
|
|
196
|
+
)
|
|
197
|
+
newEndIndex--
|
|
198
|
+
oldStartIndex++
|
|
199
|
+
}
|
|
200
|
+
//其他情况
|
|
201
|
+
else {
|
|
202
|
+
//查找新起点节点在旧节点数组中的位置
|
|
203
|
+
const idxInOld = oldKeyMap.get(newStartNode.key)
|
|
204
|
+
if (idxInOld !== undefined) {
|
|
205
|
+
//说明找到了同key节点,进行移动
|
|
206
|
+
result.push(
|
|
207
|
+
{
|
|
208
|
+
type: 'move',
|
|
209
|
+
newNode: newStartNode,
|
|
210
|
+
oldNode: oldNodes[idxInOld]
|
|
211
|
+
},
|
|
212
|
+
...patchNode(newStartNode, oldNodes[idxInOld]!)
|
|
213
|
+
)
|
|
214
|
+
//标记节点已处理
|
|
215
|
+
oldNodes[idxInOld] = null
|
|
216
|
+
} else {
|
|
217
|
+
//没有找到相同 key,则是新插入的节点
|
|
218
|
+
result.push({
|
|
219
|
+
type: 'insert',
|
|
220
|
+
newNode: newStartNode,
|
|
221
|
+
oldNode: null
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
newStartIndex++
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//处理剩余的新节点(全部为插入)
|
|
228
|
+
while (newStartIndex <= newEndIndex) {
|
|
229
|
+
result.push({
|
|
230
|
+
type: 'insert',
|
|
231
|
+
newNode: newNodes[newStartIndex],
|
|
232
|
+
oldNode: null
|
|
233
|
+
})
|
|
234
|
+
newStartIndex++
|
|
235
|
+
}
|
|
236
|
+
//处理剩余的旧节点(全部为移除)
|
|
237
|
+
while (oldStartIndex <= oldEndIndex) {
|
|
238
|
+
if (oldNodes[oldStartIndex]) {
|
|
239
|
+
result.push({
|
|
240
|
+
type: 'remove',
|
|
241
|
+
oldNode: oldNodes[oldStartIndex]!,
|
|
242
|
+
newNode: null
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
oldStartIndex++
|
|
246
|
+
}
|
|
247
|
+
return result
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 对新旧两个节点进行比对
|
|
252
|
+
*/
|
|
253
|
+
export const patchNode = (newNode: KNode, oldNode: KNode) => {
|
|
254
|
+
//比对结果数组
|
|
255
|
+
const result: NodePatchResultType[] = []
|
|
256
|
+
//namespace变更
|
|
257
|
+
if (newNode.namespace != oldNode.namespace) {
|
|
258
|
+
result.push({
|
|
259
|
+
type: 'replace',
|
|
260
|
+
newNode,
|
|
261
|
+
oldNode
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
//非文本节点的tag变化
|
|
265
|
+
else if (!newNode.isText() && newNode.tag != oldNode.tag) {
|
|
266
|
+
result.push({
|
|
267
|
+
type: 'replace',
|
|
268
|
+
newNode,
|
|
269
|
+
oldNode
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
//新节点有子节点而旧节点没有
|
|
273
|
+
else if (newNode.hasChildren() && !oldNode.hasChildren()) {
|
|
274
|
+
result.push({
|
|
275
|
+
type: 'replace',
|
|
276
|
+
newNode,
|
|
277
|
+
oldNode
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
//旧节点有子节点而新节点没有
|
|
281
|
+
else if (oldNode.hasChildren() && !newNode.hasChildren()) {
|
|
282
|
+
result.push({
|
|
283
|
+
type: 'replace',
|
|
284
|
+
newNode,
|
|
285
|
+
oldNode
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
//以下是更新节点的情况
|
|
289
|
+
else {
|
|
290
|
+
//文本节点的textContent变更
|
|
291
|
+
if (newNode.isText() && newNode.textContent != oldNode.textContent) {
|
|
292
|
+
result.push({
|
|
293
|
+
type: 'update',
|
|
294
|
+
oldNode,
|
|
295
|
+
newNode,
|
|
296
|
+
update: 'textContent'
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
//节点的styles变更
|
|
300
|
+
if (!newNode.isEqualStyles(oldNode)) {
|
|
301
|
+
result.push({
|
|
302
|
+
type: 'update',
|
|
303
|
+
newNode,
|
|
304
|
+
oldNode,
|
|
305
|
+
update: 'styles'
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
//节点的marks变更
|
|
309
|
+
if (!newNode.isEqualMarks(oldNode)) {
|
|
310
|
+
result.push({
|
|
311
|
+
type: 'update',
|
|
312
|
+
newNode,
|
|
313
|
+
oldNode,
|
|
314
|
+
update: 'marks'
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
//子节点
|
|
318
|
+
if (newNode.hasChildren() && oldNode.hasChildren()) {
|
|
319
|
+
result.push(...patchNodes(newNode.children!, oldNode.children!))
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return result
|
|
324
|
+
}
|