From 570bbb1b162ede607989b9e8b0d1bfc66a80ec7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A1=97=E8=A7=92=E5=B0=8F=E6=9E=97?= <1013335014@qq.com>
Date: Thu, 29 Aug 2024 15:33:38 +0800
Subject: [PATCH] =?UTF-8?q?Feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=BC=80?=
=?UTF-8?q?=E5=90=AF=E8=8A=82=E7=82=B9=E6=96=87=E6=9C=AC=E7=BC=96=E8=BE=91?=
=?UTF-8?q?=E5=AE=9E=E6=97=B6=E6=9B=B4=E6=96=B0=E8=8A=82=E7=82=B9=E5=A4=A7?=
=?UTF-8?q?=E5=B0=8F=E5=92=8C=E4=BD=8D=E7=BD=AE=E7=9A=84=E5=AE=9E=E4=BE=8B?=
=?UTF-8?q?=E5=8C=96=E9=80=89=E9=A1=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/constants/defaultOptions.js | 4 ++
simple-mind-map/src/core/render/Render.js | 13 +++++++
simple-mind-map/src/core/render/TextEdit.js | 35 +++++++++++++++--
.../core/render/node/nodeCreateContents.js | 31 ++++++++++-----
simple-mind-map/src/plugins/RichText.js | 38 ++++++++++++++++++-
5 files changed, 106 insertions(+), 15 deletions(-)
diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js
index 1e9ec431..886b914d 100644
--- a/simple-mind-map/src/constants/defaultOptions.js
+++ b/simple-mind-map/src/constants/defaultOptions.js
@@ -238,6 +238,10 @@ export const defaultOpt = {
padding: 100, // 超出画布四周指定范围内依旧渲染节点
removeNodeWhenOutCanvas: true // 节点移除画布可视区域后从画布删除
},
+ // 如果节点文本为空,那么为了避免空白节点高度塌陷,会用该字段指定的文本测量一个高度
+ emptyTextMeasureHeightText: 'abc123我和你',
+ // 是否在进行节点文本编辑时实时更新节点大小和节点位置,开启后当节点数量比较多时可能会造成卡顿
+ openRealtimeRenderOnNodeTextEdit: false,
// 【Select插件】
// 多选节点时鼠标移动到边缘时的画布移动偏移量
diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js
index ec2bf3b8..605313a5 100644
--- a/simple-mind-map/src/core/render/Render.js
+++ b/simple-mind-map/src/core/render/Render.js
@@ -147,6 +147,19 @@ class Render {
})
// 性能模式
this.performanceMode()
+ // 实时渲染当节点文本编辑时
+ if (this.mindMap.opt.openRealtimeRenderOnNodeTextEdit) {
+ this.mindMap.on('node_text_edit_change', ({ node, text }) => {
+ node._textData = node.createTextNode(text)
+ const { width, height } = node.getNodeRect()
+ node.width = width
+ node.height = height
+ node.layout()
+ this.mindMap.render(() => {
+ this.textEdit.updateTextEditNode()
+ })
+ })
+ }
}
// 性能模式,懒加载节点
diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js
index ed70863b..6aa334bd 100644
--- a/simple-mind-map/src/core/render/TextEdit.js
+++ b/simple-mind-map/src/core/render/TextEdit.js
@@ -25,6 +25,8 @@ export default class TextEdit {
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
this.cacheEditingText = ''
this.hasBodyMousedown = false
+ this.textNodePaddingX = 5
+ this.textNodePaddingY = 3
this.bindEvent()
}
@@ -214,7 +216,7 @@ export default class TextEdit {
this.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
- this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
+ this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
this.textEditNode.setAttribute('contenteditable', true)
this.textEditNode.addEventListener('keyup', e => {
e.stopPropagation()
@@ -240,6 +242,13 @@ export default class TextEdit {
handleInputPasteText(e)
}
})
+ this.textEditNode.addEventListener('input', () => {
+ this.mindMap.emit('node_text_edit_change', {
+ node: this.currentNode,
+ text: this.getEditText(),
+ richText: false
+ })
+ })
const targetNode =
this.mindMap.opt.customInnerElsAppendTo || document.body
targetNode.appendChild(this.textEditNode)
@@ -256,8 +265,10 @@ export default class TextEdit {
node.style.domText(this.textEditNode, scale, isMultiLine)
this.textEditNode.style.zIndex = nodeTextEditZIndex
this.textEditNode.innerHTML = textLines.join('
')
- this.textEditNode.style.minWidth = rect.width + 10 + 'px'
- this.textEditNode.style.minHeight = rect.height + 6 + 'px'
+ this.textEditNode.style.minWidth =
+ rect.width + this.textNodePaddingX * 2 + 'px'
+ this.textEditNode.style.minHeight =
+ rect.height + this.textNodePaddingY * 2 + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
@@ -280,6 +291,24 @@ export default class TextEdit {
this.cacheEditingText = ''
}
+ // 更新文本编辑框的大小和位置
+ updateTextEditNode() {
+ if (this.mindMap.richText) {
+ this.mindMap.richText.updateTextEditNode()
+ return
+ }
+ if (!this.showTextEdit || !this.currentNode) {
+ return
+ }
+ const rect = this.currentNode._textData.node.node.getBoundingClientRect()
+ this.textEditNode.style.minWidth =
+ rect.width + this.textNodePaddingX * 2 + 'px'
+ this.textEditNode.style.minHeight =
+ rect.height + this.textNodePaddingY * 2 + 'px'
+ this.textEditNode.style.left = rect.left + 'px'
+ this.textEditNode.style.top = rect.top + 'px'
+ }
+
// 删除文本编辑元素
removeTextEditEl() {
if (this.mindMap.richText) {
diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js
index a2c0ec6e..5bc5b2a9 100644
--- a/simple-mind-map/src/core/render/node/nodeCreateContents.js
+++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js
@@ -114,8 +114,10 @@ function createIconNode() {
}
// 创建富文本节点
-function createRichTextNode() {
- const { textAutoWrapWidth } = this.mindMap.opt
+function createRichTextNode(specifyText) {
+ let text =
+ typeof specifyText === 'string' ? specifyText : this.getData('text')
+ const { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
let g = new G()
// 重新设置富文本节点内容
let recoverText = false
@@ -129,7 +131,6 @@ function createRichTextNode() {
recoverText = true
}
}
- let text = this.getData('text')
if (recoverText && !isUndef(text)) {
// 判断节点内容是否是富文本
let isRichText = checkIsRichText(text)
@@ -153,7 +154,7 @@ function createRichTextNode() {
text: text
})
}
- let html = `
abc123我和你
' + div.innerHTML = `${emptyTextMeasureHeightText}
` let elTmp = div.children[0] elTmp.classList.add('smm-richtext-node-wrap') height = elTmp.getBoundingClientRect().height @@ -199,10 +200,12 @@ function createRichTextNode() { } // 创建文本节点 -function createTextNode() { +function createTextNode(specifyText) { if (this.getData('richText')) { - return this.createRichTextNode() + return this.createRichTextNode(specifyText) } + const text = + typeof specifyText === 'string' ? specifyText : this.getData('text') if (this.getData('resetRichText')) { delete this.nodeData.data.resetRichText } @@ -212,10 +215,11 @@ function createTextNode() { // 文本超长自动换行 let textStyle = this.style.getTextFontStyle() let textArr = [] - if (!isUndef(this.getData('text'))) { - textArr = String(this.getData('text')).split(/\n/gim) + if (!isUndef(text)) { + textArr = String(text).split(/\n/gim) } - let maxWidth = this.mindMap.opt.textAutoWrapWidth + const { textAutoWrapWidth: maxWidth, emptyTextMeasureHeightText } = + this.mindMap.opt let isMultiLine = false textArr.forEach((item, index) => { let arr = item.split('') @@ -247,6 +251,13 @@ function createTextNode() { g.add(node) }) let { width, height } = g.bbox() + // 如果文本为空,那么需要计算一个默认高度 + if (height <= 0) { + const tmpNode = new Text().text(emptyTextMeasureHeightText) + this.style.text(tmpNode) + const tmpBbox = tmpNode.bbox() + height = tmpBbox.height + } width = Math.min(Math.ceil(width), maxWidth) height = Math.ceil(height) g.attr('data-width', width) diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index b8cfb584..765d76cd 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -57,6 +57,8 @@ class RichText { this.cacheEditingText = '' this.lostStyle = false this.isCompositing = false + this.textNodePaddingX = 6 + this.textNodePaddingY = 4 this.initOpt() this.extendQuill() this.appendCss() @@ -71,14 +73,17 @@ class RichText { // 绑定事件 bindEvent() { this.onCompositionStart = this.onCompositionStart.bind(this) + this.onCompositionUpdate = this.onCompositionUpdate.bind(this) this.onCompositionEnd = this.onCompositionEnd.bind(this) window.addEventListener('compositionstart', this.onCompositionStart) + window.addEventListener('compositionupdate', this.onCompositionUpdate) window.addEventListener('compositionend', this.onCompositionEnd) } // 解绑事件 unbindEvent() { window.removeEventListener('compositionstart', this.onCompositionStart) + window.removeEventListener('compositionupdate', this.onCompositionUpdate) window.removeEventListener('compositionend', this.onCompositionEnd) } @@ -198,8 +203,8 @@ class RichText { let scaleX = rect.width / originWidth let scaleY = rect.height / originHeight // 内边距 - let paddingX = 6 - let paddingY = 4 + let paddingX = this.textNodePaddingX + let paddingY = this.textNodePaddingY if (richTextEditFakeInPlace) { let paddingValue = node.getPaddingVale() paddingX = paddingValue.paddingX @@ -287,6 +292,20 @@ class RichText { this.cacheEditingText = '' } + // 更新文本编辑框的大小和位置 + updateTextEditNode() { + if (!this.node) return + const rect = this.node._textData.node.node.getBoundingClientRect() + const g = this.node._textData.node + const originWidth = g.attr('data-width') + const originHeight = g.attr('data-height') + this.textEditNode.style.minWidth = + originWidth + this.textNodePaddingX * 2 + 'px' + this.textEditNode.style.minHeight = originHeight + 'px' + this.textEditNode.style.left = rect.left + 'px' + this.textEditNode.style.top = rect.top + 'px' + } + // 删除文本编辑框元素 removeTextEditEl() { if (!this.textEditNode) return @@ -491,6 +510,11 @@ class RichText { this.setTextStyleIfNotRichText(this.node) this.lostStyle = false } + this.mindMap.emit('node_text_edit_change', { + node: this.node, + text: this.getEditText(), + richText: true + }) }) // 拦截粘贴,只允许粘贴纯文本 // this.quill.clipboard.addMatcher(Node.TEXT_NODE, node => { @@ -545,6 +569,16 @@ class RichText { this.isCompositing = true } + // 中文输入中 + onCompositionUpdate() { + if (!this.showTextEdit || !this.node) return + this.mindMap.emit('node_text_edit_change', { + node: this.node, + text: this.getEditText(), + richText: true + }) + } + // 中文输入结束 onCompositionEnd() { if (!this.showTextEdit) {