From eb342bf69ba9aa79d7818fc504b8a87f4c424753 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, 17 Oct 2024 13:51:17 +0800 Subject: [PATCH] =?UTF-8?q?Feat=EF=BC=9A=E5=BD=93=E5=BC=80=E5=90=AFopenRea?= =?UTF-8?q?ltimeRenderOnNodeTextEdit=E9=80=89=E9=A1=B9=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E4=BC=9A=E5=8E=BB=E9=99=A4=E6=96=87=E6=9C=AC=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E6=A1=86=E7=9A=84=E8=83=8C=E6=99=AF=E5=92=8C=E9=98=B4=E5=BD=B1?= =?UTF-8?q?=EF=BC=8C=E8=BE=BE=E5=88=B0=E7=B1=BB=E4=BC=BC=E5=8E=9F=E5=9C=B0?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E7=9A=84=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/core/render/Render.js | 95 ++++++++++--------- simple-mind-map/src/core/render/TextEdit.js | 72 ++++++++++---- .../src/core/render/node/MindMapNode.js | 9 +- simple-mind-map/src/plugins/RichText.js | 40 ++++++-- 4 files changed, 147 insertions(+), 69 deletions(-) diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 1120d688..0d74e8ce 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -133,6 +133,11 @@ class Render { // 绑定事件 bindEvent() { + const { + openPerformance, + performanceConfig, + openRealtimeRenderOnNodeTextEdit + } = this.mindMap.opt // 画布点击事件清除当前激活节点列表 this.mindMap.on('draw_click', e => { this.clearActiveNodeListOnDrawClick(e, 'click') @@ -147,35 +152,6 @@ class Render { this.setRootNodeCenter() }) // 性能模式 - 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(() => { - // 输入框的left不会改变,所以无需更新 - this.textEdit.updateTextEditNode(['left']) - }) - }) - } - // 处理非https下的复制黏贴问题 - // 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡 - // if (!navigator.clipboard) { - // this.handlePaste = this.handlePaste.bind(this) - // window.addEventListener('paste', this.handlePaste) - // this.mindMap.on('beforeDestroy', () => { - // window.removeEventListener('paste', this.handlePaste) - // }) - // } - } - - // 性能模式,懒加载节点 - performanceMode() { - const { openPerformance, performanceConfig } = this.mindMap.opt const onViewDataChange = throttle(() => { if (this.root) { this.mindMap.emit('node_tree_render_start') @@ -188,24 +164,57 @@ class Render { ) } }, performanceConfig.time) - let lastOpen = false - this.mindMap.on('before_update_config', opt => { - lastOpen = opt.openPerformance - }) - this.mindMap.on('after_update_config', opt => { - if (opt.openPerformance && !lastOpen) { - // 动态开启性能模式 - this.mindMap.on('view_data_change', onViewDataChange) + if (openPerformance) { + this.mindMap.on('view_data_change', onViewDataChange) + } + // 文本编辑时实时更新节点大小 + this.onNodeTextEditChange = this.onNodeTextEditChange.bind(this) + if (openRealtimeRenderOnNodeTextEdit) { + this.mindMap.on('node_text_edit_change', this.onNodeTextEditChange) + } + // 监听配置改变事件 + this.mindMap.on('after_update_config', (opt, lastOpt) => { + // 更新openPerformance配置 + if (opt.openPerformance !== lastOpt.openPerformance) { + this.mindMap[opt.openPerformance ? 'on' : 'off']( + 'view_data_change', + onViewDataChange + ) this.forceLoadNode() } - if (!opt.openPerformance && lastOpen) { - // 动态关闭性能模式 - this.mindMap.off('view_data_change', onViewDataChange) - this.forceLoadNode() + // 更新openRealtimeRenderOnNodeTextEdit配置 + if ( + opt.openRealtimeRenderOnNodeTextEdit !== + lastOpt.openRealtimeRenderOnNodeTextEdit + ) { + this.mindMap[opt.openRealtimeRenderOnNodeTextEdit ? 'on' : 'off']( + 'node_text_edit_change', + this.onNodeTextEditChange + ) } }) - if (!openPerformance) return - this.mindMap.on('view_data_change', onViewDataChange) + // 处理非https下的复制黏贴问题 + // 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡 + // if (!navigator.clipboard) { + // this.handlePaste = this.handlePaste.bind(this) + // window.addEventListener('paste', this.handlePaste) + // this.mindMap.on('beforeDestroy', () => { + // window.removeEventListener('paste', this.handlePaste) + // }) + // } + } + + // 监听文本编辑事件,实时更新节点大小 + onNodeTextEditChange({ node, text }) { + node._textData = node.createTextNode(text) + const { width, height } = node.getNodeRect() + node.width = width + node.height = height + node.layout() + this.mindMap.render(() => { + // 输入框的left不会改变,所以无需更新 + this.textEdit.updateTextEditNode(['left']) + }) } // 强制渲染节点,不考虑是否在画布可视区域内 diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index def6b36e..07649c66 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -96,6 +96,22 @@ export default class TextEdit { this.mindMap.on('beforeDestroy', () => { this.unBindEvent() }) + this.mindMap.on('after_update_config', (opt, lastOpt) => { + if ( + opt.openRealtimeRenderOnNodeTextEdit !== + lastOpt.openRealtimeRenderOnNodeTextEdit + ) { + if (this.mindMap.richText) { + this.mindMap.richText.onOpenRealtimeRenderOnNodeTextEditConfigUpdate( + opt.openRealtimeRenderOnNodeTextEdit + ) + } else { + this.onOpenRealtimeRenderOnNodeTextEditConfigUpdate( + opt.openRealtimeRenderOnNodeTextEdit + ) + } + } + }) } // 解绑事件 @@ -162,7 +178,8 @@ export default class TextEdit { if (node.isUseCustomNodeContent()) { return } - const { beforeTextEdit } = this.mindMap.opt + const { beforeTextEdit, openRealtimeRenderOnNodeTextEdit } = + this.mindMap.opt if (typeof beforeTextEdit === 'function') { let isShow = false try { @@ -176,7 +193,12 @@ export default class TextEdit { this.currentNode = node const { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node) this.mindMap.view.translateXY(offsetLeft, offsetTop) - const rect = node._textData.node.node.getBoundingClientRect() + const g = node._textData.node + const rect = g.node.getBoundingClientRect() + // 如果开启了大小实时更新,那么直接隐藏节点原文本 + if (openRealtimeRenderOnNodeTextEdit) { + g.hide() + } const params = { node, rect, @@ -191,6 +213,19 @@ export default class TextEdit { this.showEditTextBox(params) } + // 当openRealtimeRenderOnNodeTextEdit配置更新后需要更新编辑框样式 + onOpenRealtimeRenderOnNodeTextEditConfigUpdate( + openRealtimeRenderOnNodeTextEdit + ) { + if (!this.textEditNode) return + this.textEditNode.style.backgroundColor = openRealtimeRenderOnNodeTextEdit + ? 'transparent' + : '#fff' + this.textEditNode.style.boxShadow = openRealtimeRenderOnNodeTextEdit + ? 'none' + : '0 0 20px rgba(0,0,0,.5)' + } + // 处理画布缩放 onScale() { const node = this.getCurrentEditNode() @@ -212,8 +247,12 @@ export default class TextEdit { // 显示文本编辑框 showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }) { if (this.showTextEdit) return - const { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText } = - this.mindMap.opt + const { + nodeTextEditZIndex, + textAutoWrapWidth, + selectTextOnEnterEditText, + openRealtimeRenderOnNodeTextEdit + } = this.mindMap.opt if (!isFromScale) { this.mindMap.emit('before_show_text_edit') } @@ -224,8 +263,12 @@ export default class TextEdit { this.textEditNode.style.cssText = ` position: fixed; box-sizing: border-box; - background-color:#fff; - box-shadow: 0 0 20px rgba(0,0,0,.5); + ${ + openRealtimeRenderOnNodeTextEdit + ? '' + : `background-color:#fff; + box-shadow: 0 0 20px rgba(0,0,0,.5);` + } padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px; margin-left: -${this.textNodePaddingX}px; margin-top: -${this.textNodePaddingY}px; @@ -351,17 +394,8 @@ export default class TextEdit { if (!this.showTextEdit) { return } - this.mindMap.execCommand( - 'SET_NODE_TEXT', - this.currentNode, - this.getEditText() - ) - if (this.currentNode.isGeneralization) { - // 概要节点 - this.currentNode.generalizationBelongNode.updateGeneralization() - } - this.mindMap.render() const currentNode = this.currentNode + const text = this.getEditText() this.currentNode = null this.textEditNode.style.display = 'none' this.textEditNode.innerHTML = '' @@ -370,6 +404,12 @@ export default class TextEdit { this.textEditNode.style.fontWeight = 'normal' this.textEditNode.style.transform = 'translateY(0)' this.showTextEdit = false + this.mindMap.execCommand('SET_NODE_TEXT', currentNode, text) + // if (currentNode.isGeneralization) { + // // 概要节点 + // currentNode.generalizationBelongNode.updateGeneralization() + // } + this.mindMap.render() this.mindMap.emit( 'hide_text_edit', this.textEditNode, diff --git a/simple-mind-map/src/core/render/node/MindMapNode.js b/simple-mind-map/src/core/render/node/MindMapNode.js index 910bb5ee..72db90fe 100644 --- a/simple-mind-map/src/core/render/node/MindMapNode.js +++ b/simple-mind-map/src/core/render/node/MindMapNode.js @@ -428,7 +428,8 @@ class MindMapNode { if (!this.group) return // 清除之前的内容 this.group.clear() - const { hoverRectPadding, tagPosition } = this.mindMap.opt + const { hoverRectPadding, tagPosition, openRealtimeRenderOnNodeTextEdit } = + this.mindMap.opt let { width, height, textContentItemMargin } = this let { paddingY } = this.getPaddingVale() const halfBorderWidth = this.getBorderWidth() / 2 @@ -524,6 +525,12 @@ class MindMapNode { .x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题 .x(textContentOffsetX) .y((textContentHeight - this._textData.height) / 2) + // 如果开启了文本编辑实时渲染,需要判断当前渲染的节点是否是正在编辑的节点,是的话将透明度设置为0不显示 + if (openRealtimeRenderOnNodeTextEdit) { + this._textData.node.opacity( + this.mindMap.renderer.textEdit.getCurrentEditNode() === this ? 0 : 1 + ) + } textContentNested.add(this._textData.node) textContentOffsetX += this._textData.width + textContentItemMargin } diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index 0695adc7..5fd14e06 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -189,7 +189,8 @@ class RichText { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText, - transformRichTextOnEnterEdit + transformRichTextOnEnterEdit, + openRealtimeRenderOnNodeTextEdit } = this.mindMap.opt textAutoWrapWidth = node.hasCustomWidth() ? node.customTextWidth @@ -222,7 +223,11 @@ class RichText { this.textEditNode.style.cssText = ` position:fixed; box-sizing: border-box; - box-shadow: 0 0 20px rgba(0,0,0,.5); + ${ + openRealtimeRenderOnNodeTextEdit + ? '' + : 'box-shadow: 0 0 20px rgba(0,0,0,.5);' + } outline: none; word-break: break-all; padding: ${paddingY}px ${paddingX}px; @@ -244,7 +249,9 @@ class RichText { this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px` this.textEditNode.style.marginTop = `-${paddingY * scaleY}px` this.textEditNode.style.zIndex = nodeTextEditZIndex - this.textEditNode.style.background = this.getBackground(node) + if (!openRealtimeRenderOnNodeTextEdit) { + this.textEditNode.style.background = this.getBackground(node) + } this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px' this.textEditNode.style.minHeight = originHeight + 'px' this.textEditNode.style.left = rect.left + 'px' @@ -297,6 +304,21 @@ class RichText { this.cacheEditingText = '' } + // 当openRealtimeRenderOnNodeTextEdit配置更新后需要更新编辑框样式 + onOpenRealtimeRenderOnNodeTextEditConfigUpdate( + openRealtimeRenderOnNodeTextEdit + ) { + if (!this.textEditNode) return + this.textEditNode.style.background = openRealtimeRenderOnNodeTextEdit + ? 'transparent' + : this.node + ? this.getBackground(node) + : '' + this.textEditNode.style.boxShadow = openRealtimeRenderOnNodeTextEdit + ? 'none' + : '0 0 20px rgba(0,0,0,.5)' + } + // 更新文本编辑框的大小和位置 updateTextEditNode() { if (!this.node) return @@ -388,6 +410,12 @@ class RichText { let html = this.getEditText() html = this.sortHtmlNodeStyles(html) const list = nodes && nodes.length > 0 ? nodes : [this.node] + const node = this.node + this.textEditNode.style.display = 'none' + this.showTextEdit = false + this.mindMap.emit('rich_text_selection_change', false) + this.node = null + this.isInserting = false list.forEach(node => { this.mindMap.execCommand('SET_NODE_TEXT', node, html, true) // if (node.isGeneralization) { @@ -396,12 +424,6 @@ class RichText { // } this.mindMap.render() }) - const node = this.node - this.textEditNode.style.display = 'none' - this.showTextEdit = false - this.mindMap.emit('rich_text_selection_change', false) - this.node = null - this.isInserting = false this.mindMap.emit('hide_text_edit', this.textEditNode, list, node) }