@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,184 @@
1
+ import { Editor } from '../Editor'
2
+ import { updateSelection } from './function'
3
+
4
+ /**
5
+ * 判断是否合法dom
6
+ */
7
+ const isLegalDom = (editor: Editor, dom: Node) => {
8
+ let legal = true
9
+ //文本元素
10
+ if (dom.nodeType == 3) {
11
+ //存在父元素并且父元素只有这么一个子元素
12
+ if (dom.parentNode && dom.parentNode.childNodes.length == 1) {
13
+ try {
14
+ //查找父元素对应的节点
15
+ const node = editor.findNode(dom.parentNode as HTMLElement)
16
+ if (!node.isText()) {
17
+ legal = false
18
+ }
19
+ } catch (error) {
20
+ legal = false
21
+ }
22
+ } else {
23
+ legal = false
24
+ }
25
+ } else if (dom.nodeType == 1) {
26
+ //非文本元素
27
+ try {
28
+ //查找该元素对应的节点
29
+ editor.findNode(dom as HTMLElement)
30
+ } catch (error) {
31
+ legal = false
32
+ }
33
+ }
34
+ return legal
35
+ }
36
+
37
+ /**
38
+ * 移除对编辑器的dom监听
39
+ */
40
+ export const removeDomObserve = (editor: Editor) => {
41
+ if (editor.domObserver) {
42
+ editor.domObserver.disconnect()
43
+ editor.domObserver = null
44
+ }
45
+ }
46
+
47
+ /**
48
+ * 设置对编辑器的dom监听,主要解决非法dom插入问题
49
+ * 中文输入和updateView时不会启用dom监听
50
+ */
51
+ export const setDomObserve = (editor: Editor) => {
52
+ if (!window.MutationObserver) {
53
+ console.warn('The current browser does not support MutationObserver')
54
+ }
55
+ removeDomObserve(editor)
56
+ editor.domObserver = new MutationObserver(mutationList => {
57
+ //中文输入的情况下不处理
58
+ if (editor.isComposition) {
59
+ return
60
+ }
61
+ //是否发生更新
62
+ let hasUpdate = false
63
+ //非法dom数组
64
+ const illegalDoms: Node[] = []
65
+ //遍历
66
+ for (let i = 0; i < mutationList.length; i++) {
67
+ const mutationRecord = mutationList[i]
68
+ //文本变更
69
+ if (mutationRecord.type == 'characterData') {
70
+ //父元素
71
+ const parentElement = mutationRecord.target.parentNode! as HTMLElement
72
+ //获取对应的节点
73
+ const parentNode = editor.findNode(parentElement)
74
+ //是文本节点且文本不一致
75
+ if (parentNode.isText() && parentNode.textContent != mutationRecord.target.textContent) {
76
+ const textContent = parentNode.textContent || ''
77
+ //更新文本内容
78
+ parentNode.textContent = mutationRecord.target.textContent || ''
79
+ //更新光标
80
+ if (editor.isSelectionInNode(parentNode)) {
81
+ updateSelection.apply(editor)
82
+ }
83
+ //这里先取消dom监听
84
+ removeDomObserve(editor)
85
+ //移除非法的文本
86
+ mutationRecord.target.textContent = textContent
87
+ //重新设置dom监听
88
+ setDomObserve(editor)
89
+ //更新标识
90
+ hasUpdate = true
91
+ }
92
+ //不是文本节点
93
+ else if (!parentNode.isText()) {
94
+ //子元素在父元素中的位置
95
+ const index = Array.from(parentElement.childNodes).findIndex(item => item === mutationRecord.target)
96
+ //将子元素转为节点
97
+ const node = editor.domParseNode(mutationRecord.target)
98
+ //添加到编辑器内
99
+ parentNode.children!.splice(index, 0, node)
100
+ node.parent = parentNode
101
+ //删除非法dom
102
+ illegalDoms.push(mutationRecord.target)
103
+ //重置光标到节点后
104
+ if (editor.selection.focused()) {
105
+ editor.setSelectionAfter(node, 'all')
106
+ }
107
+ //更新标识
108
+ hasUpdate = true
109
+ }
110
+ }
111
+ //子元素变更
112
+ else if (mutationRecord.type == 'childList') {
113
+ //新增元素中存在非法的元素
114
+ const elements = Array.from(mutationRecord.addedNodes).filter(item => !isLegalDom(editor, item))
115
+ if (elements.length > 0) {
116
+ //非法元素的父元素
117
+ const parentElement = mutationRecord.target as HTMLElement
118
+ //父元素是编辑器容器
119
+ if (parentElement === editor.$el!) {
120
+ elements.forEach(el => {
121
+ //子元素在父元素中的位置
122
+ const index = Array.from(parentElement.childNodes).findIndex(item => item === el)
123
+ //将子元素转为节点
124
+ const node = editor.domParseNode(el)
125
+ //添加到编辑器内
126
+ editor.stackNodes.splice(index, 0, node)
127
+ //删除非法dom
128
+ illegalDoms.push(el)
129
+ //重置光标到节点后
130
+ if (editor.selection.focused()) {
131
+ editor.setSelectionAfter(node, 'all')
132
+ }
133
+ //更新标识
134
+ hasUpdate = true
135
+ })
136
+ }
137
+ //不是编辑器容器的情况
138
+ else {
139
+ //获取父元素对应的node
140
+ const parentNode = editor.findNode(parentElement)
141
+ elements.forEach(el => {
142
+ //子元素在父元素中的位置
143
+ const index = Array.from(parentElement.childNodes).findIndex(item => item === el)
144
+ //将子元素转为节点
145
+ const node = editor.domParseNode(el)
146
+ //添加到编辑器内
147
+ if (parentNode.hasChildren()) {
148
+ parentNode.children!.splice(index, 0, node)
149
+ node.parent = parentNode
150
+ } else {
151
+ parentNode.parent!.children!.splice(index, 0, node)
152
+ node.parent = parentNode.parent!
153
+ }
154
+ //删除非法dom
155
+ illegalDoms.push(el)
156
+ //重置光标到节点后
157
+ if (editor.selection.focused()) {
158
+ editor.setSelectionAfter(node, 'all')
159
+ }
160
+ //更新标识
161
+ hasUpdate = true
162
+ })
163
+ }
164
+ }
165
+ }
166
+ }
167
+ //有dom变化
168
+ if (hasUpdate) {
169
+ //删除非法dom
170
+ illegalDoms.forEach(item => {
171
+ item.parentNode?.removeChild(item)
172
+ })
173
+ //更新视图
174
+ editor.updateView()
175
+ }
176
+ })
177
+ editor.domObserver.observe(editor.$el!, {
178
+ attributes: false,
179
+ characterData: true,
180
+ characterDataOldValue: true,
181
+ childList: true,
182
+ subtree: true
183
+ })
184
+ }
@@ -0,0 +1,237 @@
1
+ import { delay } from '@/tools'
2
+ import { Editor } from '../Editor'
3
+ import { handlerForPasteDrop, redressSelection, updateSelection } from './function'
4
+
5
+ /**
6
+ * 监听外部改变selection
7
+ */
8
+ export const onSelectionChange = async function (this: Editor) {
9
+ if (!this.$el) {
10
+ return
11
+ }
12
+ //正在输入中文
13
+ if (this.isComposition) {
14
+ return
15
+ }
16
+ //内部修改触发
17
+ if (this.internalCauseSelectionChange) {
18
+ return
19
+ }
20
+ //更新selection
21
+ const hasUpdate = updateSelection.apply(this)
22
+ //没有更新
23
+ if (!hasUpdate) {
24
+ return
25
+ }
26
+ //进行纠正
27
+ const hasRedress = redressSelection.apply(this)
28
+ //有纠正
29
+ if (hasRedress) {
30
+ //则渲染真实光标
31
+ await this.updateRealSelection()
32
+ return
33
+ }
34
+ //更新记录的selection
35
+ this.history.updateSelection(this.selection)
36
+ //触发事件
37
+ if (typeof this.onSelectionUpdate == 'function') {
38
+ this.onSelectionUpdate.apply(this, [this.selection])
39
+ }
40
+ }
41
+
42
+ /**
43
+ * 监听beforeinput
44
+ */
45
+ export const onBeforeInput = async function (this: Editor, e: Event) {
46
+ const event = e as InputEvent
47
+ //中文输入相关保持默认行为
48
+ if (event.inputType === 'insertCompositionText' || event.inputType === 'insertFromComposition') {
49
+ return
50
+ }
51
+ //禁用系统默认行为
52
+ event.preventDefault()
53
+ //不可编辑
54
+ if (!this.isEditable()) {
55
+ return
56
+ }
57
+ //针对insertFromDrop的特殊处理
58
+ if (event.inputType == 'insertFromDrop') {
59
+ //延时,防止和deleteByDrag冲突
60
+ await delay()
61
+ //insertFromDrop触发时不会触发selectionchange,虚拟光标位置没有更新,这里主动更新一下
62
+ updateSelection.apply(this)
63
+ }
64
+ //光标没有聚焦
65
+ if (!this.selection.focused()) {
66
+ return
67
+ }
68
+ //插入文本
69
+ if (event.inputType == 'insertText' && event.data) {
70
+ this.insertText(event.data!)
71
+ this.updateView()
72
+ }
73
+ //删除内容
74
+ else if (event.inputType == 'deleteContentBackward' || event.inputType == 'deleteByCut' || event.inputType == 'deleteByDrag') {
75
+ //禁用剪切功能的情况
76
+ if (event.inputType == 'deleteByCut' && !this.allowCut) {
77
+ return
78
+ }
79
+ this.isUserDelection = true
80
+ this.delete()
81
+ await this.updateView()
82
+ this.isUserDelection = false
83
+ }
84
+ //插入段落
85
+ else if (event.inputType == 'insertParagraph' || event.inputType == 'insertLineBreak') {
86
+ this.insertParagraph()
87
+ this.updateView()
88
+ }
89
+ //粘贴和拖入
90
+ else if (event.inputType == 'insertFromPaste' || event.inputType == 'insertFromDrop') {
91
+ //存在粘贴数据且允许粘贴
92
+ if (event.dataTransfer && this.allowPaste) {
93
+ await handlerForPasteDrop.apply(this, [event.dataTransfer])
94
+ await this.updateView()
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 监听中文输入
101
+ */
102
+ export const onComposition = async function (this: Editor, e: Event) {
103
+ const event = e as InputEvent
104
+ //不可编辑
105
+ if (!this.isEditable()) {
106
+ return
107
+ }
108
+ //开始输入中文
109
+ if (event.type == 'compositionstart') {
110
+ //改变标识
111
+ this.isComposition = true
112
+ }
113
+ //输入中文结束后
114
+ else if (event.type == 'compositionend') {
115
+ //获取真实光标
116
+ const realSelection = window.getSelection()!
117
+ const range = realSelection.getRangeAt(0)
118
+ //获取真实光标所在的真实dom,一定是文本
119
+ const element = range.endContainer
120
+ //父元素
121
+ const parentElement = element.parentNode! as HTMLElement
122
+ //获取对应的节点
123
+ const parentNode = this.findNode(parentElement)
124
+ //是文本节点且文本不一致
125
+ if (parentNode.isText() && parentNode.textContent != element.textContent) {
126
+ const textContent = parentNode.textContent || ''
127
+ //更新文本内容
128
+ parentNode.textContent = element.textContent || ''
129
+ //更新光标
130
+ if (this.isSelectionInNode(parentNode)) {
131
+ updateSelection.apply(this)
132
+ }
133
+ //移除非法的文本
134
+ element.textContent = textContent
135
+ //更新视图
136
+ await this.updateView()
137
+ }
138
+ //不是文本节点
139
+ else if (!parentNode.isText()) {
140
+ //子元素在父元素中的位置
141
+ const index = Array.from(parentElement.childNodes).findIndex(item => item === element)
142
+ //将子元素转为节点
143
+ const node = this.domParseNode(element)
144
+ //添加到编辑器内
145
+ parentNode.children!.splice(index, 0, node)
146
+ node.parent = parentNode
147
+ //删除非法dom
148
+ parentElement.removeChild(element)
149
+ //重置光标到节点后
150
+ if (this.selection.focused()) {
151
+ this.setSelectionAfter(node, 'all')
152
+ }
153
+ //更新视图
154
+ await this.updateView()
155
+ }
156
+ //改变标识
157
+ this.isComposition = false
158
+ }
159
+ }
160
+
161
+ /**
162
+ * 监听键盘事件
163
+ */
164
+ export const onKeyboard = function (this: Editor, e: Event) {
165
+ if (this.isComposition) {
166
+ return
167
+ }
168
+ const event = e as KeyboardEvent
169
+ //键盘按下
170
+ if (event.type == 'keydown') {
171
+ //不可编辑
172
+ if (!this.isEditable()) {
173
+ return
174
+ }
175
+ //触发keydown
176
+ if (typeof this.onKeydown == 'function') {
177
+ this.onKeydown.apply(this, [event])
178
+ }
179
+ }
180
+ //键盘松开
181
+ else if (event.type == 'keyup') {
182
+ //不可编辑
183
+ if (!this.isEditable()) {
184
+ return
185
+ }
186
+ //触发keyup
187
+ if (typeof this.onKeyup == 'function') {
188
+ this.onKeyup.apply(this, [event])
189
+ }
190
+ }
191
+ }
192
+
193
+ /**
194
+ * 监听编辑器获取焦点
195
+ */
196
+ export const onFocus = function (this: Editor, e: Event) {
197
+ //不可编辑
198
+ if (!this.isEditable()) {
199
+ return
200
+ }
201
+ if (typeof this.onFocus == 'function') {
202
+ this.onFocus.apply(this, [e as FocusEvent])
203
+ }
204
+ }
205
+
206
+ /**
207
+ * 监听编辑器失去焦点
208
+ */
209
+ export const onBlur = function (this: Editor, e: Event) {
210
+ //不可编辑
211
+ if (!this.isEditable()) {
212
+ return
213
+ }
214
+ if (typeof this.onBlur == 'function') {
215
+ this.onBlur.apply(this, [e as FocusEvent])
216
+ }
217
+ }
218
+
219
+ /**
220
+ * 监听编辑器复制
221
+ */
222
+ export const onCopy = function (this: Editor, e: Event) {
223
+ const event = e as ClipboardEvent
224
+ if (!this.allowCopy) {
225
+ event.preventDefault()
226
+ }
227
+ }
228
+
229
+ /**
230
+ * 监听编辑器剪切
231
+ */
232
+ export const onCut = function (this: Editor, e: Event) {
233
+ const event = e as ClipboardEvent
234
+ if (!this.allowCut) {
235
+ event.preventDefault()
236
+ }
237
+ }
@@ -0,0 +1,215 @@
1
+ import { KNode } from '../KNode'
2
+ /**
3
+ * 这里的比对结果仅进行格式化处理,只需要判断节点是否变化
4
+ */
5
+
6
+ /**
7
+ * 节点数组比对结果类型
8
+ */
9
+ export type NodePatchResultType = {
10
+ /**
11
+ * 新节点
12
+ */
13
+ newNode: KNode | null
14
+ /**
15
+ * 旧节点
16
+ */
17
+ oldNode: KNode | null
18
+ }
19
+
20
+ /**
21
+ * 对新旧两个节点数组进行比对
22
+ */
23
+ export const patchNodes = (newNodes: KNode[], oldNodes: (KNode | null)[]) => {
24
+ //两个数组都为空,无需操作
25
+ if (newNodes.length == 0 && oldNodes.length == 0) {
26
+ return []
27
+ }
28
+ //旧节点全部移除
29
+ if (newNodes.length === 0) {
30
+ return oldNodes
31
+ .filter(node => !node)
32
+ .map(oldNode => ({
33
+ oldNode,
34
+ newNode: null
35
+ })) as NodePatchResultType[]
36
+ }
37
+ //新节点全部插入
38
+ if (oldNodes.length === 0) {
39
+ return newNodes.map(newNode => ({
40
+ newNode,
41
+ oldNode: null
42
+ })) as NodePatchResultType[]
43
+ }
44
+ //比对结果数组
45
+ const result: NodePatchResultType[] = []
46
+ // 创建一个 Map 存储旧节点 key 对应的索引,提升查找效率
47
+ const oldKeyMap = new Map<number, number>()
48
+ oldNodes.forEach((node, index) => {
49
+ if (node) {
50
+ oldKeyMap.set(node.key, index)
51
+ }
52
+ })
53
+ // 双端遍历
54
+ let newStartIndex = 0
55
+ let oldStartIndex = 0
56
+ let newEndIndex = newNodes.length - 1
57
+ let oldEndIndex = oldNodes.length - 1
58
+ while (newStartIndex <= newEndIndex && oldStartIndex <= oldEndIndex) {
59
+ const newStartNode = newNodes[newStartIndex]
60
+ const oldStartNode = oldNodes[oldStartIndex]
61
+ const newEndNode = newNodes[newEndIndex]
62
+ const oldEndNode = oldNodes[oldEndIndex]
63
+ //跳过已被处理的旧节点
64
+ if (!oldStartNode) {
65
+ oldStartIndex++
66
+ }
67
+ //跳过已被处理的旧节点
68
+ else if (!oldEndNode) {
69
+ oldEndIndex--
70
+ }
71
+ //起始节点 key 匹配,进行比对
72
+ else if (newStartNode.key == oldStartNode!.key) {
73
+ result.push(...patchNode(newStartNode, oldStartNode!))
74
+ newStartIndex++
75
+ oldStartIndex++
76
+ }
77
+ //终点节点 key 匹配,进行比对
78
+ else if (newEndNode.key == oldEndNode!.key) {
79
+ result.push(...patchNode(newEndNode, oldEndNode!))
80
+ newEndIndex--
81
+ oldEndIndex--
82
+ }
83
+ //新起点和旧终点匹配,说明节点被移动
84
+ else if (newStartNode.key == oldEndNode!.key) {
85
+ result.push({
86
+ newNode: newStartNode,
87
+ oldNode: oldEndNode
88
+ })
89
+ newStartIndex++
90
+ oldEndIndex--
91
+ }
92
+ //新终点和旧起点匹配,说明节点被移动
93
+ else if (newEndNode.key == oldStartNode!.key) {
94
+ result.push({
95
+ newNode: newEndNode,
96
+ oldNode: oldStartNode
97
+ })
98
+ newEndIndex--
99
+ oldStartIndex++
100
+ }
101
+ //其他情况
102
+ else {
103
+ //查找新起点节点在旧节点数组中的位置
104
+ const idxInOld = oldKeyMap.get(newStartNode.key)
105
+ if (idxInOld !== undefined) {
106
+ //说明找到了同key节点,进行移动
107
+ result.push({
108
+ newNode: newStartNode,
109
+ oldNode: oldNodes[idxInOld]
110
+ })
111
+ //标记节点已处理
112
+ oldNodes[idxInOld] = null
113
+ } else {
114
+ //没有找到相同 key,则是新插入的节点
115
+ result.push({
116
+ newNode: newStartNode,
117
+ oldNode: null
118
+ })
119
+ }
120
+ newStartIndex++
121
+ }
122
+ }
123
+ //处理剩余的新节点(全部为插入)
124
+ while (newStartIndex <= newEndIndex) {
125
+ result.push({
126
+ newNode: newNodes[newStartIndex],
127
+ oldNode: null
128
+ })
129
+ newStartIndex++
130
+ }
131
+ //处理剩余的旧节点(全部为移除)
132
+ while (oldStartIndex <= oldEndIndex) {
133
+ if (oldNodes[oldStartIndex]) {
134
+ result.push({
135
+ oldNode: oldNodes[oldStartIndex]!,
136
+ newNode: null
137
+ })
138
+ }
139
+ oldStartIndex++
140
+ }
141
+ return result
142
+ }
143
+
144
+ /**
145
+ * 对新旧两个节点进行比对
146
+ */
147
+ export const patchNode = (newNode: KNode, oldNode: KNode) => {
148
+ //比对结果数组
149
+ const result: NodePatchResultType[] = []
150
+ //空节点
151
+ if (newNode.isEmpty() || oldNode.isEmpty()) {
152
+ result.push({
153
+ oldNode,
154
+ newNode
155
+ })
156
+ }
157
+ //type和locked、fixed、nested变更
158
+ else if (newNode.type != oldNode.type || newNode.locked != oldNode.locked || newNode.fixed != oldNode.fixed || newNode.nested != oldNode.nested) {
159
+ result.push({
160
+ newNode,
161
+ oldNode
162
+ })
163
+ }
164
+ //非文本节点的tag变化
165
+ else if (!newNode.isText() && newNode.tag != oldNode.tag) {
166
+ result.push({
167
+ newNode,
168
+ oldNode
169
+ })
170
+ }
171
+ //新节点有子节点而旧节点没有
172
+ else if (newNode.hasChildren() && !oldNode.hasChildren()) {
173
+ result.push({
174
+ newNode,
175
+ oldNode
176
+ })
177
+ }
178
+ //旧节点有子节点而新节点没有
179
+ else if (oldNode.hasChildren() && !newNode.hasChildren()) {
180
+ result.push({
181
+ newNode,
182
+ oldNode
183
+ })
184
+ }
185
+ //以下是更新节点的情况
186
+ else {
187
+ //文本节点的textContent变更
188
+ if (newNode.isText() && newNode.textContent != oldNode.textContent) {
189
+ result.push({
190
+ oldNode,
191
+ newNode
192
+ })
193
+ }
194
+ //节点的marks变更
195
+ else if (!newNode.isEqualMarks(oldNode)) {
196
+ result.push({
197
+ newNode,
198
+ oldNode
199
+ })
200
+ }
201
+ //节点的styles变更
202
+ else if (!newNode.isEqualStyles(oldNode)) {
203
+ result.push({
204
+ newNode,
205
+ oldNode
206
+ })
207
+ }
208
+ //子节点
209
+ else if (newNode.hasChildren() && oldNode.hasChildren()) {
210
+ result.push(...patchNodes(newNode.children!, oldNode.children!))
211
+ }
212
+ }
213
+
214
+ return result
215
+ }