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 = `
${this.getData('text')}
` + let html = `
${text}
` if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) { this.mindMap.commonCaches.measureRichtextNodeTextSizeEl = document.createElement('div') @@ -174,7 +175,7 @@ function createRichTextNode() { let { width, height } = el.getBoundingClientRect() // 如果文本为空,那么需要计算一个默认高度 if (height <= 0) { - div.innerHTML = '

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) {