From 740e2e341079a8caca8b48746edf0f0f47570342 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Thu, 21 Sep 2023 09:00:25 +0800 Subject: [PATCH 01/13] =?UTF-8?q?Fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E9=80=89=E8=8A=82=E7=82=B9=E6=97=B6=E9=80=89=E5=8C=BA=E6=9C=AA?= =?UTF-8?q?=E5=8C=85=E5=90=AB=E8=8A=82=E7=82=B9=E8=BE=B9=E7=95=8C=E6=97=B6?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E4=B8=8D=E4=BC=9A=E8=A2=AB=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/plugins/Select.js | 9 ++------- simple-mind-map/src/utils/index.js | 29 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/simple-mind-map/src/plugins/Select.js b/simple-mind-map/src/plugins/Select.js index 1998d192..c26ebb58 100644 --- a/simple-mind-map/src/plugins/Select.js +++ b/simple-mind-map/src/plugins/Select.js @@ -1,4 +1,4 @@ -import { bfsWalk, throttle } from '../utils' +import { bfsWalk, throttle, checkTwoRectIsOverlap } from '../utils' // 节点选择插件 class Select { @@ -210,24 +210,19 @@ class Select { left = left * scaleX + translateX top = top * scaleY + translateY if ( - ((left >= minx && left <= maxx) || (right >= minx && right <= maxx)) && - ((top >= miny && top <= maxy) || (bottom >= miny && bottom <= maxy)) + checkTwoRectIsOverlap(minx, maxx, miny, maxy, left, right, top, bottom) ) { - // this.mindMap.batchExecution.push('activeNode' + node.uid, () => { if (node.nodeData.data.isActive) { return } this.mindMap.renderer.setNodeActive(node, true) this.mindMap.renderer.addActiveNode(node) - // }) } else if (node.nodeData.data.isActive) { - // this.mindMap.batchExecution.push('activeNode' + node.uid, () => { if (!node.nodeData.data.isActive) { return } this.mindMap.renderer.setNodeActive(node, false) this.mindMap.renderer.removeActiveNode(node) - // }) } }) } diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index babbdb0e..bc13eba0 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -669,3 +669,32 @@ export const checkIsNodeStyleDataKey = key => { } return false } + +// 从节点实例列表里找出顶层的节点 +export const getTopAncestorsFomNodeList = list => { + let res = [] + list.forEach(node => { + if ( + !list.find(item => { + return item.uid !== node.uid && item.isParent(node) + }) + ) { + res.push(node) + } + }) + return res +} + +// 判断两个矩形是否重叠 +export const checkTwoRectIsOverlap = ( + minx1, + maxx1, + miny1, + maxy1, + minx2, + maxx2, + miny2, + maxy2 +) => { + return maxx1 > minx2 && maxx2 > minx1 && maxy1 > miny2 && maxy2 > miny1 +} From 036f845968392ff33c6815f3b965a2db41b9b98e Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Thu, 21 Sep 2023 09:01:37 +0800 Subject: [PATCH 02/13] =?UTF-8?q?Feat=EF=BC=9A=E6=94=AF=E6=8C=81=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E5=A4=9A=E4=B8=AA=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/core/render/Render.js | 133 ++++++++++------------ 1 file changed, 60 insertions(+), 73 deletions(-) diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index aaa52e01..ac9b85bc 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -741,81 +741,65 @@ class Render { // 将节点移动到另一个节点的前面 insertBefore(node, exist) { - if (node.isRoot) { - return - } - // 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新 - let nodeLayerChanged = - (node.layerIndex === 1 && exist.layerIndex !== 1) || - (node.layerIndex !== 1 && exist.layerIndex === 1) - // 移动节点 - let nodeParent = node.parent - let nodeBorthers = nodeParent.children - let nodeIndex = nodeBorthers.findIndex(item => { - return item.uid === node.uid - }) - if (nodeIndex === -1) { - return - } - nodeBorthers.splice(nodeIndex, 1) - nodeParent.nodeData.children.splice(nodeIndex, 1) - - // 目标节点 - let existParent = exist.parent - let existBorthers = existParent.children - let existIndex = existBorthers.findIndex(item => { - return item.uid === exist.uid - }) - if (existIndex === -1) { - return - } - existBorthers.splice(existIndex, 0, node) - existParent.nodeData.children.splice(existIndex, 0, node.nodeData) - this.mindMap.render(() => { - if (nodeLayerChanged) { - node.reRender() - } - }) + this.insertTo(node, exist, 'before') } // 将节点移动到另一个节点的后面 insertAfter(node, exist) { - if (node.isRoot) { - return - } - // 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新 - let nodeLayerChanged = - (node.layerIndex === 1 && exist.layerIndex !== 1) || - (node.layerIndex !== 1 && exist.layerIndex === 1) - // 移动节点 - let nodeParent = node.parent - let nodeBorthers = nodeParent.children - let nodeIndex = nodeBorthers.findIndex(item => { - return item.uid === node.uid - }) - if (nodeIndex === -1) { - return - } - nodeBorthers.splice(nodeIndex, 1) - nodeParent.nodeData.children.splice(nodeIndex, 1) + this.insertTo(node, exist, 'after') + } - // 目标节点 - let existParent = exist.parent - let existBorthers = existParent.children - let existIndex = existBorthers.findIndex(item => { - return item.uid === exist.uid + // 将节点移动到另一个节点的前面或后面 + insertTo(node, exist, dir = 'before') { + let nodeList = this.formatAppointNodes(node) + nodeList = nodeList.filter(item => { + return !item.isRoot }) - if (existIndex === -1) { - return + if (dir === 'after') { + nodeList.reverse() } - existIndex++ - existBorthers.splice(existIndex, 0, node) - existParent.nodeData.children.splice(existIndex, 0, node.nodeData) - this.mindMap.render(() => { - if (nodeLayerChanged) { - node.reRender() + nodeList.forEach(item => { + this.checkNodeLayerChange(item, exist) + // 移动节点 + let nodeParent = item.parent + let nodeBorthers = nodeParent.children + let nodeIndex = nodeBorthers.findIndex(item2 => { + return item.uid === item2.uid + }) + if (nodeIndex === -1) { + return } + nodeBorthers.splice(nodeIndex, 1) + nodeParent.nodeData.children.splice(nodeIndex, 1) + + // 目标节点 + let existParent = exist.parent + let existBorthers = existParent.children + let existIndex = existBorthers.findIndex(item2 => { + return item2.uid === exist.uid + }) + if (existIndex === -1) { + return + } + if (dir === 'after') { + existIndex++ + } + existBorthers.splice(existIndex, 0, item) + existParent.nodeData.children.splice(existIndex, 0, item.nodeData) }) + this.mindMap.render() + } + + // 如果是富文本模式,那么某些层级变化需要更新样式 + checkNodeLayerChange(node, toNode) { + if (this.mindMap.richText) { + let nodeLayerChanged = + (node.layerIndex === 1 && toNode.layerIndex !== 1) || + (node.layerIndex !== 1 && toNode.layerIndex === 1) + if (nodeLayerChanged) { + node.nodeData.data.resetRichText = true + } + } } // 移除节点 @@ -927,14 +911,17 @@ class Render { // 移动一个节点作为另一个节点的子节点 moveNodeTo(node, toNode) { - if (node.isRoot) { - return - } - // let copyData = copyNodeTree({}, node, false, true) - this.removeActiveNode(node) - this.removeOneNode(node) + let nodeList = this.formatAppointNodes(node) + nodeList = nodeList.filter(item => { + return !item.isRoot + }) + nodeList.forEach(item => { + this.checkNodeLayerChange(item, toNode) + this.removeActiveNode(item) + this.removeOneNode(item) + toNode.nodeData.children.push(item.nodeData) + }) this.mindMap.emit('node_active', null, [...this.activeNodeList]) - toNode.nodeData.children.push(node.nodeData) this.mindMap.render() if (toNode.isRoot) { toNode.destroy() From 69ef7faf49e45920d771967dcd6c4e6f5cdee615 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Thu, 21 Sep 2023 09:02:45 +0800 Subject: [PATCH 03/13] =?UTF-8?q?Feat=EF=BC=9A=E4=BC=98=E5=8C=96drag?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=8C=E6=94=AF=E6=8C=81=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E6=8B=96=E5=8A=A8=E5=A4=9A=E4=B8=AA=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/constants/constant.js | 2 +- .../src/constants/defaultOptions.js | 15 +- simple-mind-map/src/core/render/node/Node.js | 57 ++++ .../src/core/render/node/nodeExpandBtn.js | 1 + simple-mind-map/src/plugins/Drag.js | 322 +++++++++++------- 5 files changed, 276 insertions(+), 121 deletions(-) diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index d3d3f2b8..4e2e7be9 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -365,7 +365,7 @@ export const cssContent = ` stroke-width: 1; } - .smm-node:hover .smm-hover-node{ + .smm-node:not(.smm-node-dragging):hover .smm-hover-node{ display: block; } diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js index eaba6b86..a4040487 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -188,5 +188,18 @@ export const defaultOpt = { // 拖拽节点时鼠标移动到画布边缘是否开启画布自动移动 autoMoveWhenMouseInEdgeOnDrag: true, // 是否首次加载fit view - fit: false + fit: false, + // 拖拽多个节点时随鼠标移动的示意矩形的样式配置 + dragMultiNodeRectConfig: { + width: 40, + height: 20, + fill: '' // 填充颜色,如果不传默认使用连线的颜色 + }, + // 节点拖拽时新位置的示意矩形的填充颜色,如果不传默认使用连线的颜色 + dragPlaceholderRectFill: '', + // 节点拖拽时的透明度配置 + dragOpacityConfig: { + cloneNodeOpacity: 0.5, // 跟随鼠标移动的克隆节点或矩形的透明度 + beingDragNodeOpacity: 0.3 // 被拖拽节点的透明度 + } } diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index 493eb46f..952812c0 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -440,12 +440,14 @@ class Node { this.mindMap.emit('node_mouseup', this, e) }) this.group.on('mouseenter', e => { + if (this.isDrag) return this._isMouseenter = true // 显示展开收起按钮 this.showExpandBtn() this.mindMap.emit('node_mouseenter', this, e) }) this.group.on('mouseleave', e => { + if (!this._isMouseenter) return this._isMouseenter = false this.hideExpandBtn() this.mindMap.emit('node_mouseleave', this, e) @@ -701,6 +703,61 @@ class Node { } } + // 设置节点透明度 + // 包括连接线和下级节点 + setOpacity(val) { + // 自身及连线 + this.group.opacity(val) + this._lines.forEach(line => { + line.opacity(val) + }) + // 子节点 + this.children.forEach(item => { + item.setOpacity(val) + }) + // 概要节点 + if (this._generalizationNode) { + this._generalizationLine.opacity(val) + this._generalizationNode.group.opacity(val) + } + } + + // 隐藏子节点 + hideChildren() { + this._lines.forEach(item => { + item.hide() + }) + if (this.children && this.children.length) { + this.children.forEach(item => { + item.hide() + }) + } + } + + // 显示子节点 + showChildren() { + this._lines.forEach(item => { + item.show() + }) + if (this.children && this.children.length) { + this.children.forEach(item => { + item.show() + }) + } + } + + // 被拖拽中 + startDrag() { + this.isDrag = true + this.group.addClass('smm-node-dragging') + } + + // 拖拽结束 + endDrag() { + this.isDrag = false + this.group.removeClass('smm-node-dragging') + } + // 连线 renderLine(deep = false) { if (this.nodeData.data.expand === false) { diff --git a/simple-mind-map/src/core/render/node/nodeExpandBtn.js b/simple-mind-map/src/core/render/node/nodeExpandBtn.js index 004f74f4..ee79db27 100644 --- a/simple-mind-map/src/core/render/node/nodeExpandBtn.js +++ b/simple-mind-map/src/core/render/node/nodeExpandBtn.js @@ -136,6 +136,7 @@ function renderExpandBtn() { this._expandBtn.on('dblclick', e => { e.stopPropagation() }) + this._expandBtn.addClass('smm-expand-btn') this.group.add(this._expandBtn) } this._showExpandBtn = true diff --git a/simple-mind-map/src/plugins/Drag.js b/simple-mind-map/src/plugins/Drag.js index d3a68603..53d2a1a5 100644 --- a/simple-mind-map/src/plugins/Drag.js +++ b/simple-mind-map/src/plugins/Drag.js @@ -1,4 +1,4 @@ -import { bfsWalk, throttle } from '../utils' +import { bfsWalk, throttle, getTopAncestorsFomNodeList } from '../utils' import Base from '../layouts/Base' // 节点拖动插件 @@ -13,10 +13,14 @@ class Drag extends Base { // 复位 reset() { + // 是否正在跳转中 + this.isDragging = false + // 鼠标按下的节点 + this.mousedownNode = null + // 被拖拽中的节点列表 + this.beingDragNodeList = [] // 当前画布节点列表 this.nodeList = [] - // 当前拖拽节点 - this.node = null // 当前重叠节点 this.overlapNode = null // 当前上一个同级节点 @@ -27,16 +31,11 @@ class Drag extends Base { this.drawTransform = null // 克隆节点 this.clone = null - // 连接线 - this.line = null // 同级位置占位符 this.placeholder = null // 鼠标按下位置和节点左上角的偏移量 this.offsetX = 0 this.offsetY = 0 - // 克隆节点左上角的坐标 - this.cloneNodeLeft = 0 - this.cloneNodeTop = 0 // 当前鼠标是否按下 this.isMousedown = false // 拖拽的鼠标位置变量 @@ -53,45 +52,42 @@ class Drag extends Base { bindEvent() { this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this) this.mindMap.on('node_mousedown', (node, e) => { - if (this.mindMap.opt.readonly || node.isGeneralization) { - return - } - if (e.which !== 1 || node.isRoot) { - return - } - e.preventDefault() - // 计算鼠标按下的位置距离节点左上角的距离 - this.drawTransform = this.mindMap.draw.transform() - let { scaleX, scaleY, translateX, translateY } = this.drawTransform - let { x, y } = this.mindMap.toPos(e.clientX, e.clientY) - this.offsetX = x - (node.left * scaleX + translateX) - this.offsetY = y - (node.top * scaleY + translateY) - this.node = node - this.isMousedown = true - this.mouseDownX = x - this.mouseDownY = y - this.nodeTreeToList() - }) - this.mindMap.on('mousemove', e => { - if (this.mindMap.opt.readonly) { - return - } - if (!this.isMousedown) { - return - } - this.mindMap.emit('node_dragging', this.node) - e.preventDefault() - let { x, y } = this.mindMap.toPos(e.clientX, e.clientY) - this.mouseMoveX = x - this.mouseMoveY = y + // 只读模式、不是鼠标左键按下、按下的是概要节点或根节点直接返回 if ( - Math.abs(x - this.mouseDownX) <= this.checkDragOffset && - Math.abs(y - this.mouseDownY) <= this.checkDragOffset && - !this.node.isDrag + this.mindMap.opt.readonly || + e.which !== 1 || + node.isGeneralization || + node.isRoot ) { return } - this.mindMap.renderer.clearAllActive() + e.preventDefault() + this.isMousedown = true + // 记录鼠标按下时的节点 + this.mousedownNode = node + // 记录鼠标按下的坐标 + const { x, y } = this.mindMap.toPos(e.clientX, e.clientY) + this.mouseDownX = x + this.mouseDownY = y + }) + this.mindMap.on('mousemove', e => { + if (this.mindMap.opt.readonly || !this.isMousedown) { + return + } + e.preventDefault() + const { x, y } = this.mindMap.toPos(e.clientX, e.clientY) + this.mouseMoveX = x + this.mouseMoveY = y + // 还没开始移动时鼠标位移过小不认为是拖拽 + if ( + !this.isDragging && + Math.abs(x - this.mouseDownX) <= this.checkDragOffset && + Math.abs(y - this.mouseDownY) <= this.checkDragOffset + ) { + return + } + this.mindMap.emit('node_dragging') + this.handleStartMove() this.onMove(x, y, e) }) this.onMouseup = this.onMouseup.bind(this) @@ -105,9 +101,12 @@ class Drag extends Base { return } this.isMousedown = false - let _nodeIsDrag = this.node.isDrag - this.node.isDrag = false - this.node.show() + // 恢复被拖拽节点的临时设置 + this.beingDragNodeList.forEach(node => { + node.setOpacity(1) + node.showChildren() + node.endDrag() + }) this.removeCloneNode() let overlapNodeUid = this.overlapNode ? this.overlapNode.nodeData.data.uid @@ -117,17 +116,33 @@ class Drag extends Base { // 存在重叠子节点,则移动作为其子节点 if (this.overlapNode) { this.mindMap.renderer.setNodeActive(this.overlapNode, false) - this.mindMap.execCommand('MOVE_NODE_TO', this.node, this.overlapNode) + this.mindMap.execCommand( + 'MOVE_NODE_TO', + this.beingDragNodeList, + this.overlapNode + ) } else if (this.prevNode) { // 存在前一个相邻节点,作为其下一个兄弟节点 this.mindMap.renderer.setNodeActive(this.prevNode, false) - this.mindMap.execCommand('INSERT_AFTER', this.node, this.prevNode) + this.mindMap.execCommand( + 'INSERT_AFTER', + this.beingDragNodeList, + this.prevNode + ) } else if (this.nextNode) { // 存在下一个相邻节点,作为其前一个兄弟节点 this.mindMap.renderer.setNodeActive(this.nextNode, false) - this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode) - } else if (_nodeIsDrag && this.mindMap.opt.enableFreeDrag) { - // 自定义位置 + this.mindMap.execCommand( + 'INSERT_BEFORE', + this.beingDragNodeList, + this.nextNode + ) + } else if ( + this.clone && + this.mindMap.opt.enableFreeDrag && + this.beingDragNodeList.length === 1 + ) { + // 如果只拖拽了一个节点,那么设置自定义位置 let { x, y } = this.mindMap.toPos( e.clientX - this.offsetX, e.clientY - this.offsetY @@ -135,11 +150,16 @@ class Drag extends Base { let { scaleX, scaleY, translateX, translateY } = this.drawTransform x = (x - translateX) / scaleX y = (y - translateY) / scaleY - this.node.left = x - this.node.top = y - this.node.customLeft = x - this.node.customTop = y - this.mindMap.execCommand('SET_NODE_CUSTOM_POSITION', this.node, x, y) + this.mousedownNode.left = x + this.mousedownNode.top = y + this.mousedownNode.customLeft = x + this.mousedownNode.customTop = y + this.mindMap.execCommand( + 'SET_NODE_CUSTOM_POSITION', + this.mousedownNode, + x, + y + ) this.mindMap.render() } this.reset() @@ -150,24 +170,131 @@ class Drag extends Base { }) } + // 拖动中 + onMove(x, y, e) { + if (!this.isMousedown) { + return + } + // 更新克隆节点的位置 + let { scaleX, scaleY, translateX, translateY } = this.drawTransform + let cloneNodeLeft = x - this.offsetX + let cloneNodeTop = y - this.offsetY + x = (cloneNodeLeft - translateX) / scaleX + y = (cloneNodeTop - translateY) / scaleY + let t = this.clone.transform() + this.clone.translate(x - t.translateX, y - t.translateY) + // 检测新位置 + this.checkOverlapNode() + // 如果注册了多选节点插件,那么复用它的边缘自动移动画布功能 + if (this.mindMap.opt.autoMoveWhenMouseInEdgeOnDrag && this.mindMap.select) { + this.drawTransform = this.mindMap.draw.transform() + this.mindMap.select.clearAutoMoveTimer() + this.mindMap.select.onMove(e.clientX, e.clientY) + } + } + + // 开始拖拽时初始化一些数据 + handleStartMove() { + if (!this.isDragging) { + this.isDragging = true + // 鼠标按下的节点 + let node = this.mousedownNode + // 计算鼠标按下的位置距离节点左上角的距离 + this.drawTransform = this.mindMap.draw.transform() + let { scaleX, scaleY, translateX, translateY } = this.drawTransform + this.offsetX = this.mouseDownX - (node.left * scaleX + translateX) + this.offsetY = this.mouseDownY - (node.top * scaleY + translateY) + // 如果鼠标按下的节点是激活节点,那么保存当前所有激活的节点 + if (node.nodeData.data.isActive) { + // 找出这些激活节点中的最顶层节点 + this.beingDragNodeList = getTopAncestorsFomNodeList( + // 过滤掉根节点和概要节点 + this.mindMap.renderer.activeNodeList.filter(item => { + return !item.isRoot && !item.isGeneralization + }) + ) + } else { + // 否则只拖拽按下的节点 + this.beingDragNodeList = [node] + } + // 将节点树转为节点数组 + this.nodeTreeToList() + // 创建克隆节点 + this.createCloneNode() + // 清除当前所有激活的节点 + this.mindMap.renderer.clearAllActive() + } + } + + // 节点由树转换成数组,从子节点到根节点 + nodeTreeToList() { + const list = [] + bfsWalk(this.mindMap.renderer.root, node => { + // 过滤掉当前被拖拽的节点 + if (this.checkIsInBeingDragNodeList(node)) { + return + } + if (!list[node.layerIndex]) { + list[node.layerIndex] = [] + } + list[node.layerIndex].push(node) + }) + this.nodeList = list.reduceRight((res, cur) => { + return [...res, ...cur] + }, []) + } + // 创建克隆节点 createCloneNode() { if (!this.clone) { - // 节点 - this.clone = this.node.group.clone() - this.clone.opacity(0.5) + const { + dragMultiNodeRectConfig, + dragPlaceholderRectFill, + dragOpacityConfig + } = this.mindMap.opt + const { + width: rectWidth, + height: rectHeight, + fill: rectFill + } = dragMultiNodeRectConfig + const node = this.beingDragNodeList[0] + const lineColor = node.style.merge('lineColor', true) + // 如果当前被拖拽的节点数量大于1,那么创建一个矩形示意 + if (this.beingDragNodeList.length > 1) { + this.clone = this.draw + .rect() + .size(rectWidth, rectHeight) + .radius(rectHeight / 2) + .fill({ + color: rectFill || lineColor + }) + this.offsetX = rectWidth / 2 + this.offsetY = rectHeight / 2 + } else { + // 否则克隆当前的节点 + this.clone = node.group.clone() + // 删除展开收起按钮元素 + const expandEl = this.clone.findOne('.smm-expand-btn') + if (expandEl) { + expandEl.remove() + } + this.mindMap.draw.add(this.clone) + } + this.clone.opacity(dragOpacityConfig.cloneNodeOpacity) this.clone.css('z-index', 99999) - this.node.isDrag = true - this.node.hide() - // 连接线 - this.line = this.draw.path() - this.line.opacity(0.5) - this.node.styleLine(this.line, this.node) - // 同级位置占位符 + // 同级位置提示元素 this.placeholder = this.draw.rect().fill({ - color: this.node.style.merge('lineColor', true) + color: dragPlaceholderRectFill || lineColor + }) + // 当前被拖拽的节点的临时设置 + this.beingDragNodeList.forEach(node => { + // 降低透明度 + node.setOpacity(dragOpacityConfig.beingDragNodeOpacity) + // 隐藏连线及下级节点 + node.hideChildren() + // 设置拖拽状态 + node.startDrag() }) - this.mindMap.draw.add(this.clone) } } @@ -177,42 +304,9 @@ class Drag extends Base { return } this.clone.remove() - this.line.remove() this.placeholder.remove() } - // 拖动中 - onMove(x, y, e) { - if (!this.isMousedown) { - return - } - this.createCloneNode() - let { scaleX, scaleY, translateX, translateY } = this.drawTransform - this.cloneNodeLeft = x - this.offsetX - this.cloneNodeTop = y - this.offsetY - x = (this.cloneNodeLeft - translateX) / scaleX - y = (this.cloneNodeTop - translateY) / scaleY - let t = this.clone.transform() - this.clone.translate(x - t.translateX, y - t.translateY) - // 连接线 - let parent = this.node.parent - this.line.plot( - this.quadraticCurvePath( - parent.left + parent.width / 2, - parent.top + parent.height / 2, - x + this.node.width / 2, - y + this.node.height / 2 - ) - ) - this.checkOverlapNode() - // 如果注册了多选节点插件,那么复用它的边缘自动移动画布功能 - if (this.mindMap.opt.autoMoveWhenMouseInEdgeOnDrag && this.mindMap.select) { - this.drawTransform = this.mindMap.draw.transform() - this.mindMap.select.clearAutoMoveTimer() - this.mindMap.select.onMove(e.clientX, e.clientY) - } - } - // 检测重叠节点 checkOverlapNode() { if (!this.drawTransform || !this.placeholder) { @@ -226,9 +320,6 @@ class Drag extends Base { if (node.nodeData.data.isActive) { this.mindMap.renderer.setNodeActive(node, false) } - if (node.uid === this.node.uid || this.node.isParent(node)) { - return - } if (this.overlapNode || (this.prevNode && this.nextNode)) { return } @@ -484,7 +575,7 @@ class Drag extends Base { if (node.layerIndex === 1) { sameDir = item.dir === node.dir } - return item !== this.node && sameDir + return sameDir && !this.checkIsInBeingDragNodeList(item) }) : [] this.handleVerticalCheck(node, checkList) @@ -535,7 +626,7 @@ class Drag extends Base { handleFishbone(node) { let checkList = node.parent ? node.parent.children.filter(item => { - return item !== this.node && item.layerIndex > 1 + return item.layerIndex > 1 && !this.checkIsInBeingDragNodeList(item) }) : [] if (node.layerIndex === 1) { @@ -553,8 +644,8 @@ class Drag extends Base { // 获取节点的兄弟节点列表通用方法 commonGetNodeCheckList(node) { return node.parent - ? node.parent.children.filter(item => { - return item !== this.node + ? [...node.parent.children].filter(item => { + return !this.checkIsInBeingDragNodeList(item) }) : [] } @@ -585,18 +676,11 @@ class Drag extends Base { } } - // 节点由树转换成数组,从子节点到根节点 - nodeTreeToList() { - const list = [] - bfsWalk(this.mindMap.renderer.root, node => { - if (!list[node.layerIndex]) { - list[node.layerIndex] = [] - } - list[node.layerIndex].push(node) + // 检查某个节点是否在被拖拽节点内 + checkIsInBeingDragNodeList(node) { + return !!this.beingDragNodeList.find(item => { + return item.uid === node.uid || item.isParent(node) }) - this.nodeList = list.reduceRight((res, cur) => { - return [...res, ...cur] - }, []) } } From 73e78555752bc2dc119884b5a783f5fc417bc7d2 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Thu, 21 Sep 2023 09:51:46 +0800 Subject: [PATCH 04/13] =?UTF-8?q?Feat=EF=BC=9A1.=E5=8F=8C=E5=87=BB?= =?UTF-8?q?=E5=85=B3=E8=81=94=E7=BA=BF=E8=BF=9B=E5=85=A5=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=BA=BF=E6=96=87=E6=9C=AC=E7=BC=96=E8=BE=91=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=EF=BC=9B2.=E5=85=B3=E8=81=94=E7=BA=BF=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E4=B8=BA=E9=BB=98=E8=AE=A4=E6=96=87=E6=9C=AC=E7=9A=84=E8=AF=9D?= =?UTF-8?q?=E4=B8=8D=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/core/render/TextEdit.js | 27 +++---------------- .../src/plugins/AssociativeLine.js | 5 ++++ .../associativeLine/associativeLineText.js | 26 +++++++++++++----- simple-mind-map/src/utils/index.js | 19 +++++++++++++ 4 files changed, 48 insertions(+), 29 deletions(-) diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index fe5faf95..b7a56aa7 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 } from '../../utils' +import { getStrWithBrFromHtml, checkNodeOuter, focusInput, selectAllInput } from '../../utils' import { ERROR_TYPES } from '../../constants/constant' // 节点文字编辑类 @@ -188,35 +188,16 @@ export default class TextEdit { this.showTextEdit = true // 选中文本 // if (!this.cacheEditingText) { - // this.selectNodeText() + // selectAllInput(this.textEditNode) // } if (isInserting || (selectTextOnEnterEditText && !isFromKeyDown)) { - this.selectNodeText() + selectAllInput(this.textEditNode) } else { - this.focus() + focusInput(this.textEditNode) } this.cacheEditingText = '' } - // 聚焦 - focus() { - let selection = window.getSelection() - let range = document.createRange() - range.selectNodeContents(this.textEditNode) - range.collapse() - selection.removeAllRanges() - selection.addRange(range) - } - - // 选中文本 - selectNodeText() { - let selection = window.getSelection() - let range = document.createRange() - range.selectNodeContents(this.textEditNode) - selection.removeAllRanges() - selection.addRange(range) - } - // 获取当前正在编辑的内容 getEditText() { return getStrWithBrFromHtml(this.textEditNode.innerHTML) diff --git a/simple-mind-map/src/plugins/AssociativeLine.js b/simple-mind-map/src/plugins/AssociativeLine.js index 42550286..4dbc4ed2 100644 --- a/simple-mind-map/src/plugins/AssociativeLine.js +++ b/simple-mind-map/src/plugins/AssociativeLine.js @@ -234,6 +234,11 @@ class AssociativeLine { controlPoints }) }) + // 双击进入关联线文本编辑状态 + clickPath.dblclick(() => { + if (!this.activeLine) return + this.showEditTextBox(text) + }) // 渲染关联线文字 this.renderText(this.getText(node, toNode), path, text) this.lineList.push([path, clickPath, text, node, toNode]) diff --git a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js index b511d4ed..6d37a2c4 100644 --- a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js +++ b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js @@ -1,5 +1,9 @@ import { Text } from '@svgdotjs/svg.js' -import { getStrWithBrFromHtml } from '../../utils/index' +import { + getStrWithBrFromHtml, + focusInput, + selectAllInput +} from '../../utils/index' // 创建文字节点 function createText(data) { @@ -36,7 +40,7 @@ function showEditTextBox(g) { this.mindMap.keyCommand.addShortcut('Enter', () => { this.hideEditTextBox() }) - + // 输入框元素没有创建过,则先创建 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;` @@ -55,20 +59,27 @@ function showEditTextBox(g) { associativeLineTextFontFamily, associativeLineTextLineHeight } = this.mindMap.themeConfig + let { defaultAssociativeLineText, nodeTextEditZIndex } = this.mindMap.opt let scale = this.mindMap.view.scale let [, , , node, toNode] = this.activeLine - let textLines = ( - this.getText(node, toNode) || this.mindMap.opt.defaultAssociativeLineText - ).split(/\n/gim) + let text = this.getText(node, toNode) + let textLines = (text || defaultAssociativeLineText).split(/\n/gim) this.textEditNode.style.fontFamily = associativeLineTextFontFamily this.textEditNode.style.fontSize = associativeLineTextFontSize * scale + 'px' this.textEditNode.style.lineHeight = textLines.length > 1 ? associativeLineTextLineHeight : 'normal' - this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex + this.textEditNode.style.zIndex = nodeTextEditZIndex this.textEditNode.innerHTML = textLines.join('
') this.textEditNode.style.display = 'block' this.updateTextEditBoxPos(g) this.showTextEdit = true + // 如果是默认文本要全选输入框 + if (text === '' || text === defaultAssociativeLineText) { + selectAllInput(this.textEditNode) + } else { + // 否则聚焦即可 + focusInput(this.textEditNode) + } } // 处理画布缩放 @@ -94,6 +105,9 @@ function hideEditTextBox() { } let [path, , text, node, toNode] = this.activeLine let str = getStrWithBrFromHtml(this.textEditNode.innerHTML) + // 如果是默认文本,那么不保存 + let isDefaultText = str === this.mindMap.opt.defaultAssociativeLineText + str = isDefaultText ? '' : str this.mindMap.execCommand('SET_NODE_DATA', node, { associativeLineText: { ...(node.nodeData.data.associativeLineText || {}), diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index bc13eba0..5d8c109c 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -698,3 +698,22 @@ export const checkTwoRectIsOverlap = ( ) => { return maxx1 > minx2 && maxx2 > minx1 && maxy1 > miny2 && maxy2 > miny1 } + +// 聚焦指定输入框 +export const focusInput = el => { + let selection = window.getSelection() + let range = document.createRange() + range.selectNodeContents(el) + range.collapse() + selection.removeAllRanges() + selection.addRange(range) +} + +// 聚焦全选指定输入框 +export const selectAllInput = el => { + let selection = window.getSelection() + let range = document.createRange() + range.selectNodeContents(el) + selection.removeAllRanges() + selection.addRange(range) +} From c0ad18cff8bc5eb1e7bd5e4c4a5c8afba5472f8c Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Thu, 21 Sep 2023 09:53:35 +0800 Subject: [PATCH 05/13] =?UTF-8?q?Feat=EF=BC=9A=E5=AD=98=E5=9C=A8=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E8=8A=82=E7=82=B9=E6=97=B6=E7=82=B9=E5=87=BB=E5=85=B3?= =?UTF-8?q?=E8=81=94=E7=BA=BF=E5=8F=AF=E7=9B=B4=E6=8E=A5=E6=BF=80=E6=B4=BB?= =?UTF-8?q?=E5=85=B3=E8=81=94=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/plugins/AssociativeLine.js | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/simple-mind-map/src/plugins/AssociativeLine.js b/simple-mind-map/src/plugins/AssociativeLine.js index 4dbc4ed2..0225d2ca 100644 --- a/simple-mind-map/src/plugins/AssociativeLine.js +++ b/simple-mind-map/src/plugins/AssociativeLine.js @@ -257,28 +257,25 @@ class AssociativeLine { }) { let { associativeLineActiveColor } = this.mindMap.themeConfig // 如果当前存在激活节点,那么取消激活节点 - if (this.mindMap.renderer.activeNodeList.length > 0) { - this.clearActiveNodes() - } else { - // 否则清除当前的关联线的激活状态,如果有的话 - this.clearActiveLine() - // 保存当前激活的关联线信息 - this.activeLine = [path, clickPath, text, node, toNode] - // 让不可见的点击线显示 - clickPath.stroke({ color: associativeLineActiveColor }) - // 如果没有输入过关联线文字,那么显示默认文字 - if (!this.getText(node, toNode)) { - this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text) - } - // 渲染控制点和连线 - this.renderControls( - startPoint, - endPoint, - controlPoints[0], - controlPoints[1] - ) - this.mindMap.emit('associative_line_click', path, clickPath, node, toNode) + this.mindMap.execCommand('CLEAR_ACTIVE_NODE') + // 否则清除当前的关联线的激活状态,如果有的话 + this.clearActiveLine() + // 保存当前激活的关联线信息 + this.activeLine = [path, clickPath, text, node, toNode] + // 让不可见的点击线显示 + clickPath.stroke({ color: associativeLineActiveColor }) + // 如果没有输入过关联线文字,那么显示默认文字 + if (!this.getText(node, toNode)) { + this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text) } + // 渲染控制点和连线 + this.renderControls( + startPoint, + endPoint, + controlPoints[0], + controlPoints[1] + ) + this.mindMap.emit('associative_line_click', path, clickPath, node, toNode) } // 移除所有连接线 @@ -488,13 +485,6 @@ class AssociativeLine { }) } - // 清除当前激活的节点 - clearActiveNodes() { - if (this.mindMap.renderer.activeNodeList.length > 0) { - this.mindMap.execCommand('CLEAR_ACTIVE_NODE') - } - } - // 清除激活的线 clearActiveLine() { if (this.activeLine) { From fcf48ca3dcc25fdc20351344e86d989870550f59 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Thu, 21 Sep 2023 15:10:17 +0800 Subject: [PATCH 06/13] =?UTF-8?q?Fix=EF=BC=9A=E5=A2=9E=E5=8A=A0=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E8=BE=B9=E7=95=8C=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/plugins/Export.js | 2 +- simple-mind-map/src/plugins/RichText.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/simple-mind-map/src/plugins/Export.js b/simple-mind-map/src/plugins/Export.js index a90aa7c0..47bd579e 100644 --- a/simple-mind-map/src/plugins/Export.js +++ b/simple-mind-map/src/plugins/Export.js @@ -41,7 +41,7 @@ class Export { let task = imageList.map(async item => { let imgUlr = item.attr('href') || item.attr('xlink:href') // 已经是data:URL形式不用转换 - if (/^data:/.test(imgUlr)) { + if (/^data:/.test(imgUlr) || imgUlr === 'none') { return } let imgData = await imgToDataUrl(imgUlr) diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index 4117d144..4e3c5131 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -616,7 +616,7 @@ class RichText { // 处理导入数据 handleSetData(data) { let walk = root => { - if (!root.data.richText) { + if (root.data && !root.data.richText) { root.data.richText = true root.data.resetRichText = true } From a97d549d69a4cc07dc8bf28a06779ad60da5b7ee Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Thu, 21 Sep 2023 15:58:22 +0800 Subject: [PATCH 07/13] =?UTF-8?q?Feat=EF=BC=9A=E4=BC=98=E5=8C=96=E5=AD=90?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E7=9A=84=E6=8F=92=E5=85=A5=EF=BC=9A1.?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E5=AF=B9=E5=A4=9A=E4=B8=AA=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=8F=92=E5=85=A5=E5=AD=90=E8=8A=82=E7=82=B9=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E8=BF=9B=E5=85=A5=E7=BC=96=E8=BE=91=E7=8A=B6=E6=80=81?= =?UTF-8?q?=EF=BC=9B2.=E6=96=B0=E6=8F=92=E5=85=A5=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E8=87=AA=E5=8A=A8=E8=BF=9B=E5=85=A5=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/core/render/Render.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index ac9b85bc..87802f4b 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -510,6 +510,7 @@ class Render { defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList + let handleMultiNodes = list.length > 1 list.forEach(node => { if (node.isGeneralization) { return @@ -522,12 +523,13 @@ class Render { : defaultInsertBelowSecondLevelNodeText let isRichText = !!this.mindMap.richText node.nodeData.children.push({ - inserting: openEdit, + inserting: handleMultiNodes ? false : openEdit,// 如果同时对多个节点插入子节点,那么无需进入编辑模式 data: { text: text, expand: true, richText: isRichText, resetRichText: isRichText, + isActive: handleMultiNodes || !openEdit,// 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 ...(appointData || {}) }, children: [...appointChildren] @@ -538,6 +540,10 @@ class Render { node.destroy() } }) + // 如果同时对多个节点插入子节点,需要清除原来激活的节点 + if (handleMultiNodes || !openEdit) { + this.clearActive() + } this.mindMap.render() } From 7a2605fdad7eafc642e719cc88008f2bb5068630 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Thu, 21 Sep 2023 16:27:26 +0800 Subject: [PATCH 08/13] =?UTF-8?q?Feat=EF=BC=9A=E6=94=AF=E6=8C=81=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E5=AF=B9=E5=A4=9A=E4=B8=AA=E8=8A=82=E7=82=B9=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E5=85=84=E5=BC=9F=E8=8A=82=E7=82=B9=EF=BC=9B=E5=AF=B9?= =?UTF-8?q?=E6=A0=B9=E8=8A=82=E7=82=B9=E8=B0=83=E7=94=A8=E6=8F=92=E5=85=A5?= =?UTF-8?q?=E5=85=84=E5=BC=9F=E8=8A=82=E7=82=B9=E7=9A=84=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=86=8D=E5=88=9B=E5=BB=BA=E5=AD=90=E8=8A=82?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/core/render/Render.js | 51 ++++++++++++++--------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 87802f4b..3c8ad5e5 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -462,35 +462,48 @@ class Render { defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList - let first = list[0] - if (first.isGeneralization) { - return - } - if (first.isRoot) { - this.insertChildNode(openEdit, appointNodes, appointData) - } else { - let text = - first.layerIndex === 1 - ? defaultInsertSecondLevelNodeText - : defaultInsertBelowSecondLevelNodeText - if (first.layerIndex === 1) { - first.parent.destroy() + let handleMultiNodes = list.length > 1 + let needDestroyNodeList = {} + list.forEach(node => { + if (node.isGeneralization || node.isRoot) { + return } - let index = this.getNodeIndex(first) + const parent = node.parent + const isOneLayer = node.layerIndex === 1 + // 插入二级节点时根节点需要重新渲染 + if (isOneLayer && !needDestroyNodeList[parent.uid]) { + needDestroyNodeList[parent.uid] = parent + } + // 新插入节点的默认文本 + let text = isOneLayer + ? defaultInsertSecondLevelNodeText + : defaultInsertBelowSecondLevelNodeText + // 计算插入位置 + let index = parent.nodeData.children.findIndex(item => { + return item.data.uid === node.uid + }) let isRichText = !!this.mindMap.richText - first.parent.nodeData.children.splice(index + 1, 0, { - inserting: openEdit, + parent.nodeData.children.splice(index + 1, 0, { + inserting: handleMultiNodes ? false : openEdit, // 如果同时对多个节点插入子节点,那么无需进入编辑模式, data: { text: text, expand: true, richText: isRichText, resetRichText: isRichText, + isActive: handleMultiNodes || !openEdit, // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 ...(appointData || {}) }, children: [...appointChildren] }) - this.mindMap.render() + }) + Object.keys(needDestroyNodeList).forEach(key => { + needDestroyNodeList[key].destroy() + }) + // 如果同时对多个节点插入子节点,需要清除原来激活的节点 + if (handleMultiNodes || !openEdit) { + this.clearActive() } + this.mindMap.render() } // 插入子节点 @@ -523,13 +536,13 @@ class Render { : defaultInsertBelowSecondLevelNodeText let isRichText = !!this.mindMap.richText node.nodeData.children.push({ - inserting: handleMultiNodes ? false : openEdit,// 如果同时对多个节点插入子节点,那么无需进入编辑模式 + inserting: handleMultiNodes ? false : openEdit, // 如果同时对多个节点插入子节点,那么无需进入编辑模式 data: { text: text, expand: true, richText: isRichText, resetRichText: isRichText, - isActive: handleMultiNodes || !openEdit,// 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 + isActive: handleMultiNodes || !openEdit, // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 ...(appointData || {}) }, children: [...appointChildren] From 443465eb8675f6a9676073f14a888e227bd96291 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Fri, 22 Sep 2023 08:57:00 +0800 Subject: [PATCH 09/13] =?UTF-8?q?Feat=EF=BC=9A=E5=B0=86=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=94=AF=E4=B8=80=E6=A0=87=E8=AF=86=E7=94=B1id=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E6=94=B9=E4=B8=BAuid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/core/render/node/Node.js | 2 +- .../src/plugins/AssociativeLine.js | 22 +++++++++---------- .../associativeLine/associativeLineText.js | 4 ++-- .../associativeLine/associativeLineUtils.js | 2 +- simple-mind-map/src/utils/index.js | 5 ++--- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index 952812c0..d287755b 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -14,7 +14,7 @@ class Node { constructor(opt = {}) { // 节点数据 this.nodeData = this.handleData(opt.data || {}) - // id + // uid this.uid = opt.uid // 控制实例 this.mindMap = opt.mindMap diff --git a/simple-mind-map/src/plugins/AssociativeLine.js b/simple-mind-map/src/plugins/AssociativeLine.js index 0225d2ca..51fc51ee 100644 --- a/simple-mind-map/src/plugins/AssociativeLine.js +++ b/simple-mind-map/src/plugins/AssociativeLine.js @@ -149,8 +149,8 @@ class AssociativeLine { ) { nodeToIds.set(cur, data.associativeLineTargets) } - if (data.id) { - idToNode.set(data.id, cur) + if (data.uid) { + idToNode.set(data.uid, cur) } }, () => {}, @@ -158,8 +158,8 @@ class AssociativeLine { 0 ) nodeToIds.forEach((ids, node) => { - ids.forEach((id, index) => { - let toNode = idToNode.get(id) + ids.forEach((uid, index) => { + let toNode = idToNode.get(uid) if (!node || !toNode) return const associativeLinePoint = (node.nodeData.data.associativeLinePoint || [])[index] @@ -397,21 +397,21 @@ class AssociativeLine { addLine(fromNode, toNode) { if (!fromNode || !toNode) return // 目标节点如果没有id,则生成一个id - let id = toNode.nodeData.data.id - if (!id) { - id = uuid() + let uid = toNode.nodeData.data.uid + if (!uid) { + uid = uuid() this.mindMap.execCommand('SET_NODE_DATA', toNode, { - id + uid }) } // 将目标节点id保存起来 let list = fromNode.nodeData.data.associativeLineTargets || [] // 连线节点是否存在相同的id,存在则阻止添加关联线 - const sameLine = list.some(item => item === id) + const sameLine = list.some(item => item === uid) if (sameLine) { return } - list.push(id) + list.push(uid) // 保存控制点 let [startPoint, endPoint] = computeNodePoints(fromNode, toNode) let controlPoints = computeCubicBezierPathPoints( @@ -460,7 +460,7 @@ class AssociativeLine { let newAssociativeLineText = {} if (associativeLineText) { Object.keys(associativeLineText).forEach(item => { - if (item !== toNode.nodeData.data.id) { + if (item !== toNode.nodeData.data.uid) { newAssociativeLineText[item] = associativeLineText[item] } }) diff --git a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js index 6d37a2c4..201077f0 100644 --- a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js +++ b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js @@ -111,7 +111,7 @@ function hideEditTextBox() { this.mindMap.execCommand('SET_NODE_DATA', node, { associativeLineText: { ...(node.nodeData.data.associativeLineText || {}), - [toNode.nodeData.data.id]: str + [toNode.nodeData.data.uid]: str } }) this.textEditNode.style.display = 'none' @@ -127,7 +127,7 @@ function getText(node, toNode) { if (!obj) { return '' } - return obj[toNode.nodeData.data.id] || '' + return obj[toNode.nodeData.data.uid] || '' } // 渲染关联线文字 diff --git a/simple-mind-map/src/plugins/associativeLine/associativeLineUtils.js b/simple-mind-map/src/plugins/associativeLine/associativeLineUtils.js index b68216e5..ca46b6f8 100644 --- a/simple-mind-map/src/plugins/associativeLine/associativeLineUtils.js +++ b/simple-mind-map/src/plugins/associativeLine/associativeLineUtils.js @@ -1,7 +1,7 @@ // 获取目标节点在起始节点的目标数组中的索引 export const getAssociativeLineTargetIndex = (node, toNode) => { return node.nodeData.data.associativeLineTargets.findIndex(item => { - return item === toNode.nodeData.data.id + return item === toNode.nodeData.data.uid }) } diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index 5d8c109c..c854eaaf 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -170,9 +170,8 @@ export const copyNodeTree = ( keepId = false ) => { tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data) - // 去除节点id,因为节点id不能重复 - if (tree.data.id && !keepId) delete tree.data.id - if (tree.data.uid) delete tree.data.uid + // 去除节点uid,因为节点uid不能重复 + if (tree.data.uid && !keepId) delete tree.data.uid if (removeActiveState) { tree.data.isActive = false } From a9ea4b8e338c5be6b1509c87de0d2241be3d47bf Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Fri, 22 Sep 2023 10:00:44 +0800 Subject: [PATCH 10/13] =?UTF-8?q?Feat=EF=BC=9A1.=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E6=8F=92=E5=85=A5=E5=A4=9A=E4=B8=AA=E5=90=8C?= =?UTF-8?q?=E7=BA=A7=E8=8A=82=E7=82=B9=E3=80=81=E5=A4=9A=E4=B8=AA=E5=AD=90?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E7=9A=84=E5=91=BD=E4=BB=A4=EF=BC=9B2.?= =?UTF-8?q?=E5=A4=8D=E5=88=B6=E3=80=81=E5=89=AA=E5=88=87=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=90=8C=E6=97=B6=E6=93=8D=E4=BD=9C=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E8=8A=82=E7=82=B9=EF=BC=9B3.=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=AF=B9=E6=8F=92=E5=85=A5=E5=90=8C=E7=BA=A7=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E3=80=81=E5=AD=90=E8=8A=82=E7=82=B9=E7=9A=84=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E6=8F=92=E5=85=A5=E7=9A=84=E7=AC=AC=E5=9B=9B=E4=B8=AA=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E6=95=B0=E6=8D=AE=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/core/render/Render.js | 235 ++++++++++++++++------ simple-mind-map/src/utils/index.js | 51 +++++ web/src/pages/Edit/components/Edit.vue | 187 +++++++++++++---- 3 files changed, 363 insertions(+), 110 deletions(-) diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 3c8ad5e5..1f1c7a6d 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -13,7 +13,12 @@ import { walk, bfsWalk, loadImage, - isUndef + isUndef, + getTopAncestorsFomNodeList, + addDataToAppointNodes, + createUidForAppointNodes, + formatDataToArray, + getNodeIndex } from '../../utils' import { shapeList } from './node/Shape' import { lineStyleProps } from '../../themes/default' @@ -40,7 +45,6 @@ const layouts = { } // 渲染 - class Render { // 构造函数 constructor(opt = {}) { @@ -132,9 +136,18 @@ class Render { // 插入同级节点 this.insertNode = this.insertNode.bind(this) this.mindMap.command.add('INSERT_NODE', this.insertNode) + // 插入多个同级节点 + this.insertMultiNode = this.insertMultiNode.bind(this) + this.mindMap.command.add('INSERT_MULTI_NODE', this.insertMultiNode) // 插入子节点 this.insertChildNode = this.insertChildNode.bind(this) this.mindMap.command.add('INSERT_CHILD_NODE', this.insertChildNode) + // 插入多个子节点 + this.insertMultiChildNode = this.insertMultiChildNode.bind(this) + this.mindMap.command.add( + 'INSERT_MULTI_CHILD_NODE', + this.insertMultiChildNode + ) // 上移节点 this.upNode = this.upNode.bind(this) this.mindMap.command.add('UP_NODE', this.upNode) @@ -386,15 +399,6 @@ class Render { }) } - // 获取节点在同级里的索引位置 - getNodeIndex(node) { - return node.parent - ? node.parent.children.findIndex(item => { - return item.uid === node.uid - }) - : 0 - } - // 全选 selectAll() { walk( @@ -439,31 +443,36 @@ class Render { } } - // 规范指定节点数据 - formatAppointNodes(appointNodes) { - if (!appointNodes) return [] - return Array.isArray(appointNodes) ? appointNodes : [appointNodes] - } - - // 插入同级节点,多个节点只会操作第一个节点 + // 插入同级节点 insertNode( openEdit = true, appointNodes = [], appointData = null, appointChildren = [] ) { - appointNodes = this.formatAppointNodes(appointNodes) + appointNodes = formatDataToArray(appointNodes) if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return } this.textEdit.hideEditTextBox() - let { + const { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt - let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList - let handleMultiNodes = list.length > 1 - let needDestroyNodeList = {} + const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList + const handleMultiNodes = list.length > 1 + const isRichText = !!this.mindMap.richText + const params = { + expand: true, + richText: isRichText, + resetRichText: isRichText, + isActive: handleMultiNodes || !openEdit // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 + } + // 动态指定的子节点数据也需要添加相关属性 + appointChildren = addDataToAppointNodes(appointChildren, { + ...params + }) + const needDestroyNodeList = {} list.forEach(node => { if (node.isGeneralization || node.isRoot) { return @@ -475,22 +484,18 @@ class Render { needDestroyNodeList[parent.uid] = parent } // 新插入节点的默认文本 - let text = isOneLayer + const text = isOneLayer ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText // 计算插入位置 - let index = parent.nodeData.children.findIndex(item => { + const index = parent.nodeData.children.findIndex(item => { return item.data.uid === node.uid }) - let isRichText = !!this.mindMap.richText parent.nodeData.children.splice(index + 1, 0, { inserting: handleMultiNodes ? false : openEdit, // 如果同时对多个节点插入子节点,那么无需进入编辑模式, data: { text: text, - expand: true, - richText: isRichText, - resetRichText: isRichText, - isActive: handleMultiNodes || !openEdit, // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 + ...params, ...(appointData || {}) }, children: [...appointChildren] @@ -506,6 +511,51 @@ class Render { this.mindMap.render() } + // 插入多个同级节点 + insertMultiNode(appointNodes, nodeList) { + if (!nodeList || nodeList.length <= 0) return + appointNodes = formatDataToArray(appointNodes) + if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { + return + } + this.textEdit.hideEditTextBox() + const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList + const isRichText = !!this.mindMap.richText + const params = { + expand: true, + richText: isRichText, + resetRichText: isRichText, + isActive: true + } + nodeList = addDataToAppointNodes(nodeList, params) + const needDestroyNodeList = {} + list.forEach(node => { + if (node.isGeneralization || node.isRoot) { + return + } + const parent = node.parent + const isOneLayer = node.layerIndex === 1 + // 插入二级节点时根节点需要重新渲染 + if (isOneLayer && !needDestroyNodeList[parent.uid]) { + needDestroyNodeList[parent.uid] = parent + } + // 计算插入位置 + const index = parent.nodeData.children.findIndex(item => { + return item.data.uid === node.uid + }) + parent.nodeData.children.splice( + index + 1, + 0, + ...createUidForAppointNodes(simpleDeepClone(nodeList)) + ) + }) + Object.keys(needDestroyNodeList).forEach(key => { + needDestroyNodeList[key].destroy() + }) + this.clearActive() + this.mindMap.render() + } + // 插入子节点 insertChildNode( openEdit = true, @@ -513,17 +563,28 @@ class Render { appointData = null, appointChildren = [] ) { - appointNodes = this.formatAppointNodes(appointNodes) + appointNodes = formatDataToArray(appointNodes) if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return } this.textEdit.hideEditTextBox() - let { + const { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt - let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList - let handleMultiNodes = list.length > 1 + const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList + const handleMultiNodes = list.length > 1 + const isRichText = !!this.mindMap.richText + const params = { + expand: true, + richText: isRichText, + resetRichText: isRichText, + isActive: handleMultiNodes || !openEdit // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 + } + // 动态指定的子节点数据也需要添加相关属性 + appointChildren = addDataToAppointNodes(appointChildren, { + ...params + }) list.forEach(node => { if (node.isGeneralization) { return @@ -531,18 +592,14 @@ class Render { if (!node.nodeData.children) { node.nodeData.children = [] } - let text = node.isRoot + const text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText - let isRichText = !!this.mindMap.richText node.nodeData.children.push({ inserting: handleMultiNodes ? false : openEdit, // 如果同时对多个节点插入子节点,那么无需进入编辑模式 data: { text: text, - expand: true, - richText: isRichText, - resetRichText: isRichText, - isActive: handleMultiNodes || !openEdit, // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 + ...params, ...(appointData || {}) }, children: [...appointChildren] @@ -560,6 +617,41 @@ class Render { this.mindMap.render() } + // 插入多个子节点 + insertMultiChildNode(appointNodes, childList) { + if (!childList || childList.length <= 0) return + appointNodes = formatDataToArray(appointNodes) + if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { + return + } + this.textEdit.hideEditTextBox() + const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList + const isRichText = !!this.mindMap.richText + const params = { + expand: true, + richText: isRichText, + resetRichText: isRichText, + isActive: true + } + childList = addDataToAppointNodes(childList, params) + list.forEach(node => { + if (node.isGeneralization) { + return + } + if (!node.nodeData.children) { + node.nodeData.children = [] + } + node.nodeData.children.push(...childList) + // 插入子节点时自动展开子节点 + node.nodeData.data.expand = true + if (node.isRoot) { + node.destroy() + } + }) + this.clearActive() + this.mindMap.render() + } + // 上移节点,多个节点只会操作第一个节点 upNode() { if (this.activeNodeList.length <= 0) { @@ -617,19 +709,19 @@ class Render { // 复制节点 copy() { this.beingCopyData = this.copyNode() - this.setCoptyDataToClipboard(this.beingCopyData) + this.setCopyDataToClipboard(this.beingCopyData) } // 剪切节点 cut() { this.mindMap.execCommand('CUT_NODE', copyData => { this.beingCopyData = copyData - this.setCoptyDataToClipboard(copyData) + this.setCopyDataToClipboard(copyData) }) } // 将粘贴或剪切的数据设置到用户剪切板中 - setCoptyDataToClipboard(data) { + setCopyDataToClipboard(data) { if (navigator.clipboard) { navigator.clipboard.writeText( JSON.stringify({ @@ -720,13 +812,9 @@ class Render { } if (smmData) { this.mindMap.execCommand( - 'INSERT_CHILD_NODE', - false, + 'INSERT_MULTI_CHILD_NODE', [], - { - ...smmData.data - }, - [...smmData.children] + Array.isArray(smmData) ? smmData : [smmData] ) } else { this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], { @@ -770,7 +858,7 @@ class Render { // 将节点移动到另一个节点的前面或后面 insertTo(node, exist, dir = 'before') { - let nodeList = this.formatAppointNodes(node) + let nodeList = formatDataToArray(node) nodeList = nodeList.filter(item => { return !item.isRoot }) @@ -823,7 +911,7 @@ class Render { // 移除节点 removeNode(appointNodes = []) { - appointNodes = this.formatAppointNodes(appointNodes) + appointNodes = formatDataToArray(appointNodes) if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return } @@ -895,32 +983,40 @@ class Render { // 移除某个指定节点 removeOneNode(node) { - let index = this.getNodeIndex(node) + let index = getNodeIndex(node) node.remove() node.parent.children.splice(index, 1) node.parent.nodeData.children.splice(index, 1) } - // 复制节点,多个节点只会操作第一个节点 + // 复制节点 copyNode() { if (this.activeNodeList.length <= 0) { return } - return copyNodeTree({}, this.activeNodeList[0], true) + const nodeList = getTopAncestorsFomNodeList(this.activeNodeList) + return nodeList.map(node => { + return copyNodeTree({}, node, true) + }) } - // 剪切节点,多个节点只会操作第一个节点 + // 剪切节点 cutNode(callback) { if (this.activeNodeList.length <= 0) { return } - let node = this.activeNodeList[0] - if (node.isRoot) { - return null - } - let copyData = copyNodeTree({}, node, true) - this.removeActiveNode(node) - this.removeOneNode(node) + const nodeList = getTopAncestorsFomNodeList(this.activeNodeList).filter( + node => { + return !node.isRoot + } + ) + const copyData = nodeList.map(node => { + return copyNodeTree({}, node, true) + }) + nodeList.forEach(node => { + this.removeActiveNode(node) + this.removeOneNode(node) + }) this.mindMap.emit('node_active', null, [...this.activeNodeList]) this.mindMap.render() if (callback && typeof callback === 'function') { @@ -928,9 +1024,9 @@ class Render { } } - // 移动一个节点作为另一个节点的子节点 + // 移动节点作为另一个节点的子节点 moveNodeTo(node, toNode) { - let nodeList = this.formatAppointNodes(node) + let nodeList = formatDataToArray(node) nodeList = nodeList.filter(item => { return !item.isRoot }) @@ -949,11 +1045,16 @@ class Render { // 粘贴节点到节点 pasteNode(data) { - if (this.activeNodeList.length <= 0 || !data) { + data = formatDataToArray(data) + if (this.activeNodeList.length <= 0 || data.length <= 0) { return } - this.activeNodeList.forEach(item => { - item.nodeData.children.push(simpleDeepClone(data)) + this.activeNodeList.forEach(node => { + node.nodeData.children.push( + ...data.map(item => { + return simpleDeepClone(item) + }) + ) }) this.mindMap.render() } diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index c854eaaf..cd52b936 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -716,3 +716,54 @@ export const selectAllInput = el => { selection.removeAllRanges() selection.addRange(range) } + +// 给指定的节点列表树数据添加附加数据,会修改原数据 +export const addDataToAppointNodes = (appointNodes, data = {}) => { + const walk = list => { + list.forEach(node => { + node.data = { + ...node.data, + ...data + } + if (node.children && node.children.length > 0) { + walk(node.children) + } + }) + } + walk(appointNodes) + return appointNodes +} + +// 给指定的节点列表树数据添加uid,如果不存在的话,会修改原数据 +export const createUidForAppointNodes = appointNodes => { + const walk = list => { + list.forEach(node => { + if (!node.data) { + node.data = {} + } + if (isUndef(node.data.uid)) { + node.data.uid = createUid() + } + if (node.children && node.children.length > 0) { + walk(node.children) + } + }) + } + walk(appointNodes) + return appointNodes +} + +// 传入一个数据,如果该数据是数组,那么返回该数组,否则返回一个以该数据为成员的数组 +export const formatDataToArray = data => { + if (!data) return [] + return Array.isArray(data) ? data : [data] +} + +// 获取节点在同级里的位置索引 +export const getNodeIndex = node => { + return node.parent + ? node.parent.children.findIndex(item => { + return item.uid === node.uid + }) + : 0 +} diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue index d0044080..c8315c24 100644 --- a/web/src/pages/Edit/components/Edit.vue +++ b/web/src/pages/Edit/components/Edit.vue @@ -12,7 +12,10 @@ - + @@ -264,10 +267,10 @@ export default { // 如果url中存在要打开的文件,那么思维导图数据、主题、布局都使用默认的 if (hasFileURL) { root = { - "data": { - "text": "根节点" + data: { + text: '根节点' }, - "children": [] + children: [] } layout = exampleData.layout theme = exampleData.theme @@ -296,7 +299,7 @@ export default { useLeftKeySelectionRightKeyDrag: this.useLeftKeySelectionRightKeyDrag, customInnerElsAppendTo: null, enableAutoEnterTextEditWhenKeydown: true, - customHandleClipboardText: handleClipboardText, + customHandleClipboardText: handleClipboardText // isUseCustomNodeContent: true, // 示例1:组件里用到了router、store、i18n等实例化vue组件时需要用到的东西 // customCreateNodeContent: (node) => { @@ -344,46 +347,33 @@ export default { this.mindMap.keyCommand.addShortcut('Control+s', () => { this.manualSave() }) - // 转发事件 - ;[ - 'node_active', - 'data_change', - 'view_data_change', - 'back_forward', - 'node_contextmenu', - 'node_click', - 'draw_click', - 'expand_btn_click', - 'svg_mousedown', - 'mouseup', - 'mode_change', - 'node_tree_render_end', - 'rich_text_selection_change', - 'transforming-dom-to-images', - 'generalization_node_contextmenu', - 'painter_start', - 'painter_end', - 'scrollbar_change' - ].forEach(event => { - this.mindMap.on(event, (...args) => { - this.$bus.$emit(event, ...args) - }) + // 转发事件 + ;[ + 'node_active', + 'data_change', + 'view_data_change', + 'back_forward', + 'node_contextmenu', + 'node_click', + 'draw_click', + 'expand_btn_click', + 'svg_mousedown', + 'mouseup', + 'mode_change', + 'node_tree_render_end', + 'rich_text_selection_change', + 'transforming-dom-to-images', + 'generalization_node_contextmenu', + 'painter_start', + 'painter_end', + 'scrollbar_change' + ].forEach(event => { + this.mindMap.on(event, (...args) => { + this.$bus.$emit(event, ...args) }) + }) this.bindSaveEvent() - // setTimeout(() => { - // 动态给指定节点添加子节点 - // this.mindMap.execCommand('INSERT_CHILD_NODE', false, this.mindMap.renderer.root, { - // text: '自定义内容' - // }) - - // 动态给指定节点添加同级节点 - // this.mindMap.execCommand('INSERT_NODE', false, this.mindMap.renderer.root, { - // text: '自定义内容' - // }) - - // 动态删除指定节点 - // this.mindMap.execCommand('REMOVE_NODE', this.mindMap.renderer.root.children[0]) - // }, 5000); + this.testDynamicCreateNodes() // 如果应用被接管,那么抛出事件传递思维导图实例 if (window.takeOverApp) { this.$bus.$emit('app_inited', this.mindMap) @@ -477,6 +467,117 @@ export default { // 移除节点富文本编辑插件 removeRichTextPlugin() { this.mindMap.removePlugin(RichText) + }, + + // 测试动态插入节点 + testDynamicCreateNodes() { + return + setTimeout(() => { + // 动态给指定节点添加子节点 + // this.mindMap.execCommand( + // 'INSERT_CHILD_NODE', + // false, + // this.mindMap.renderer.root, + // { + // text: '自定义内容' + // }, + // [ + // { + // data: { + // text: '自定义子节点' + // } + // } + // ] + // ) + + // 动态给指定节点添加同级节点 + // this.mindMap.execCommand( + // 'INSERT_NODE', + // false, + // null, + // { + // text: '自定义内容' + // }, + // [ + // { + // data: { + // text: '自定义同级节点' + // }, + // children: [ + // { + // data: { + // text: '自定义同级节点2' + // }, + // children: [] + // } + // ] + // } + // ] + // ) + + // 动态插入多个子节点 + // this.mindMap.execCommand('INSERT_MULTI_CHILD_NODE', null, [ + // { + // data: { + // text: '自定义节点1' + // }, + // children: [ + // { + // data: { + // text: '自定义节点1-1' + // }, + // children: [] + // } + // ] + // }, + // { + // data: { + // text: '自定义节点2' + // }, + // children: [ + // { + // data: { + // text: '自定义节点2-1' + // }, + // children: [] + // } + // ] + // } + // ]) + + // 动态插入多个同级节点 + // this.mindMap.execCommand('INSERT_MULTI_NODE', null, [ + // { + // data: { + // text: '自定义节点1' + // }, + // children: [ + // { + // data: { + // text: '自定义节点1-1' + // }, + // children: [] + // } + // ] + // }, + // { + // data: { + // text: '自定义节点2' + // }, + // children: [ + // { + // data: { + // text: '自定义节点2-1' + // }, + // children: [] + // } + // ] + // } + // ]) + + // 动态删除指定节点 + // this.mindMap.execCommand('REMOVE_NODE', this.mindMap.renderer.root.children[0]) + }, 5000) } } } From 518b7642a013dacf2cafbb984359eaba94919b6e Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Fri, 22 Sep 2023 10:57:32 +0800 Subject: [PATCH 11/13] =?UTF-8?q?Demo=EF=BC=9A=E4=BF=AE=E5=A4=8D=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E7=9F=A5=E7=8A=80=E6=80=9D=E7=BB=B4=E5=AF=BC=E5=9B=BE?= =?UTF-8?q?=E5=A4=9A=E4=B8=AA=E8=8A=82=E7=82=B9=E6=97=B6=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E7=B2=98=E8=B4=B4=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/utils/handleClipboardText.js | 112 ++++++++++++++++----------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/web/src/utils/handleClipboardText.js b/web/src/utils/handleClipboardText.js index 4a9f5623..35671c72 100644 --- a/web/src/utils/handleClipboardText.js +++ b/web/src/utils/handleClipboardText.js @@ -1,57 +1,69 @@ import { imgToDataUrl } from 'simple-mind-map/src/utils/index' // 处理知犀 -const handleZHIXI = async text => { - text = text.replace('￿', '') +const handleZHIXI = async data => { try { - // 只处理一项 - const node = JSON.parse(text)[0] - const newNode = {} - const waitLoadImageList = [] - const walk = async (root, newRoot) => { - newRoot.data = { - text: root.data.text, - hyperlink: root.data.hyperlink, - hyperlinkTitle: root.data.hyperlinkTitle, - note: root.data.note - } - // 图片 - if (root.data.image) { - let resolve = null - let promise = new Promise(_resolve => { - resolve = _resolve - }) - waitLoadImageList.push(promise) - try { - newRoot.data.image = await imgToDataUrl(root.data.image) - newRoot.data.imageSize = root.data.imageSize - resolve() - } catch (error) { - resolve() - } - } - // 子节点 - newRoot.children = [] - if (root.children && root.children.length > 0) { - root.children.forEach(item => { - // 概要 - if (item.data.type === 'generalize') { - newRoot.data.generalization = { - text: item.data.text - } - return - } - let newChild = {} - newRoot.children.push(newChild) - walk(item, newChild) - }) + try { + if (!Array.isArray(data)) { + data = String(data).replace('￿', '') + data = JSON.parse(data) } + } catch (error) { + console.log(error) } - walk(node, newNode) + if (!Array.isArray(data)) { + data = [] + } + const newNodeList = [] + const waitLoadImageList = [] + const walk = (list, newList) => { + list.forEach(async item => { + let newRoot = {} + newList.push(newRoot) + newRoot.data = { + text: item.data.text, + hyperlink: item.data.hyperlink, + hyperlinkTitle: item.data.hyperlinkTitle, + note: item.data.note + } + // 图片 + if (item.data.image) { + let resolve = null + let promise = new Promise(_resolve => { + resolve = _resolve + }) + waitLoadImageList.push(promise) + try { + newRoot.data.image = await imgToDataUrl(item.data.image) + newRoot.data.imageSize = item.data.imageSize + resolve() + } catch (error) { + resolve() + } + } + // 子节点 + newRoot.children = [] + if (item.children && item.children.length > 0) { + const children = [] + item.children.forEach(item2 => { + // 概要 + if (item2.data.type === 'generalize') { + newRoot.data.generalization = { + text: item2.data.text + } + } else { + children.push(item2) + } + }) + walk(children, newRoot.children) + } + }) + } + walk(data, newNodeList) await Promise.all(waitLoadImageList) return { simpleMindMap: true, - data: newNode + data: newNodeList } } catch (error) { return '' @@ -59,7 +71,15 @@ const handleZHIXI = async text => { } const handleClipboardText = async text => { - // 处理知犀数据 + // 知犀数据格式1 + try { + let parsedData = JSON.parse(text) + if (parsedData.__c_zx_v !== undefined) { + const res = await handleZHIXI(parsedData.children) + return res + } + } catch (error) {} + // 知犀数据格式2 if (text.includes('￿')) { const res = await handleZHIXI(text) return res From 11ea7d452ccd464b7800b362b420e795619df428 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Fri, 22 Sep 2023 16:11:33 +0800 Subject: [PATCH 12/13] =?UTF-8?q?Feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E6=95=B0?= =?UTF-8?q?=E5=AD=A6=E5=85=AC=E5=BC=8F=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/full.js | 2 + simple-mind-map/package-lock.json | 300 +++++++++++++++++++--- simple-mind-map/package.json | 1 + simple-mind-map/src/core/render/Render.js | 12 + simple-mind-map/src/plugins/Formula.js | 53 ++++ 5 files changed, 337 insertions(+), 31 deletions(-) create mode 100644 simple-mind-map/src/plugins/Formula.js diff --git a/simple-mind-map/full.js b/simple-mind-map/full.js index 6aecb594..68f47c64 100644 --- a/simple-mind-map/full.js +++ b/simple-mind-map/full.js @@ -14,6 +14,7 @@ import TouchEvent from './src/plugins/TouchEvent.js' import Search from './src/plugins/Search.js' import Painter from './src/plugins/Painter.js' import Scrollbar from './src/plugins/Scrollbar.js' +import Formula from './src/plugins/Formula.js' import xmind from './src/parse/xmind.js' import markdown from './src/parse/markdown.js' import icons from './src/svg/icons.js' @@ -43,5 +44,6 @@ MindMap.usePlugin(MiniMap) .usePlugin(Search) .usePlugin(Painter) .usePlugin(Scrollbar) + .usePlugin(Formula) export default MindMap diff --git a/simple-mind-map/package-lock.json b/simple-mind-map/package-lock.json index d05ccda1..5089e5f4 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.15-fix.2", + "version": "0.7.1-fix.2", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.6.15-fix.2", + "version": "0.7.1-fix.2", "license": "MIT", "dependencies": { "@svgdotjs/svg.js": "^3.0.16", @@ -13,8 +13,10 @@ "eventemitter3": "^4.0.7", "jspdf": "^2.5.1", "jszip": "^3.10.1", + "katex": "^0.16.8", "mdast-util-from-markdown": "^1.3.0", "quill": "^1.3.6", + "tern": "^0.24.3", "uuid": "^9.0.0", "xml-js": "^1.6.11" }, @@ -187,6 +189,36 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-loose": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-6.1.0.tgz", + "integrity": "sha512-FHhXoiF0Uch3IqsrnPpWwCtiv5PYvipTpT1k9lDMgQVVYc9iDuSl5zdJV358aI8twfHCYMFBRVYvAVki9wC/ng==", + "dependencies": { + "acorn": "^6.2.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-loose/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -247,8 +279,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-arraybuffer": { "version": "1.0.2", @@ -263,7 +294,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -371,11 +401,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/core-js": { "version": "3.27.1", @@ -523,6 +560,31 @@ "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==", "optional": true }, + "node_modules/enhanced-resolve": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-2.3.0.tgz", + "integrity": "sha512-n6e4bsCpzsP0OB76X+vEWhySUQI8GHPVFVK+3QkX35tbryy2WoeGeK5kQ+oxzgDVHjIZyz5fyS60Mi3EpQLc0Q==", + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.3.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.3" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -796,8 +858,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/function-bind": { "version": "1.1.1", @@ -829,7 +890,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -872,6 +932,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "node_modules/grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", @@ -990,7 +1055,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1148,6 +1212,21 @@ "setimmediate": "^1.0.5" } }, + "node_modules/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -1233,6 +1312,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/memory-fs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz", + "integrity": "sha512-QTNXnl79X97kZ9jJk/meJrtDuvgvRakX5LU7HZW1L7MsXHuSTwoMIzN9tOLLH3Xfsj/gbsSqX/ovnsqz246zKQ==", + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, "node_modules/micromark": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", @@ -1659,7 +1747,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1686,6 +1773,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -1713,7 +1808,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1800,7 +1894,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1849,6 +1942,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -2140,6 +2238,50 @@ "node": ">=12.0.0" } }, + "node_modules/tapable": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", + "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tern": { + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/tern/-/tern-0.24.3.tgz", + "integrity": "sha512-Z8uvtdWIlFn1GWy0HW5FhZ8VDryZwoJUdnjZU25C7/PBOltLIn1uv+WF3rVq6S1761YbsmbZYRP/l0ZJBCkvrw==", + "dependencies": { + "acorn": "^6.0.0", + "acorn-loose": "^6.0.0", + "acorn-walk": "^6.0.0", + "enhanced-resolve": "^2.2.2", + "glob": "^7.1.1", + "minimatch": "^3.0.3", + "resolve-from": "2.0.0" + }, + "bin": { + "tern": "bin/tern" + } + }, + "node_modules/tern/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/tern/node_modules/resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -2266,8 +2408,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/xml-js": { "version": "1.6.11", @@ -2418,6 +2559,26 @@ "dev": true, "requires": {} }, + "acorn-loose": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-6.1.0.tgz", + "integrity": "sha512-FHhXoiF0Uch3IqsrnPpWwCtiv5PYvipTpT1k9lDMgQVVYc9iDuSl5zdJV358aI8twfHCYMFBRVYvAVki9wC/ng==", + "requires": { + "acorn": "^6.2.0" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + } + } + }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2459,8 +2620,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-arraybuffer": { "version": "1.0.2", @@ -2472,7 +2632,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2549,11 +2708,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "core-js": { "version": "3.27.1", @@ -2660,6 +2823,25 @@ "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==", "optional": true }, + "enhanced-resolve": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-2.3.0.tgz", + "integrity": "sha512-n6e4bsCpzsP0OB76X+vEWhySUQI8GHPVFVK+3QkX35tbryy2WoeGeK5kQ+oxzgDVHjIZyz5fyS60Mi3EpQLc0Q==", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.3.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.3" + } + }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "requires": { + "prr": "~1.0.1" + } + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2872,8 +3054,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "function-bind": { "version": "1.1.1", @@ -2899,7 +3080,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2927,6 +3107,11 @@ "type-fest": "^0.20.2" } }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", @@ -3009,7 +3194,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3131,6 +3315,14 @@ "setimmediate": "^1.0.5" } }, + "katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==", + "requires": { + "commander": "^8.3.0" + } + }, "kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -3196,6 +3388,15 @@ "@types/mdast": "^3.0.0" } }, + "memory-fs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz", + "integrity": "sha512-QTNXnl79X97kZ9jJk/meJrtDuvgvRakX5LU7HZW1L7MsXHuSTwoMIzN9tOLLH3Xfsj/gbsSqX/ovnsqz246zKQ==", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, "micromark": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", @@ -3412,7 +3613,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3433,6 +3633,11 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -3451,7 +3656,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -3516,8 +3720,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -3548,6 +3751,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -3752,6 +3960,37 @@ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", "optional": true }, + "tapable": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", + "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==" + }, + "tern": { + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/tern/-/tern-0.24.3.tgz", + "integrity": "sha512-Z8uvtdWIlFn1GWy0HW5FhZ8VDryZwoJUdnjZU25C7/PBOltLIn1uv+WF3rVq6S1761YbsmbZYRP/l0ZJBCkvrw==", + "requires": { + "acorn": "^6.0.0", + "acorn-loose": "^6.0.0", + "acorn-walk": "^6.0.0", + "enhanced-resolve": "^2.2.2", + "glob": "^7.1.1", + "minimatch": "^3.0.3", + "resolve-from": "2.0.0" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==" + } + } + }, "text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -3847,8 +4086,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "xml-js": { "version": "1.6.11", diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index 898108ba..7854a579 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -31,6 +31,7 @@ "eventemitter3": "^4.0.7", "jspdf": "^2.5.1", "jszip": "^3.10.1", + "katex": "^0.16.8", "mdast-util-from-markdown": "^1.3.0", "quill": "^1.3.6", "tern": "^0.24.3", diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 1f1c7a6d..8ebbc7a7 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -215,6 +215,9 @@ class Render { // 设置节点标签 this.setNodeTag = this.setNodeTag.bind(this) this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag) + // 设置节点公式 + this.insertFormula = this.insertFormula.bind(this) + this.mindMap.command.add('INSERT_FORMULA', this.insertFormula) // 添加节点概要 this.addGeneralization = this.addGeneralization.bind(this) this.mindMap.command.add('ADD_GENERALIZATION', this.addGeneralization) @@ -1278,6 +1281,15 @@ class Render { }) } + // 设置节点公式 + insertFormula(formula) { + // 只在富文本模式下可用,并且需要注册Formula插件 + if (!this.mindMap.richText || !this.mindMap.formula) return + this.activeNodeList.forEach(node => { + this.mindMap.formula.insertFormulaToNode(node, formula) + }) + } + // 添加节点概要 addGeneralization(data) { if (this.activeNodeList.length <= 0) { diff --git a/simple-mind-map/src/plugins/Formula.js b/simple-mind-map/src/plugins/Formula.js new file mode 100644 index 00000000..52a174f5 --- /dev/null +++ b/simple-mind-map/src/plugins/Formula.js @@ -0,0 +1,53 @@ +import katex from 'katex' +import Quill from 'quill' + +// 数学公式支持插件 +// 该插件在富文本模式下可用 +class Formula { + // 构造函数 + constructor(opt) { + this.opt = opt + this.mindMap = opt.mindMap + window.katex = katex + this.extendQuill() + } + + // 修改formula格式工具 + extendQuill() { + const QuillFormula = Quill.import('formats/formula') + + class CustomFormulaBlot extends QuillFormula { + static create(value) { + let node = super.create(value) + if (typeof value === 'string') { + katex.render(value, node, { + throwOnError: false, + errorColor: '#f00', + output: 'mathml' // 增加该配置,默认只输出公式 + }) + node.setAttribute('data-value', value) + } + return node + } + } + + Quill.register('formats/formula', CustomFormulaBlot, true) + } + + // 给指定的节点插入指定公式 + insertFormulaToNode(node, formula) { + let richTextPlugin = this.mindMap.richText + richTextPlugin.showEditText(node) + richTextPlugin.quill.insertEmbed( + richTextPlugin.quill.getLength() - 1, + 'formula', + formula + ) + richTextPlugin.setTextStyleIfNotRichText(richTextPlugin.node) + richTextPlugin.hideEditText([node]) + } +} + +Formula.instanceName = 'formula' + +export default Formula From aafcba8bb7120e48700408ebeb01281b9060ce6b Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Fri, 22 Sep 2023 16:12:40 +0800 Subject: [PATCH 13/13] =?UTF-8?q?Demo=EF=BC=9A=E6=94=AF=E6=8C=81=E6=95=B0?= =?UTF-8?q?=E5=AD=A6=E5=85=AC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/assets/icon-font/iconfont.css | 10 +- web/src/assets/icon-font/iconfont.ttf | Bin 27128 -> 27356 bytes web/src/assets/icon-font/iconfont.woff | Bin 17104 -> 17268 bytes web/src/assets/icon-font/iconfont.woff2 | Bin 14612 -> 14776 bytes web/src/config/constant.js | 129 ++++++----- web/src/lang/en_us.js | 29 ++- web/src/lang/zh_cn.js | 19 +- web/src/pages/Edit/components/Edit.vue | 11 +- .../pages/Edit/components/FormulaSidebar.vue | 201 ++++++++++++++++++ .../pages/Edit/components/NodeIconSidebar.vue | 12 +- web/src/pages/Edit/components/Toolbar.vue | 15 ++ 11 files changed, 353 insertions(+), 73 deletions(-) create mode 100644 web/src/pages/Edit/components/FormulaSidebar.vue diff --git a/web/src/assets/icon-font/iconfont.css b/web/src/assets/icon-font/iconfont.css index dc17607d..4eae4d8c 100644 --- a/web/src/assets/icon-font/iconfont.css +++ b/web/src/assets/icon-font/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 2479351 */ - src: url('iconfont.woff2?t=1691822758372') format('woff2'), - url('iconfont.woff?t=1691822758372') format('woff'), - url('iconfont.ttf?t=1691822758372') format('truetype'); + src: url('iconfont.woff2?t=1695365666344') format('woff2'), + url('iconfont.woff?t=1695365666344') format('woff'), + url('iconfont.ttf?t=1695365666344') format('truetype'); } .iconfont { @@ -13,6 +13,10 @@ -moz-osx-font-smoothing: grayscale; } +.icongongshi:before { + content: "\e617"; +} + .icontouming:before { content: "\e60c"; } diff --git a/web/src/assets/icon-font/iconfont.ttf b/web/src/assets/icon-font/iconfont.ttf index 028f19aed55b87eb8da6251e8d5430d227eef0c6..2cbfa9ba6c8a3f2b48bcb2b721dc5d59267e7262 100644 GIT binary patch delta 2134 zcmb7^>u*$56vfw>nLCfpwA0Q^r|pz>9v%9ir9eR*Qp!W{!9xKbAjm+Ww3U=nZ9}Ry znMSl}2r+0)6a8RN5UA}@Z{`MUhhd*t)p5>YsC{tQ7$Wvy6)eQW zCqXG1Jz=j<={GQdqdbqzoNwV>ypNA@3a4>q<{W`O&tmL1)RH6`G zG{MCU*{}}J;Aw0`J^X2G;7*^zqnL$aJdek@-)G@O6x$f)W;}(3SdKhAgmU=s0ybd@ z<|B>-DzFASuo{c71+(!4)?*h65Mz8h8D$WSSb-20paxq}h3yE#fl@Re7t1gY`6z=2 z5tIaw#2hR}2_8ccYEg%|SjogXhi+K+hq@e|GuN8<;VOC#?s*+{Br+)G&<@{g5!B5N=VUWg&S6 zCK=*4Fx`-R0}~G^Ft7xWfPuw;1P##dg$+n=;G)Q&MI~hh77bEvVEG_10}Ba>8(30E z!ocD}bfko3hUg3sEl{a3u=J3*28sYO4@m2QdVnl2P#KU012qC^G*B&&6$a`CqBBOQ zAP7sUp_U-44OA6mje+`t=u#0X4Wdg+sJS#mr-@L0kWB^(5u)o%C`pL^AfY%RdLx7~ zg={fUu#l|=N*AI_LMUR0j+#);5S>**VMBDF+gN{U9J13uwL|nq2z3wHXP^ThEe3i4 z(ruU#?5kW8`UCQg0Wr=)26_fEY@mA}CkLz=$DBzL8ugE9GufnLVsgn_Qc)N%v; zjVY#9L#Jb^%RtX#>I(zik0}PAVIROvmO;agfSXLa1_93JS$_`f{J^dE4D1)U#Sd%P zIgsH7HS8hCFiRSC6J)4<4f_f*M-1#R$h>J_uR&(Oz^;Sa{C|z^KRotgST(5c)3&9y z1GZo7E%wunqYkkSSZAFn=QplP?o*zQ?E>90e5lu!1qt{}?@l+y|Xs?juvE;Y2L$i-p&Z!)$+F12b zb!+uVO?Ax&wXxd4+Us>JjBZQW3HwQVoOY_yozIFV>eb>UL3_PwQVZh^;gGW;9t?+E z_L=pZDm9d79rLQ)aJ3!kZ0l}+arBqgQo9ZK7x#|W AKmY&$ delta 1902 zcmb7^*>6;37{-5RmhPqPOgFj?bXvNwizrf|s3_KgfC>mSwqa>wSenqp$^~Pz5;a^X zL<5Ep6SyM85Skz%2uLE~f(xQi6E#t9-kFq%QR9L*0iUm5`~#em-<>R$h6#~?fa`QHxhdUfZ@YgHGZ_zrCRXn4oq_E{Zw`+y(C;$ht={3ZI2 z)|W(bc=zboD>v_FAjpc*U3<3;X5P-Z-*>z3>>eB&2~xoleO9aM#Gb+3JIYUXpM`Bl zAv83ycVFpfU;6!vuv4Fh|9bCI%kq8#t;NzCw})OT>;8k{h;C>Lx$j#4JKz89ek!jy zd{EZ~gxp=d-0%92GzH&G{2d$!=Io!kL1>il)M~cQMS9A9BB53K>Hs}NNmz-+t$N~A zxFtxv&Ny%BcxUQ3#9@wdiqo9ozcX9jow2e=O!vC$P8& z_aD^`SAVY-jUM$0Yx1aLSi+;;VKY4HA~w^beqwDNbrx&)sK;1`N8QHU!-e{e&GX0s zSZaaZgv@{~^vD*NlID;>FsF==RWNnVA=6;XJ+cqB!XqPLt|da2!dwf5%!N6{glvZO zc~Wv5w%Q}_VeTP9F2vl!h5U%E^^nkcokt$UoZLcg#ayLAzQvr9LJr1Uae^M5H+tl1 zY>Vr^HXwsz&Uivr$F_N7dThH#_Q&>lrcAnDYoSPBhdl}hcEqEYU>|rC80@4+(ZN3T z5L5r{vPXHsu6UFz?5al@!#?*YZP;~>a)G)*FvL&>>C~p6|!%7G+xLa^k~rV ztNz>JM$Xi05x0Up!PlXJ&}-pPIMo+vkBmnPq8p;Qyn+0e3wjGF++29BsJiHK@#&K7 zCC5vTmgc6NC@U)eusm0>sbaL^OhqPE5L**F9FN7<$G?c@DwkD`Ri3K+xhht5yt=OX zdQGxsq~?5WdF`(0b<@Y|lJ!OPz4eFc?=_4!+-&%>G1+*&sjcZm)3xTd=CRJiqC{@S zxtVz_O>(<1rl8((m|B<^pF9u9p I8LAKe3-Al9$N&HU diff --git a/web/src/assets/icon-font/iconfont.woff b/web/src/assets/icon-font/iconfont.woff index 3a255b1f7d3d81fa7406f0f2aa9661d69b9ba89b..3b4512b73f20f1664aa80e958a6067306a00790e 100644 GIT binary patch delta 16620 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 delta 16459 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 + @@ -43,6 +44,7 @@ import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js' import SearchPlugin from 'simple-mind-map/src/plugins/Search.js' import Painter from 'simple-mind-map/src/plugins/Painter.js' import ScrollbarPlugin from 'simple-mind-map/src/plugins/Scrollbar.js' +import Formula from 'simple-mind-map/src/plugins/Formula.js' import OutlineSidebar from './OutlineSidebar' import Style from './Style' import BaseStyle from './BaseStyle' @@ -75,6 +77,7 @@ import { showLoading, hideLoading } from '@/utils/loading' import handleClipboardText from '@/utils/handleClipboardText' import Scrollbar from './Scrollbar.vue' import exampleData from 'simple-mind-map/example/exampleData' +import FormulaSidebar from './FormulaSidebar.vue' // 注册插件 MindMap.usePlugin(MiniMap) @@ -91,6 +94,7 @@ MindMap.usePlugin(MiniMap) .usePlugin(SearchPlugin) .usePlugin(Painter) .usePlugin(ScrollbarPlugin) + .usePlugin(Formula) // 注册自定义主题 customThemeList.forEach(item => { @@ -123,7 +127,8 @@ export default { NodeIconSidebar, NodeIconToolbar, OutlineEdit, - Scrollbar + Scrollbar, + FormulaSidebar }, data() { return { @@ -489,7 +494,6 @@ export default { // } // ] // ) - // 动态给指定节点添加同级节点 // this.mindMap.execCommand( // 'INSERT_NODE', @@ -514,7 +518,6 @@ export default { // } // ] // ) - // 动态插入多个子节点 // this.mindMap.execCommand('INSERT_MULTI_CHILD_NODE', null, [ // { @@ -544,7 +547,6 @@ export default { // ] // } // ]) - // 动态插入多个同级节点 // this.mindMap.execCommand('INSERT_MULTI_NODE', null, [ // { @@ -574,7 +576,6 @@ export default { // ] // } // ]) - // 动态删除指定节点 // this.mindMap.execCommand('REMOVE_NODE', this.mindMap.renderer.root.children[0]) }, 5000) diff --git a/web/src/pages/Edit/components/FormulaSidebar.vue b/web/src/pages/Edit/components/FormulaSidebar.vue new file mode 100644 index 00000000..114025ef --- /dev/null +++ b/web/src/pages/Edit/components/FormulaSidebar.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/web/src/pages/Edit/components/NodeIconSidebar.vue b/web/src/pages/Edit/components/NodeIconSidebar.vue index 168eb970..d3ea3afb 100644 --- a/web/src/pages/Edit/components/NodeIconSidebar.vue +++ b/web/src/pages/Edit/components/NodeIconSidebar.vue @@ -1,9 +1,15 @@