@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,293 @@
|
|
|
1
|
+
import { event as DapEvent } from 'dap-util'
|
|
2
|
+
import { Editor, KNode, KNodeMarksType, KNodeStylesType } from '@/model'
|
|
3
|
+
import { Extension } from '../Extension'
|
|
4
|
+
import defaultIcon from './icon.svg?raw'
|
|
5
|
+
import './style.less'
|
|
6
|
+
|
|
7
|
+
export type SetAttachmentOptionType = {
|
|
8
|
+
url: string
|
|
9
|
+
text: string
|
|
10
|
+
icon?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type UpdateAttachmentOptionType = {
|
|
14
|
+
url?: string
|
|
15
|
+
text?: string
|
|
16
|
+
icon?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type AttachmentExtensionPropsType = {
|
|
20
|
+
icon: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module '../../model' {
|
|
24
|
+
interface EditorCommandsType {
|
|
25
|
+
getAttachment?: () => KNode | null
|
|
26
|
+
hasAttachment?: () => boolean
|
|
27
|
+
setAttachment?: (options: SetAttachmentOptionType) => Promise<void>
|
|
28
|
+
updateAttachment?: (options: UpdateAttachmentOptionType) => Promise<void>
|
|
29
|
+
getAttachmentInfo?: () => { url: string; text: string; icon: string } | null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 下载附件
|
|
35
|
+
*/
|
|
36
|
+
const downloadAttachment = (editor: Editor) => {
|
|
37
|
+
DapEvent.off(editor.$el!, 'click.attachment')
|
|
38
|
+
DapEvent.on(editor.$el!, 'click.attachment', async e => {
|
|
39
|
+
//可编辑状态下无法下载
|
|
40
|
+
if (editor.isEditable()) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
const event = e as MouseEvent
|
|
44
|
+
const elm = event.target as HTMLElement
|
|
45
|
+
if (elm === editor.$el) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
const node = editor.findNode(elm)
|
|
49
|
+
const matchNode = node.getMatchNode({
|
|
50
|
+
tag: 'span',
|
|
51
|
+
marks: {
|
|
52
|
+
'kaitify-attachment': true
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
//点击的是附件
|
|
56
|
+
if (matchNode) {
|
|
57
|
+
//获取文件地址
|
|
58
|
+
const url = matchNode.marks!['kaitify-attachment'] as string
|
|
59
|
+
//使用fetch读取文件地址
|
|
60
|
+
const res = await fetch(url, {
|
|
61
|
+
method: 'GET'
|
|
62
|
+
})
|
|
63
|
+
//获取blob数据
|
|
64
|
+
const blob = await res.blob()
|
|
65
|
+
//创建a标签进行下载
|
|
66
|
+
const a = document.createElement('a')
|
|
67
|
+
a.setAttribute('target', '_blank')
|
|
68
|
+
a.setAttribute('href', URL.createObjectURL(blob))
|
|
69
|
+
a.setAttribute(
|
|
70
|
+
'download',
|
|
71
|
+
matchNode.children!.reduce((val, item) => {
|
|
72
|
+
return val + item.textContent
|
|
73
|
+
}, '')
|
|
74
|
+
)
|
|
75
|
+
a.click()
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const AttachmentExtension = (props?: AttachmentExtensionPropsType) =>
|
|
81
|
+
Extension.create({
|
|
82
|
+
name: 'attachment',
|
|
83
|
+
pasteKeepStyles(node) {
|
|
84
|
+
const styles: KNodeStylesType = {}
|
|
85
|
+
if (node.isMatch({ tag: 'span', marks: { 'kaitify-attachment': true } })) {
|
|
86
|
+
styles.backgroundImage = node.styles!.backgroundImage
|
|
87
|
+
}
|
|
88
|
+
return styles
|
|
89
|
+
},
|
|
90
|
+
pasteKeepMarks(node) {
|
|
91
|
+
const marks: KNodeMarksType = {}
|
|
92
|
+
if (node.isMatch({ tag: 'span', marks: { 'kaitify-attachment': true } })) {
|
|
93
|
+
marks['kaitify-attachment'] = node.marks!['kaitify-attachment']
|
|
94
|
+
}
|
|
95
|
+
return marks
|
|
96
|
+
},
|
|
97
|
+
domParseNodeCallback(node) {
|
|
98
|
+
if (node.isMatch({ tag: 'span', marks: { 'kaitify-attachment': true } })) {
|
|
99
|
+
//锁定节点
|
|
100
|
+
node.locked = true
|
|
101
|
+
//设为行内
|
|
102
|
+
node.type = 'inline'
|
|
103
|
+
//处理子孙节点
|
|
104
|
+
KNode.flat(node.children!).forEach(item => {
|
|
105
|
+
//锁定节点
|
|
106
|
+
item.locked = true
|
|
107
|
+
//非文本节点
|
|
108
|
+
if (!item.isText()) {
|
|
109
|
+
//有子节点转为行内
|
|
110
|
+
if (item.hasChildren()) {
|
|
111
|
+
item.type = 'inline'
|
|
112
|
+
}
|
|
113
|
+
//无子节点转为闭合
|
|
114
|
+
else {
|
|
115
|
+
item.type = 'closed'
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
return node
|
|
121
|
+
},
|
|
122
|
+
formatRules: [
|
|
123
|
+
({ editor, node }) => {
|
|
124
|
+
if (
|
|
125
|
+
!node.isEmpty() &&
|
|
126
|
+
node.isMatch({
|
|
127
|
+
tag: 'span',
|
|
128
|
+
marks: {
|
|
129
|
+
'kaitify-attachment': true
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
) {
|
|
133
|
+
//附件节点必须是锁定的
|
|
134
|
+
node.locked = true
|
|
135
|
+
//附件节点必须行内
|
|
136
|
+
node.type = 'inline'
|
|
137
|
+
//保持子孙节点的类型
|
|
138
|
+
KNode.flat(node.children!).forEach(item => {
|
|
139
|
+
//锁定节点
|
|
140
|
+
item.locked = true
|
|
141
|
+
//非文本节点
|
|
142
|
+
if (!item.isText()) {
|
|
143
|
+
//有子节点转为行内
|
|
144
|
+
if (item.hasChildren()) {
|
|
145
|
+
item.type = 'inline'
|
|
146
|
+
}
|
|
147
|
+
//无子节点转为闭合
|
|
148
|
+
else {
|
|
149
|
+
item.type = 'closed'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
//没有不可编辑标记的话需要设置
|
|
154
|
+
if (node.marks!['contenteditable'] != 'false') {
|
|
155
|
+
node.marks!['contenteditable'] = 'false'
|
|
156
|
+
}
|
|
157
|
+
//两侧设置空白元素
|
|
158
|
+
const previousNode = node.getPrevious(node.parent ? node.parent!.children! : editor.stackNodes)
|
|
159
|
+
const nextNode = node.getNext(node.parent ? node.parent!.children! : editor.stackNodes)
|
|
160
|
+
//前一个节点不存在或者不是零宽度空白文本节点
|
|
161
|
+
if (!previousNode || !previousNode.isZeroWidthText()) {
|
|
162
|
+
const zeroWidthText = KNode.createZeroWidthText()
|
|
163
|
+
editor.addNodeBefore(zeroWidthText, node)
|
|
164
|
+
}
|
|
165
|
+
//后一个节点不存在或者不是零宽度空白文本节点
|
|
166
|
+
if (!nextNode || !nextNode.isZeroWidthText()) {
|
|
167
|
+
const zeroWidthText = KNode.createZeroWidthText()
|
|
168
|
+
editor.addNodeAfter(zeroWidthText, node)
|
|
169
|
+
}
|
|
170
|
+
//重置光标
|
|
171
|
+
if (editor.isSelectionInNode(node, 'start')) {
|
|
172
|
+
const newTextNode = node.getNext(node.parent ? node.parent!.children! : editor.stackNodes)
|
|
173
|
+
if (newTextNode) editor.setSelectionBefore(newTextNode, 'start')
|
|
174
|
+
}
|
|
175
|
+
if (editor.isSelectionInNode(node, 'end')) {
|
|
176
|
+
const newTextNode = node.getNext(node.parent ? node.parent!.children! : editor.stackNodes)
|
|
177
|
+
if (newTextNode) editor.setSelectionBefore(newTextNode, 'end')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
afterUpdateView() {
|
|
183
|
+
//下载附件
|
|
184
|
+
downloadAttachment(this)
|
|
185
|
+
},
|
|
186
|
+
addCommands() {
|
|
187
|
+
/**
|
|
188
|
+
* 获取光标所在的附件节点,如果光标不在一个附件节点内,返回null
|
|
189
|
+
*/
|
|
190
|
+
const getAttachment = () => {
|
|
191
|
+
return this.getMatchNodeBySelection({
|
|
192
|
+
tag: 'span',
|
|
193
|
+
marks: {
|
|
194
|
+
'kaitify-attachment': true
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 判断光标范围内是否有附件节点
|
|
201
|
+
*/
|
|
202
|
+
const hasAttachment = () => {
|
|
203
|
+
return this.isSelectionNodesSomeMatch({
|
|
204
|
+
tag: 'span',
|
|
205
|
+
marks: {
|
|
206
|
+
'kaitify-attachment': true
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 插入附件
|
|
213
|
+
*/
|
|
214
|
+
const setAttachment = async (options: SetAttachmentOptionType) => {
|
|
215
|
+
if (!this.selection.focused() || hasAttachment()) {
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
if (!options.url || !options.text) {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
const defaultIconBase64 = `data:image/svg+xml;base64,${btoa(defaultIcon)}`
|
|
222
|
+
//设置html内容
|
|
223
|
+
const html = `<span kaitify-attachment="${options.url}" contenteditable="false" style="background-image:url(${options.icon || props?.icon || defaultIconBase64})"><span>${options.text}</span></span>`
|
|
224
|
+
//html内容转为节点数组
|
|
225
|
+
const nodes = this.htmlParseNode(html)
|
|
226
|
+
//插入节点
|
|
227
|
+
this.insertNode(nodes[0])
|
|
228
|
+
//更新视图
|
|
229
|
+
await this.updateView()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 更新附件
|
|
234
|
+
*/
|
|
235
|
+
const updateAttachment = async (options: UpdateAttachmentOptionType) => {
|
|
236
|
+
if (!this.selection.focused()) {
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
if (!options.url && !options.text && !options.icon) {
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
const attachmentNode = getAttachment()
|
|
243
|
+
if (!attachmentNode) {
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
//更新url
|
|
247
|
+
if (options.url) {
|
|
248
|
+
attachmentNode.marks!['kaitify-attachment'] = options.url
|
|
249
|
+
}
|
|
250
|
+
//更新text
|
|
251
|
+
if (options.text) {
|
|
252
|
+
const textNode = KNode.create({
|
|
253
|
+
type: 'text',
|
|
254
|
+
textContent: options.text
|
|
255
|
+
})
|
|
256
|
+
textNode.parent = attachmentNode
|
|
257
|
+
attachmentNode.children = [textNode]
|
|
258
|
+
}
|
|
259
|
+
if (options.icon) {
|
|
260
|
+
attachmentNode.styles!['backgroundImage'] = `url(${options.icon})`
|
|
261
|
+
}
|
|
262
|
+
//更新视图
|
|
263
|
+
await this.updateView()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 获取附件信息
|
|
268
|
+
*/
|
|
269
|
+
const getAttachmentInfo = () => {
|
|
270
|
+
if (!this.selection.focused()) {
|
|
271
|
+
return null
|
|
272
|
+
}
|
|
273
|
+
const attachmentNode = getAttachment()
|
|
274
|
+
if (!attachmentNode) {
|
|
275
|
+
return null
|
|
276
|
+
}
|
|
277
|
+
const url = attachmentNode.marks!['kaitify-attachment'] as string
|
|
278
|
+
const text = attachmentNode.children!.reduce((val, item) => {
|
|
279
|
+
return val + item.textContent
|
|
280
|
+
}, '')
|
|
281
|
+
const icon = attachmentNode.styles!['backgroundImage']!.match(/url\(["']?(.*?)["']?\)/)?.[1]!
|
|
282
|
+
return { url, text, icon }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
getAttachment,
|
|
287
|
+
hasAttachment,
|
|
288
|
+
setAttachment,
|
|
289
|
+
updateAttachment,
|
|
290
|
+
getAttachmentInfo
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.Kaitify {
|
|
2
|
+
span[kaitify-attachment] {
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
justify-content: flex-start;
|
|
5
|
+
flex-wrap: wrap;
|
|
6
|
+
align-items: center;
|
|
7
|
+
margin: 0 var(--kaitify-sides-between);
|
|
8
|
+
color: #06c27c;
|
|
9
|
+
background-position: left center;
|
|
10
|
+
background-repeat: no-repeat;
|
|
11
|
+
background-size: 20px;
|
|
12
|
+
padding-left: 25px;
|
|
13
|
+
|
|
14
|
+
&:hover {
|
|
15
|
+
cursor: pointer !important;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//非编辑状态下
|
|
20
|
+
&:not([contenteditable='true']) {
|
|
21
|
+
span[kaitify-attachment]:hover {
|
|
22
|
+
text-decoration: underline;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { KNodeStylesType } from '@/model'
|
|
2
|
+
import { Extension } from '../Extension'
|
|
3
|
+
|
|
4
|
+
declare module '../../model' {
|
|
5
|
+
interface EditorCommandsType {
|
|
6
|
+
isBackColor?: (value: string) => boolean
|
|
7
|
+
setBackColor?: (value: string) => Promise<void>
|
|
8
|
+
unsetBackColor?: (value: string) => Promise<void>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const BackColorExtension = () =>
|
|
13
|
+
Extension.create({
|
|
14
|
+
name: 'backColor',
|
|
15
|
+
pasteKeepStyles(node) {
|
|
16
|
+
const styles: KNodeStylesType = {}
|
|
17
|
+
if (node.isText() && node.hasStyles()) {
|
|
18
|
+
if (node.styles!.hasOwnProperty('backgroundColor')) styles.backgroundColor = node.styles!.backgroundColor
|
|
19
|
+
}
|
|
20
|
+
return styles
|
|
21
|
+
},
|
|
22
|
+
addCommands() {
|
|
23
|
+
/**
|
|
24
|
+
* 光标所在文本的背景颜色是否与入参一致
|
|
25
|
+
*/
|
|
26
|
+
const isBackColor = (value: string) => {
|
|
27
|
+
return this.commands.isTextStyle!('backgroundColor', value) || this.commands.isTextStyle!('background', value)
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 设置背景颜色
|
|
31
|
+
*/
|
|
32
|
+
const setBackColor = async (value: string) => {
|
|
33
|
+
if (isBackColor(value)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
await this.commands.setTextStyle!({
|
|
37
|
+
backgroundColor: value
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 取消背景颜色
|
|
42
|
+
*/
|
|
43
|
+
const unsetBackColor = async (value: string) => {
|
|
44
|
+
if (!isBackColor(value)) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
await this.commands.removeTextStyle!(['backgroundColor', 'background'])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
isBackColor,
|
|
52
|
+
setBackColor,
|
|
53
|
+
unsetBackColor
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Editor, KNode } from '@/model'
|
|
2
|
+
import { getSelectionBlockNodes } from '@/model/config/function'
|
|
3
|
+
import { Extension } from '../Extension'
|
|
4
|
+
import './style.less'
|
|
5
|
+
|
|
6
|
+
declare module '../../model' {
|
|
7
|
+
interface EditorCommandsType {
|
|
8
|
+
getBlockquote?: () => KNode | null
|
|
9
|
+
hasBlockquote?: () => boolean
|
|
10
|
+
allBlockquote?: () => boolean
|
|
11
|
+
setBlockquote?: () => Promise<void>
|
|
12
|
+
unsetBlockquote?: () => Promise<void>
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 块节点转为引用
|
|
18
|
+
*/
|
|
19
|
+
const toBlockquote = (editor: Editor, node: KNode) => {
|
|
20
|
+
if (!node.isBlock()) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
//是固定的块节点或者内嵌套的块节点
|
|
24
|
+
if (node.fixed || node.nested) {
|
|
25
|
+
//创建引用节点
|
|
26
|
+
const blockquoteNode = KNode.create({
|
|
27
|
+
type: 'block',
|
|
28
|
+
tag: 'blockquote',
|
|
29
|
+
children: []
|
|
30
|
+
})
|
|
31
|
+
//将块节点的子节点给引用节点
|
|
32
|
+
node.children!.forEach((item, index) => {
|
|
33
|
+
editor.addNode(item, blockquoteNode, index)
|
|
34
|
+
})
|
|
35
|
+
//将引用节点添加到块节点下
|
|
36
|
+
blockquoteNode.parent = node
|
|
37
|
+
node.children = [blockquoteNode]
|
|
38
|
+
}
|
|
39
|
+
//非固定块节点
|
|
40
|
+
else {
|
|
41
|
+
editor.toParagraph(node)
|
|
42
|
+
node.tag = 'blockquote'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const BlockquoteExtension = () =>
|
|
47
|
+
Extension.create({
|
|
48
|
+
name: 'blockquote',
|
|
49
|
+
extraKeepTags: ['blockquote'],
|
|
50
|
+
domParseNodeCallback(node) {
|
|
51
|
+
if (node.isMatch({ tag: 'blockquote' })) {
|
|
52
|
+
node.type = 'block'
|
|
53
|
+
}
|
|
54
|
+
return node
|
|
55
|
+
},
|
|
56
|
+
formatRules: [
|
|
57
|
+
({ node }) => {
|
|
58
|
+
if (node.isMatch({ tag: 'blockquote' })) {
|
|
59
|
+
node.type = 'block'
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
addCommands() {
|
|
64
|
+
/**
|
|
65
|
+
* 获取光标所在的引用节点,如果光标不在一个引用节点内,返回null
|
|
66
|
+
*/
|
|
67
|
+
const getBlockquote = () => {
|
|
68
|
+
return this.getMatchNodeBySelection({
|
|
69
|
+
tag: 'blockquote'
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 判断光标范围内是否有引用节点
|
|
75
|
+
*/
|
|
76
|
+
const hasBlockquote = () => {
|
|
77
|
+
return this.isSelectionNodesSomeMatch({
|
|
78
|
+
tag: 'blockquote'
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 光标范围内是否都是引用节点
|
|
84
|
+
*/
|
|
85
|
+
const allBlockquote = () => {
|
|
86
|
+
return this.isSelectionNodesAllMatch({
|
|
87
|
+
tag: 'blockquote'
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 设置引用
|
|
93
|
+
*/
|
|
94
|
+
const setBlockquote = async () => {
|
|
95
|
+
if (allBlockquote()) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
//起点和终点在一起
|
|
99
|
+
if (this.selection.collapsed()) {
|
|
100
|
+
const blockNode = this.selection.start!.node.getBlock()
|
|
101
|
+
toBlockquote(this, blockNode)
|
|
102
|
+
}
|
|
103
|
+
//起点和终点不在一起
|
|
104
|
+
else {
|
|
105
|
+
const blockNodes = getSelectionBlockNodes.apply(this)
|
|
106
|
+
blockNodes.forEach(item => {
|
|
107
|
+
toBlockquote(this, item)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
await this.updateView()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 取消引用
|
|
115
|
+
*/
|
|
116
|
+
const unsetBlockquote = async () => {
|
|
117
|
+
if (!allBlockquote()) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
//起点和终点在一起
|
|
121
|
+
if (this.selection.collapsed()) {
|
|
122
|
+
const matchNode = this.selection.start!.node.getMatchNode({ tag: 'blockquote' })
|
|
123
|
+
if (matchNode) this.toParagraph(matchNode)
|
|
124
|
+
}
|
|
125
|
+
//起点和终点不在一起
|
|
126
|
+
else {
|
|
127
|
+
const blockNodes = getSelectionBlockNodes.apply(this)
|
|
128
|
+
blockNodes.forEach(item => {
|
|
129
|
+
const matchNode = item.getMatchNode({ tag: 'blockquote' })
|
|
130
|
+
if (matchNode) this.toParagraph(matchNode)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
await this.updateView()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
getBlockquote,
|
|
138
|
+
hasBlockquote,
|
|
139
|
+
allBlockquote,
|
|
140
|
+
setBlockquote,
|
|
141
|
+
unsetBlockquote
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.Kaitify blockquote {
|
|
2
|
+
line-height: var(--kaitify-line-height);
|
|
3
|
+
margin: 0 0 var(--kaitify-large-margin) 0 !important;
|
|
4
|
+
padding: 0 0 0 var(--kaitify-large-padding);
|
|
5
|
+
border-left: 6px solid fade(#000, 10);
|
|
6
|
+
color: #666;
|
|
7
|
+
|
|
8
|
+
&:last-child {
|
|
9
|
+
margin-bottom: 0 !important;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
:root[kaitify-dark] .Kaitify blockquote {
|
|
14
|
+
color: fade(#fff, 90);
|
|
15
|
+
border-left-color: fade(#fff, 30);
|
|
16
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { KNodeStylesType } from '@/model'
|
|
2
|
+
import { splitNodeToNodes } from '@/model/config/function'
|
|
3
|
+
import { Extension } from '../Extension'
|
|
4
|
+
|
|
5
|
+
declare module '../../model' {
|
|
6
|
+
interface EditorCommandsType {
|
|
7
|
+
isBold?: () => boolean
|
|
8
|
+
setBold?: () => Promise<void>
|
|
9
|
+
unsetBold?: () => Promise<void>
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const BoldExtension = () =>
|
|
14
|
+
Extension.create({
|
|
15
|
+
name: 'bold',
|
|
16
|
+
extraKeepTags: ['b', 'strong'],
|
|
17
|
+
domParseNodeCallback(node) {
|
|
18
|
+
if (node.isMatch({ tag: 'b' }) || node.isMatch({ tag: 'strong' })) {
|
|
19
|
+
node.type = 'inline'
|
|
20
|
+
}
|
|
21
|
+
return node
|
|
22
|
+
},
|
|
23
|
+
pasteKeepStyles(node) {
|
|
24
|
+
const styles: KNodeStylesType = {}
|
|
25
|
+
if (node.isText() && node.hasStyles()) {
|
|
26
|
+
if (node.styles!.hasOwnProperty('fontWeight')) styles.fontWeight = node.styles!.fontWeight
|
|
27
|
+
}
|
|
28
|
+
return styles
|
|
29
|
+
},
|
|
30
|
+
formatRules: [
|
|
31
|
+
({ editor, node }) => {
|
|
32
|
+
if (!node.isEmpty() && (node.isMatch({ tag: 'b' }) || node.isMatch({ tag: 'strong' }))) {
|
|
33
|
+
const styles: KNodeStylesType = node.styles || {}
|
|
34
|
+
node.styles = {
|
|
35
|
+
...styles,
|
|
36
|
+
fontWeight: 'bold'
|
|
37
|
+
}
|
|
38
|
+
node.tag = editor.textRenderTag
|
|
39
|
+
splitNodeToNodes.apply(editor, [node])
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
addCommands() {
|
|
44
|
+
/**
|
|
45
|
+
* 光标所在文本是否加粗
|
|
46
|
+
*/
|
|
47
|
+
const isBold = () => {
|
|
48
|
+
return this.commands.isTextStyle!('fontWeight', 'bold') || this.commands.isTextStyle!('fontWeight', 'bolder') || this.commands.isTextStyle!('fontWeight', '700') || this.commands.isTextStyle!('fontWeight', '800') || this.commands.isTextStyle!('fontWeight', '900')
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 设置加粗
|
|
52
|
+
*/
|
|
53
|
+
const setBold = async () => {
|
|
54
|
+
if (isBold()) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
await this.commands.setTextStyle!({
|
|
58
|
+
fontWeight: 'bold'
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 取消加粗
|
|
63
|
+
*/
|
|
64
|
+
const unsetBold = async () => {
|
|
65
|
+
if (!isBold()) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
await this.commands.removeTextStyle!(['fontWeight'])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
isBold,
|
|
73
|
+
setBold,
|
|
74
|
+
unsetBold
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|