@sy-common/wang-editor 1.0.0-beta.20 → 1.0.0-beta.21
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/package.json +1 -1
- package/src/hr-color-menu.js +177 -0
- package/src/index.vue +108 -23
package/package.json
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 分割线颜色菜单插件
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const MENU_KEY = 'hrColor'
|
|
6
|
+
|
|
7
|
+
// 颜色列表
|
|
8
|
+
const COLOR_LIST = [
|
|
9
|
+
'',
|
|
10
|
+
'rgb(0, 0, 0)',
|
|
11
|
+
'rgb(38, 38, 38)',
|
|
12
|
+
'rgb(89, 89, 89)',
|
|
13
|
+
'rgb(140, 140, 140)',
|
|
14
|
+
'rgb(191, 191, 191)',
|
|
15
|
+
'rgb(225, 60, 57)',
|
|
16
|
+
'rgb(231, 95, 51)',
|
|
17
|
+
'rgb(235, 144, 58)',
|
|
18
|
+
'rgb(245, 219, 77)',
|
|
19
|
+
'rgb(114, 192, 64)',
|
|
20
|
+
'rgb(89, 191, 192)',
|
|
21
|
+
'rgb(66, 144, 247)',
|
|
22
|
+
'rgb(54, 88, 226)',
|
|
23
|
+
'rgb(106, 57, 201)',
|
|
24
|
+
'rgb(216, 68, 147)',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
// 图标 SVG:分割线 + 彩色条
|
|
28
|
+
const HR_COLOR_SVG = '<svg viewBox="0 0 1092 1024"><path d="M0 51.2m51.2 0l989.866667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-989.866667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#999"/><path d="M0 460.8m51.2 0l170.666667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-170.666667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#ff4d4f"/><path d="M273 460.8m51.2 0l170.666667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-170.666667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#faad14"/><path d="M546 460.8m51.2 0l170.666667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-170.666667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#52c41a"/><path d="M819 460.8m51.2 0l170.666667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-170.666667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#1890ff"/><path d="M0 870.4m51.2 0l989.866667 0q51.2 0 51.2 51.2l0 0q0 51.2-51.2 51.2l-989.866667 0q-51.2 0-51.2-51.2l0 0q0 51.2 51.2-51.2Z" fill="#999"/></svg>'
|
|
29
|
+
|
|
30
|
+
function checkHrSelected(editor) {
|
|
31
|
+
if (editor.isDisabled()) return false
|
|
32
|
+
if (!editor.selection) return false
|
|
33
|
+
try {
|
|
34
|
+
const nodeGen = editor.nodes({ match: n => n.type === 'divider', universal: true })
|
|
35
|
+
for (const [node] of nodeGen) {
|
|
36
|
+
if (node) return true
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {}
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getDividerCurrentColor(editor) {
|
|
43
|
+
try {
|
|
44
|
+
const [nodeEntry] = editor.nodes({ match: n => n.type === 'divider', universal: true })
|
|
45
|
+
if (!nodeEntry) return ''
|
|
46
|
+
const [node] = nodeEntry
|
|
47
|
+
const domNode = editor.toDOMNode(node)
|
|
48
|
+
if (domNode) {
|
|
49
|
+
const hrElement = domNode.querySelector('hr') || domNode
|
|
50
|
+
return hrElement.getAttribute('data-divider-color') || ''
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {}
|
|
53
|
+
return ''
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setDividerColor(editor, color) {
|
|
57
|
+
try {
|
|
58
|
+
const [nodeEntry] = editor.nodes({ match: n => n.type === 'divider', universal: true })
|
|
59
|
+
if (!nodeEntry) return
|
|
60
|
+
const [node] = nodeEntry
|
|
61
|
+
|
|
62
|
+
const domNode = editor.toDOMNode(node)
|
|
63
|
+
if (domNode) {
|
|
64
|
+
const hrElement = domNode.querySelector('hr') || domNode
|
|
65
|
+
if (color) {
|
|
66
|
+
hrElement.style.border = 'none'
|
|
67
|
+
hrElement.style.borderTop = '2px solid ' + color
|
|
68
|
+
hrElement.style.margin = '10px 0'
|
|
69
|
+
hrElement.setAttribute('data-divider-color', color)
|
|
70
|
+
} else {
|
|
71
|
+
hrElement.style.border = ''
|
|
72
|
+
hrElement.style.borderTop = ''
|
|
73
|
+
hrElement.style.margin = ''
|
|
74
|
+
hrElement.removeAttribute('data-divider-color')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.warn('setDividerColor error:', e)
|
|
79
|
+
}
|
|
80
|
+
editor.emit('change')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 创建颜色选择面板
|
|
84
|
+
function createColorPanel() {
|
|
85
|
+
const $content = document.createElement('div')
|
|
86
|
+
$content.style.cssText = 'padding: 10px; display: flex; flex-wrap: wrap; gap: 4px; width: 200px;'
|
|
87
|
+
|
|
88
|
+
COLOR_LIST.forEach(color => {
|
|
89
|
+
const $item = document.createElement('button')
|
|
90
|
+
$item.type = 'button'
|
|
91
|
+
$item.style.cssText = color
|
|
92
|
+
? 'width: 24px; height: 24px; border: 1px solid #d9d9d9; border-radius: 4px; cursor: pointer; background-color: ' + color + ';'
|
|
93
|
+
: 'width: 24px; height: 24px; border: 2px dashed #d9d9d9; border-radius: 4px; cursor: pointer; background: transparent;'
|
|
94
|
+
$item.setAttribute('data-color', color || '')
|
|
95
|
+
$content.appendChild($item)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return $content
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 从 HTML 提取所有 hr 的颜色
|
|
102
|
+
function extractHrColorsFromHtml(html) {
|
|
103
|
+
const colors = []
|
|
104
|
+
const tempDiv = document.createElement('div')
|
|
105
|
+
tempDiv.innerHTML = html
|
|
106
|
+
const hrs = tempDiv.querySelectorAll('hr')
|
|
107
|
+
hrs.forEach(hr => {
|
|
108
|
+
const style = hr.getAttribute('style') || ''
|
|
109
|
+
const colorMatch = style.match(/border-top\s*:\s*\d+px\s+solid\s+(rgb\([^)]+\)|#[a-fA-F0-9]+)/i)
|
|
110
|
+
colors.push(colorMatch ? colorMatch[1] : '')
|
|
111
|
+
})
|
|
112
|
+
return colors
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function registerHrColorMenu(Boot) {
|
|
116
|
+
const menuConf = {
|
|
117
|
+
key: MENU_KEY,
|
|
118
|
+
factory() {
|
|
119
|
+
let $panel = null
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
title: '分割线颜色',
|
|
123
|
+
iconSvg: HR_COLOR_SVG,
|
|
124
|
+
tag: 'button',
|
|
125
|
+
showDropPanel: true,
|
|
126
|
+
|
|
127
|
+
isDisabled(editor) {
|
|
128
|
+
return !checkHrSelected(editor)
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
isActive(editor) {
|
|
132
|
+
return false
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
getValue(editor) {
|
|
136
|
+
return getDividerCurrentColor(editor)
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
exec(editor, value) {
|
|
140
|
+
setDividerColor(editor, value)
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
getPanelContentElem(editor) {
|
|
144
|
+
if (!$panel) {
|
|
145
|
+
$panel = createColorPanel()
|
|
146
|
+
$panel.querySelectorAll('button').forEach(btn => {
|
|
147
|
+
btn.addEventListener('click', () => {
|
|
148
|
+
const color = btn.getAttribute('data-color')
|
|
149
|
+
setDividerColor(editor, color)
|
|
150
|
+
editor.hidePanelOrModal()
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
return $panel
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
Boot.registerMenu(menuConf)
|
|
162
|
+
} catch (e) {
|
|
163
|
+
if (!e.message.includes('already') && !e.message.includes('duplicate')) {
|
|
164
|
+
console.warn('HrColorMenu register warning:', e.message)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export {
|
|
170
|
+
MENU_KEY,
|
|
171
|
+
COLOR_LIST,
|
|
172
|
+
registerHrColorMenu,
|
|
173
|
+
checkHrSelected,
|
|
174
|
+
getDividerCurrentColor,
|
|
175
|
+
setDividerColor,
|
|
176
|
+
extractHrColorsFromHtml
|
|
177
|
+
}
|
package/src/index.vue
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
<div style="z-index: 999">
|
|
3
3
|
<!-- 工具栏 -->
|
|
4
4
|
<Toolbar
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
:editor="editor"
|
|
6
|
+
:defaultConfig="toolbarConfig"
|
|
7
7
|
/>
|
|
8
8
|
<!-- 编辑器 -->
|
|
9
9
|
<Editor
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
:style="editorStyle"
|
|
11
|
+
:defaultConfig="editorConfig"
|
|
12
|
+
v-model="html"
|
|
13
|
+
@onChange="onChange"
|
|
14
|
+
@onCreated="onCreated"
|
|
15
15
|
/>
|
|
16
16
|
</div>
|
|
17
17
|
</template>
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
import '@wangeditor-next/editor/dist/css/style.css'
|
|
21
21
|
import {oneOf} from '@lambo-design/shared/utils/platform';
|
|
22
22
|
import {Editor, Toolbar} from '@wangeditor-next/editor-for-vue2'
|
|
23
|
+
import {Boot} from '@wangeditor-next/editor'
|
|
24
|
+
import {registerHrColorMenu, MENU_KEY, extractHrColorsFromHtml} from './hr-color-menu'
|
|
25
|
+
|
|
26
|
+
// 注册分割线颜色菜单(模块级别,只执行一次)
|
|
27
|
+
registerHrColorMenu(Boot)
|
|
23
28
|
|
|
24
29
|
export default {
|
|
25
30
|
name: 'WangEditor',
|
|
@@ -110,9 +115,12 @@ export default {
|
|
|
110
115
|
return {
|
|
111
116
|
editor: null,
|
|
112
117
|
html: '',
|
|
118
|
+
hrCount: 0, // 追踪分割线数量
|
|
113
119
|
toolbarConfig: {
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
insertKeys: {
|
|
121
|
+
index: 24, // 在分割线菜单后面插入
|
|
122
|
+
keys: [MENU_KEY]
|
|
123
|
+
}
|
|
116
124
|
},
|
|
117
125
|
editorConfig: {
|
|
118
126
|
placeholder: '请输入内容...',
|
|
@@ -232,10 +240,17 @@ export default {
|
|
|
232
240
|
watch: {
|
|
233
241
|
value: {
|
|
234
242
|
handler: function (newVal, oldVal) {
|
|
235
|
-
//
|
|
236
|
-
|
|
243
|
+
// 初次赋值或从空内容变为有内容时处理
|
|
244
|
+
const isEmpty = !newVal || newVal === '<p><br></p>'
|
|
245
|
+
const wasEmpty = !oldVal || oldVal === '<p><br></p>'
|
|
246
|
+
|
|
247
|
+
if (!isEmpty && wasEmpty) {
|
|
237
248
|
if (this.valueType === 'html') {
|
|
238
249
|
let html = newVal;
|
|
250
|
+
|
|
251
|
+
// 先从 HTML 提取所有 hr 的颜色
|
|
252
|
+
const hrColors = extractHrColorsFromHtml(html)
|
|
253
|
+
|
|
239
254
|
// 正则匹配 <table>...</table> 区域
|
|
240
255
|
const tableRegex = /(<table)([\s\S]*?)(<\/table>)/gi;
|
|
241
256
|
// 替换表格区域中的 borderWidth: **px;
|
|
@@ -253,9 +268,30 @@ export default {
|
|
|
253
268
|
table.setAttribute('data-processed', 'true');
|
|
254
269
|
});
|
|
255
270
|
// 更新处理后的 HTML
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
271
|
+
this.editor.setHtml(tempDiv.innerHTML)
|
|
272
|
+
|
|
273
|
+
// setHtml 后设置 hr 颜色到 DOM
|
|
274
|
+
if (hrColors.length > 0) {
|
|
275
|
+
// 用 setTimeout 等待 DOM 渲染完成
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
try {
|
|
278
|
+
const container = this.editor.getEditableContainer()
|
|
279
|
+
if (!container) return
|
|
280
|
+
const hrs = container.querySelectorAll('hr')
|
|
281
|
+
hrs.forEach((hr, index) => {
|
|
282
|
+
const color = hrColors[index] || ''
|
|
283
|
+
if (color) {
|
|
284
|
+
hr.style.border = 'none'
|
|
285
|
+
hr.style.borderTop = '2px solid ' + color
|
|
286
|
+
hr.style.margin = '10px 0'
|
|
287
|
+
hr.setAttribute('data-divider-color', color)
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
} catch (e) {
|
|
291
|
+
console.warn('设置 hr 颜色失败:', e)
|
|
292
|
+
}
|
|
293
|
+
}, 100)
|
|
294
|
+
}
|
|
259
295
|
} else {
|
|
260
296
|
this.editor.insertText(newVal)
|
|
261
297
|
}
|
|
@@ -299,17 +335,66 @@ export default {
|
|
|
299
335
|
|
|
300
336
|
onCreated(editor) {
|
|
301
337
|
this.editor = Object.seal(editor) // 【注意】一定要用 Object.seal() 否则会报错
|
|
302
|
-
let html = this.value
|
|
303
|
-
if (html) this.editor.setHtml(html)
|
|
304
338
|
|
|
339
|
+
let html = this.value
|
|
340
|
+
if (html) {
|
|
341
|
+
this.editor.setHtml(html)
|
|
342
|
+
// 处理已有颜色的 hr 元素,从 style 中提取颜色
|
|
343
|
+
this.$nextTick(() => {
|
|
344
|
+
try {
|
|
345
|
+
const container = this.editor.getEditableContainer()
|
|
346
|
+
if (!container) return
|
|
347
|
+
const hrs = container.querySelectorAll('hr')
|
|
348
|
+
// 初始化 hrCount,记录已有分割线数量
|
|
349
|
+
this.hrCount = hrs.length
|
|
350
|
+
hrs.forEach(hr => {
|
|
351
|
+
const style = hr.getAttribute('style') || ''
|
|
352
|
+
// 匹配 border 中的颜色
|
|
353
|
+
const colorMatch = style.match(/border\s*:[^;]*solid\s+(rgb\([^)]+\)|#[a-fA-F0-9]+)/i)
|
|
354
|
+
if (colorMatch) {
|
|
355
|
+
hr.setAttribute('data-divider-color', colorMatch[1])
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
} catch (e) {
|
|
359
|
+
console.warn('处理已存在 hr 颜色失败:', e)
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
}
|
|
305
363
|
},
|
|
306
364
|
onChange(editor) {
|
|
307
365
|
let self = this;
|
|
308
366
|
let html = this.editor.getHtml()
|
|
367
|
+
|
|
309
368
|
// 创建一个临时的 div 容器来解析 HTML
|
|
310
369
|
const tempDiv = document.createElement('div');
|
|
311
370
|
tempDiv.innerHTML = html;
|
|
312
371
|
|
|
372
|
+
// 从编辑器 DOM 获取所有 hr 的颜色,写入输出 HTML
|
|
373
|
+
const editorContainer = this.editor.getEditableContainer()
|
|
374
|
+
const editorHrs = editorContainer ? editorContainer.querySelectorAll('hr') : []
|
|
375
|
+
const outputHrs = tempDiv.querySelectorAll('hr')
|
|
376
|
+
|
|
377
|
+
// 检测新增的分割线,设置默认颜色
|
|
378
|
+
const DEFAULT_HR_COLOR = 'rgb(250, 64, 6)'
|
|
379
|
+
if (editorHrs.length > this.hrCount) {
|
|
380
|
+
// 有新增的分割线
|
|
381
|
+
for (let i = this.hrCount; i < editorHrs.length; i++) {
|
|
382
|
+
const hr = editorHrs[i]
|
|
383
|
+
hr.style.border = 'none'
|
|
384
|
+
hr.style.borderTop = '2px solid ' + DEFAULT_HR_COLOR
|
|
385
|
+
hr.style.margin = '10px 0'
|
|
386
|
+
hr.setAttribute('data-divider-color', DEFAULT_HR_COLOR)
|
|
387
|
+
}
|
|
388
|
+
this.hrCount = editorHrs.length
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
editorHrs.forEach((hr, index) => {
|
|
392
|
+
const color = hr.getAttribute('data-divider-color') || ''
|
|
393
|
+
if (color && outputHrs[index]) {
|
|
394
|
+
outputHrs[index].setAttribute('style', 'border: none; border-top: 2px solid ' + color + '; margin: 10px 0;')
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
|
|
313
398
|
// 处理 table 元素 -- 查找新增的表格并添加默认样式
|
|
314
399
|
const tables = tempDiv.querySelectorAll('table:not([data-processed])');
|
|
315
400
|
tables.forEach(table => {
|
|
@@ -338,13 +423,6 @@ export default {
|
|
|
338
423
|
table.setAttribute('data-processed', 'true');
|
|
339
424
|
});
|
|
340
425
|
|
|
341
|
-
// 处理 hr 元素
|
|
342
|
-
const hrs = tempDiv.querySelectorAll('hr');
|
|
343
|
-
hrs.forEach(hr => {
|
|
344
|
-
const currentStyle = hr.getAttribute('style') || '';
|
|
345
|
-
const newStyle = `${currentStyle}; border: 1px solid rgb(250, 64, 6);`.trim();
|
|
346
|
-
hr.setAttribute('style', newStyle);
|
|
347
|
-
});
|
|
348
426
|
// 更新处理后的 HTML
|
|
349
427
|
html = tempDiv.innerHTML;
|
|
350
428
|
this.$emit('input', self.valueType === 'html' ? html : editor.getText())
|
|
@@ -379,3 +457,10 @@ export default {
|
|
|
379
457
|
|
|
380
458
|
}
|
|
381
459
|
</script>
|
|
460
|
+
|
|
461
|
+
<style lang="less" scoped>
|
|
462
|
+
/deep/ .table-container {
|
|
463
|
+
width: fit-content !important;
|
|
464
|
+
margin: 10px auto !important;
|
|
465
|
+
}
|
|
466
|
+
</style>
|