From c6a3f4ac7b33724db4274b173b37a2eda3598f1d Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Fri, 11 Aug 2023 17:39:04 +0800 Subject: [PATCH 01/15] =?UTF-8?q?Feat=EF=BC=9A=E5=8E=BB=E9=99=A4=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E9=9A=90=E8=97=8F=E8=BE=93=E5=85=A5=E6=A1=86=EF=BC=8C?= =?UTF-8?q?=E9=80=9A=E8=BF=87navigator.clipboard=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=A4=8D=E5=88=B6=E7=B2=98=E8=B4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/constants/defaultOptions.js | 3 - simple-mind-map/src/core/render/Render.js | 30 +++++-- simple-mind-map/src/core/render/TextEdit.js | 88 +++++-------------- simple-mind-map/src/plugins/RichText.js | 5 ++ web/src/pages/Edit/components/Outline.vue | 2 - 5 files changed, 48 insertions(+), 80 deletions(-) diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js index dfd07c83..d144a5a2 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -127,10 +127,7 @@ export const defaultOpt = { customInnerElsAppendTo: null, // 拖拽元素时,指示元素新位置的块的最大高度 nodeDragPlaceholderMaxSize: 20, - // 是否允许创建一个隐藏的输入框,该输入框会在节点激活时聚焦,用于粘贴数据和自动进入文本编辑状态 - enableCreateHiddenInput: true, // 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式 - // 该配置在enableCreateHiddenInput设为true时生效 enableAutoEnterTextEditWhenKeydown: true, // 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果 // 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果 diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 8944dba9..b5294043 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -108,10 +108,6 @@ class Render { this.mindMap.execCommand('CLEAR_ACTIVE_NODE') } }) - // 粘贴事件 - this.mindMap.on('paste', data => { - this.onPaste(data) - }) // let timer = null // this.mindMap.on('view_data_change', () => { // clearTimeout(timer) @@ -273,8 +269,7 @@ class Render { this.copy = this.copy.bind(this) this.mindMap.keyCommand.addShortcut('Control+c', this.copy) this.mindMap.keyCommand.addShortcut('Control+v', () => { - // 隐藏输入框可能会失去焦点,所以要重新聚焦 - this.textEdit.focusHiddenInput() + this.onPaste() }) this.cut = this.cut.bind(this) this.mindMap.keyCommand.addShortcut('Control+x', this.cut) @@ -608,7 +603,28 @@ class Render { } // 粘贴事件 - async onPaste({ text, img }) { + async onPaste() { + // 读取剪贴板的文字和图片 + let text = null + let img = null + if (navigator.clipboard) { + try { + text = await navigator.clipboard.readText() + const items = await navigator.clipboard.read() + if (items && items.length > 0) { + for (const clipboardItem of items) { + for (const type of clipboardItem.types) { + if (/^image\//.test(type)) { + img = await clipboardItem.getType(type) + break + } + } + } + } + } catch (error) { + console.log(error) + } + } // 检查剪切板数据是否有变化 // 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法 const imgSize = img ? img.size : 0 diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index 92499d98..0926d3e0 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -1,4 +1,4 @@ -import { getStrWithBrFromHtml, checkNodeOuter, isMobile } from '../../utils' +import { getStrWithBrFromHtml, checkNodeOuter } from '../../utils' // 节点文字编辑类 export default class TextEdit { @@ -10,16 +10,11 @@ export default class TextEdit { this.currentNode = null // 文本编辑框 this.textEditNode = null - // 隐藏的文本输入框 - this.hiddenInputEl = null - // 节点激活时默认聚焦到隐藏输入框 - this.enableFocus = true // 文本编辑框是否显示 this.showTextEdit = false // 如果编辑过程中缩放画布了,那么缓存当前编辑的内容 this.cacheEditingText = '' this.bindEvent() - this.createHiddenInput() } // 事件 @@ -51,10 +46,6 @@ export default class TextEdit { this.mindMap.on('before_node_active', () => { this.hideEditTextBox() }) - // 节点激活事件 - this.mindMap.on('node_active', () => { - this.focusHiddenInput() - }) // 注册编辑快捷键 this.mindMap.keyCommand.addShortcut('F2', () => { if (this.renderer.activeNodeList.length <= 0) { @@ -63,74 +54,29 @@ export default class TextEdit { this.show(this.renderer.activeNodeList[0]) }) this.mindMap.on('scale', this.onScale) - } - - // 创建一个隐藏的文本输入框 - createHiddenInput() { - const { enableCreateHiddenInput, enableAutoEnterTextEditWhenKeydown } = - this.mindMap.opt - if (this.hiddenInputEl || isMobile() || !enableCreateHiddenInput) return - this.hiddenInputEl = document.createElement('input') - this.hiddenInputEl.type = 'text' - this.hiddenInputEl.style.cssText = ` - position: fixed; - left: -99999px; - top: -99999px; - ` - // 监听按键事件 - if (enableAutoEnterTextEditWhenKeydown) { - this.hiddenInputEl.addEventListener('keydown', e => { + // // 监听按键事件,判断是否自动进入文本编辑模式 + if (this.mindMap.opt.enableAutoEnterTextEditWhenKeydown) { + window.addEventListener('keydown', e => { const activeNodeList = this.mindMap.renderer.activeNodeList if (activeNodeList.length <= 0 || activeNodeList.length > 1) return const node = activeNodeList[0] // 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式 - const keyCode = e.keyCode - if ( - node && - (keyCode === 229 || - (keyCode >= 65 && keyCode <= 90) || - (keyCode >= 48 && keyCode <= 57)) && - !this.mindMap.keyCommand.hasCombinationKey(e) - ) { + if (node && this.checkIsAutoEnterTextEditKey(e)) { this.show(node) } }) } - // 监听粘贴事件 - this.hiddenInputEl.addEventListener('paste', async event => { - event.preventDefault() - const text = (event.clipboardData || window.clipboardData).getData('text') - const files = event.clipboardData.files - let img = null - if (files.length > 0) { - for (let i = 0; i < files.length; i++) { - if (/^image\//.test(files[i].type)) { - img = files[i] - break - } - } - } - this.mindMap.emit('paste', { - text, - img - }) - }) - document.body.appendChild(this.hiddenInputEl) } - // 让隐藏的文本输入框聚焦 - focusHiddenInput() { - if (this.hiddenInputEl && this.enableFocus) this.hiddenInputEl.focus() - } - - // 关闭默认聚焦 - stopFocusOnNodeActive() { - this.enableFocus = false - } - - // 开启默认聚焦 - openFocusOnNodeActive() { - this.enableFocus = true + // 判断是否是自动进入文本编模式的按钮 + checkIsAutoEnterTextEditKey(e) { + const keyCode = e.keyCode + return ( + (keyCode === 229 || + (keyCode >= 65 && keyCode <= 90) || + (keyCode >= 48 && keyCode <= 57)) && + !this.mindMap.keyCommand.hasCombinationKey(e) + ) } // 注册临时快捷键 @@ -188,6 +134,7 @@ export default class TextEdit { // 显示文本编辑框 showEditTextBox(node, rect, isInserting) { + if (this.showTextEdit) return this.mindMap.emit('before_show_text_edit') this.registerTmpShortcut() if (!this.textEditNode) { @@ -203,6 +150,11 @@ export default class TextEdit { this.textEditNode.addEventListener('mousedown', e => { e.stopPropagation() }) + this.textEditNode.addEventListener('keydown', e => { + if (this.checkIsAutoEnterTextEditKey(e)) { + e.stopPropagation() + } + }) const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body targetNode.appendChild(this.textEditNode) diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index 7b818e4e..c5830b44 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -198,6 +198,11 @@ class RichText { this.textEditNode.addEventListener('mousedown', e => { e.stopPropagation() }) + this.textEditNode.addEventListener('keydown', e => { + if (this.mindMap.renderer.textEdit.checkIsAutoEnterTextEditKey(e)) { + e.stopPropagation() + } + }) const targetNode = customInnerElsAppendTo || document.body targetNode.appendChild(this.textEditNode) } diff --git a/web/src/pages/Edit/components/Outline.vue b/web/src/pages/Edit/components/Outline.vue index 87e2b9ca..fa1fe23b 100644 --- a/web/src/pages/Edit/components/Outline.vue +++ b/web/src/pages/Edit/components/Outline.vue @@ -258,9 +258,7 @@ export default { this.notHandleDataChange = true const targetNode = this.mindMap.renderer.findNodeByUid(data.uid) if (targetNode && targetNode.nodeData.data.isActive) return - this.mindMap.renderer.textEdit.stopFocusOnNodeActive() this.mindMap.execCommand('GO_TARGET_NODE', data.uid, () => { - this.mindMap.renderer.textEdit.openFocusOnNodeActive() this.notHandleDataChange = false }) }, From 5745e4567ba0e6273f3079b30b05f49e2b13b94f Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Sat, 12 Aug 2023 10:57:51 +0800 Subject: [PATCH 02/15] =?UTF-8?q?Feat:=E4=BF=AE=E6=94=B9=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E7=B2=98=E8=B4=B4=E5=AE=9E=E7=8E=B0=E6=96=B9=E5=BC=8F,?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=E5=88=9B=E5=BB=BA=E9=9A=90=E8=97=8F=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86,=E6=94=AF=E6=8C=81=E8=B7=A8=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=99=A8=E7=B2=98=E8=B4=B4=E6=80=9D=E7=BB=B4=E5=AF=BC?= =?UTF-8?q?=E5=9B=BE=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/package-lock.json | 4 +- simple-mind-map/src/core/render/Render.js | 58 ++++++++++++++++++++--- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/simple-mind-map/package-lock.json b/simple-mind-map/package-lock.json index ee730af9..a3858abc 100644 --- a/simple-mind-map/package-lock.json +++ b/simple-mind-map/package-lock.json @@ -1,11 +1,11 @@ { "name": "simple-mind-map", - "version": "0.6.12", + "version": "0.6.13", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.6.12", + "version": "0.6.13", "license": "MIT", "dependencies": { "@svgdotjs/svg.js": "^3.0.16", diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index b5294043..752ac75a 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -444,7 +444,12 @@ class Render { } // 插入同级节点,多个节点只会操作第一个节点 - insertNode(openEdit = true, appointNodes = [], appointData = null) { + insertNode( + openEdit = true, + appointNodes = [], + appointData = null, + appointChildren = [] + ) { appointNodes = this.formatAppointNodes(appointNodes) if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return @@ -480,14 +485,19 @@ class Render { resetRichText: isRichText, ...(appointData || {}) }, - children: [] + children: [...appointChildren] }) this.mindMap.render() } } // 插入子节点 - insertChildNode(openEdit = true, appointNodes = [], appointData = null) { + insertChildNode( + openEdit = true, + appointNodes = [], + appointData = null, + appointChildren = [] + ) { appointNodes = this.formatAppointNodes(appointNodes) if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return @@ -518,7 +528,7 @@ class Render { resetRichText: isRichText, ...(appointData || {}) }, - children: [] + children: [...appointChildren] }) // 插入子节点时自动展开子节点 node.nodeData.data.expand = true @@ -586,15 +596,29 @@ class Render { // 复制节点 copy() { this.beingCopyData = this.copyNode() + this.setCoptyDataToClipboard(this.beingCopyData) } // 剪切节点 cut() { this.mindMap.execCommand('CUT_NODE', copyData => { this.beingCopyData = copyData + this.setCoptyDataToClipboard(copyData) }) } + // 将粘贴或剪切的数据设置到用户剪切板中 + setCoptyDataToClipboard(data) { + if (navigator.clipboard) { + navigator.clipboard.writeText( + JSON.stringify({ + simpleMindMap: true, + data + }) + ) + } + } + // 粘贴节点 paste() { if (this.beingCopyData) { @@ -642,9 +666,29 @@ class Render { if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) { // 存在文本,则创建子节点 if (text) { - this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], { - text - }) + // 判断粘贴的是否是simple-mind-map的数据 + let smmData = null + try { + const parsedData = JSON.parse(text) + if (parsedData && parsedData.simpleMindMap) { + smmData = parsedData.data + } + } catch (error) {} + if (smmData) { + this.mindMap.execCommand( + 'INSERT_CHILD_NODE', + false, + [], + { + ...smmData.data + }, + [...smmData.children] + ) + } else { + this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], { + text + }) + } } // 存在图片,则添加到当前激活节点 if (img) { From 94478fe9f3e641f84ad317005637f8c954f9a9f5 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Sat, 12 Aug 2023 11:12:11 +0800 Subject: [PATCH 03/15] =?UTF-8?q?Demo:=E4=BF=AE=E5=A4=8D=E5=BC=80=E5=90=AF?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E8=87=AA=E5=8A=A8=E8=BF=9B=E5=85=A5=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E7=BC=96=E8=BE=91=E6=A8=A1=E5=BC=8F=E5=92=8C=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E8=BE=93=E5=85=A5=E6=A1=86=E5=86=B2=E7=AA=81=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/constants/defaultOptions.js | 3 +- web/src/pages/Edit/components/BaseStyle.vue | 173 ++++++++++++++---- web/src/pages/Edit/components/Edit.vue | 3 +- web/src/pages/Edit/components/Export.vue | 3 + .../pages/Edit/components/NodeHyperlink.vue | 8 +- web/src/pages/Edit/components/NodeImage.vue | 3 +- web/src/pages/Edit/components/NodeNote.vue | 2 +- web/src/pages/Edit/components/NodeTag.vue | 1 + web/src/pages/Edit/components/Search.vue | 2 + 9 files changed, 156 insertions(+), 42 deletions(-) diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js index d144a5a2..f4e9004c 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -128,7 +128,8 @@ export const defaultOpt = { // 拖拽元素时,指示元素新位置的块的最大高度 nodeDragPlaceholderMaxSize: 20, // 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式 - enableAutoEnterTextEditWhenKeydown: true, + // 开启该特性后,需要给你的输入框绑定keydown事件,并禁止冒泡 + enableAutoEnterTextEditWhenKeydown: false, // 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果 // 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果 richTextEditFakeInPlace: false diff --git a/web/src/pages/Edit/components/BaseStyle.vue b/web/src/pages/Edit/components/BaseStyle.vue index b3f99296..d068ca7d 100644 --- a/web/src/pages/Edit/components/BaseStyle.vue +++ b/web/src/pages/Edit/components/BaseStyle.vue @@ -277,7 +277,9 @@