diff --git a/README.md b/README.md index 2e4752ea..0e52b3a1 100644 --- a/README.md +++ b/README.md @@ -149,4 +149,8 @@ const mindMap = new MindMap({ 千帆 + + + 才镇 +

\ No newline at end of file diff --git a/simple-mind-map/full.js b/simple-mind-map/full.js index 2c78b8be..986cec13 100644 --- a/simple-mind-map/full.js +++ b/simple-mind-map/full.js @@ -2,6 +2,7 @@ import MindMap from './index' import MiniMap from './src/plugins/MiniMap.js' import Watermark from './src/plugins/Watermark.js' import KeyboardNavigation from './src/plugins/KeyboardNavigation.js' +import ExortXMind from './src/plugins/ExortXMind.js' import ExportPDF from './src/plugins/ExportPDF.js' import Export from './src/plugins/Export.js' import Drag from './src/plugins/Drag.js' @@ -11,6 +12,7 @@ import RichText from './src/plugins/RichText' import NodeImgAdjust from './src/plugins/NodeImgAdjust.js' import TouchEvent from './src/plugins/TouchEvent.js' import Search from './src/plugins/Search.js' +import Painter from './src/plugins/Painter.js' import xmind from './src/parse/xmind.js' import markdown from './src/parse/markdown.js' import icons from './src/svg/icons.js' @@ -30,6 +32,7 @@ MindMap .usePlugin(Watermark) .usePlugin(Drag) .usePlugin(KeyboardNavigation) + .usePlugin(ExortXMind) .usePlugin(ExportPDF) .usePlugin(Export) .usePlugin(Select) @@ -38,5 +41,6 @@ MindMap .usePlugin(TouchEvent) .usePlugin(NodeImgAdjust) .usePlugin(Search) + .usePlugin(Painter) export default MindMap \ No newline at end of file diff --git a/simple-mind-map/package-lock.json b/simple-mind-map/package-lock.json index 29f719ea..160c304d 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.7", + "version": "0.6.11-fix.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.6.7", + "version": "0.6.11-fix.1", "license": "MIT", "dependencies": { "@svgdotjs/svg.js": "^3.0.16", diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index 9490ff9b..3b991fe4 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -323,7 +323,9 @@ export const nodeDataNoStylePropList = [ 'richText', 'resetRichText', 'uid', - 'activeStyle' + 'activeStyle', + 'associativeLineTargets', + 'associativeLineTargetControlOffsets' ] // 数据缓存 diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js index a89f448d..2c72933e 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -122,5 +122,9 @@ export const defaultOpt = { // 是否开启自定义节点内容 isUseCustomNodeContent: false, // 自定义返回节点内容的方法 - customCreateNodeContent: null + customCreateNodeContent: null, + // 指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下 + customInnerElsAppendTo: null, + // 拖拽元素时,指示元素新位置的块的最大高度 + nodeDragPlaceholderMaxSize: 20 } diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index bc2c4439..f407ec80 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -153,9 +153,12 @@ class Render { // 剪切节点 this.cutNode = this.cutNode.bind(this) this.mindMap.command.add('CUT_NODE', this.cutNode) - // 修改节点样式 + // 修改节点单个样式 this.setNodeStyle = this.setNodeStyle.bind(this) this.mindMap.command.add('SET_NODE_STYLE', this.setNodeStyle) + // 修改节点多个样式 + this.setNodeStyles = this.setNodeStyles.bind(this) + this.mindMap.command.add('SET_NODE_STYLES', this.setNodeStyles) // 切换节点是否激活 this.setNodeActive = this.setNodeActive.bind(this) this.mindMap.command.add('SET_NODE_ACTIVE', this.setNodeActive) @@ -859,6 +862,42 @@ class Render { } } + // 设置节点多个样式 + setNodeStyles(node, style, isActive) { + let data = {} + if (isActive) { + data = { + activeStyle: { + ...(node.nodeData.data.activeStyle || {}), + ...style + } + } + } else { + data = style + } + // 如果开启了富文本,则需要应用到富文本上 + if (this.mindMap.richText) { + let config = this.mindMap.richText.normalStyleToRichTextStyle(style) + if (Object.keys(config).length > 0) { + this.mindMap.richText.showEditText(node) + this.mindMap.richText.formatAllText(config) + this.mindMap.richText.hideEditText([node]) + } + } + this.setNodeDataRender(node, data) + // 更新了连线的样式 + let props = Object.keys(style) + let hasLineStyleProps = false + props.forEach((key) => { + if (lineStyleProps.includes(key)) { + hasLineStyleProps = true + } + }) + if (hasLineStyleProps) { + ;(node.parent || node).renderLine(true) + } + } + // 设置节点是否激活 setNodeActive(node, active) { this.setNodeData(node, { @@ -1132,6 +1171,8 @@ class Render { node.generalizationBelongNode.updateGeneralization() } if (!notRender) this.mindMap.render() + } else { + this.mindMap.emit('node_tree_render_end') } } diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index e426ecf6..7fbf7698 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -12,6 +12,8 @@ export default class TextEdit { this.textEditNode = null // 隐藏的文本输入框 this.hiddenInputEl = null + // 节点激活时默认聚焦到隐藏输入框 + this.enableFocus = true // 文本编辑框是否显示 this.showTextEdit = false // 如果编辑过程中缩放画布了,那么缓存当前编辑的内容 @@ -97,7 +99,17 @@ export default class TextEdit { // 让隐藏的文本输入框聚焦 focusHiddenInput() { - if (this.hiddenInputEl) this.hiddenInputEl.focus() + if (this.hiddenInputEl && this.enableFocus) this.hiddenInputEl.focus() + } + + // 关闭默认聚焦 + stopFocusOnNodeActive() { + this.enableFocus = false + } + + // 开启默认聚焦 + openFocusOnNodeActive() { + this.enableFocus = true } // 注册临时快捷键 @@ -167,7 +179,11 @@ export default class TextEdit { this.textEditNode.addEventListener('click', e => { e.stopPropagation() }) - document.body.appendChild(this.textEditNode) + this.textEditNode.addEventListener('mousedown', (e) => { + e.stopPropagation() + }) + const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body + targetNode.appendChild(this.textEditNode) } let scale = this.mindMap.view.scale let lineHeight = node.style.merge('lineHeight') diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index 4a5a6ac2..9c0c8910 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -385,10 +385,10 @@ class Node { this.active(e) }) this.group.on('mousedown', e => { - if (this.isRoot && e.which === 3) { + if (this.isRoot && e.which === 3 && !this.mindMap.opt.readonly) { e.stopPropagation() } - if (!this.isRoot) { + if (!this.isRoot && !this.mindMap.opt.readonly) { e.stopPropagation() } // 多选和取消多选 @@ -414,7 +414,7 @@ class Node { this.mindMap.emit('node_mousedown', this, e) }) this.group.on('mouseup', e => { - if (!this.isRoot) { + if (!this.isRoot && !this.mindMap.opt.readonly) { e.stopPropagation() } this.mindMap.emit('node_mouseup', this, e) diff --git a/simple-mind-map/src/core/render/node/nodeCommandWraps.js b/simple-mind-map/src/core/render/node/nodeCommandWraps.js index 3ef043e9..8745fa7f 100644 --- a/simple-mind-map/src/core/render/node/nodeCommandWraps.js +++ b/simple-mind-map/src/core/render/node/nodeCommandWraps.js @@ -43,6 +43,11 @@ function setStyle(prop, value, isActive) { this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive) } +// 修改多个样式 +function setStyles(style, isActive) { + this.mindMap.execCommand('SET_NODE_STYLES', this, style, isActive) +} + export default { setData, setText, @@ -52,5 +57,6 @@ export default { setNote, setTag, setShape, - setStyle + setStyle, + setStyles } diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js index 505bab59..2d5268bf 100644 --- a/simple-mind-map/src/core/render/node/nodeCreateContents.js +++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js @@ -268,7 +268,7 @@ function createNoteNode() { if (!this.noteEl) { this.noteEl = document.createElement('div') this.noteEl.style.cssText = ` - position: absolute; + position: fixed; padding: 10px; border-radius: 5px; box-shadow: 0 2px 5px rgb(0 0 0 / 10%); @@ -276,7 +276,8 @@ function createNoteNode() { background-color: #fff; z-index: ${ this.mindMap.opt.nodeNoteTooltipZIndex } ` - document.body.appendChild(this.noteEl) + const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body + targetNode.appendChild(this.noteEl) } this.noteEl.innerText = this.nodeData.data.note } diff --git a/simple-mind-map/src/layouts/LogicalStructure.js b/simple-mind-map/src/layouts/LogicalStructure.js index 518bac05..01ac0325 100644 --- a/simple-mind-map/src/layouts/LogicalStructure.js +++ b/simple-mind-map/src/layouts/LogicalStructure.js @@ -234,7 +234,7 @@ class LogicalStructure extends Base { let nodeUseLineStylePath = nodeUseLineStyle ? ` L ${item.left + item.width},${y2}` : '' - if (node.isRoot) { + if (node.isRoot && !this.mindMap.themeConfig.rootLineKeepSameInCurve) { path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath } else { path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath diff --git a/simple-mind-map/src/layouts/MindMap.js b/simple-mind-map/src/layouts/MindMap.js index bdab6c67..6d9f7bf5 100644 --- a/simple-mind-map/src/layouts/MindMap.js +++ b/simple-mind-map/src/layouts/MindMap.js @@ -315,7 +315,7 @@ class MindMap extends Base { nodeUseLineStylePath = ` L ${item.left + item.width},${y2}` } } - if (node.isRoot) { + if (node.isRoot && !this.mindMap.themeConfig.rootLineKeepSameInCurve) { path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath } else { path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath diff --git a/simple-mind-map/src/plugins/Drag.js b/simple-mind-map/src/plugins/Drag.js index 78a4b8aa..28a8b354 100644 --- a/simple-mind-map/src/plugins/Drag.js +++ b/simple-mind-map/src/plugins/Drag.js @@ -44,6 +44,7 @@ class Drag extends Base { this.mouseMoveY = 0 // 鼠标移动的距离距鼠标按下的位置距离多少以上才认为是拖动事件 this.checkDragOffset = 10 + this.minOffset = 10 } // 绑定事件 @@ -105,6 +106,9 @@ class Drag extends Base { this.node.isDrag = false this.node.show() this.removeCloneNode() + let overlapNodeUid = this.overlapNode ? this.overlapNode.nodeData.data.uid : '' + let prevNodeUid = this.prevNode ? this.prevNode.nodeData.data.uid : '' + let nextNodeUid = this.nextNode ? this.nextNode.nodeData.data.uid : '' // 存在重叠子节点,则移动作为其子节点 if (this.overlapNode) { this.mindMap.renderer.setNodeActive(this.overlapNode, false) @@ -134,7 +138,11 @@ class Drag extends Base { this.mindMap.render() } this.reset() - this.mindMap.emit('node_dragend') + this.mindMap.emit('node_dragend', { + overlapNodeUid, + prevNodeUid, + nextNodeUid + }) } // 创建克隆节点 @@ -199,6 +207,7 @@ class Drag extends Base { if (!this.drawTransform) { return } + const { nodeDragPlaceholderMaxSize } = this.mindMap.opt let x = this.mouseMoveX let y = this.mouseMoveY this.overlapNode = null @@ -240,19 +249,19 @@ class Drag extends Base { let prevNodeRect = this.getNodeRect(prevBrother) prevBrotherOffset = nodeRect.top - prevNodeRect.bottom // 间距小于10就当它不存在 - prevBrotherOffset = prevBrotherOffset >= 10 ? prevBrotherOffset / 2 : 0 + prevBrotherOffset = prevBrotherOffset >= this.minOffset ? prevBrotherOffset / 2 : 0 } else { // 没有前一个兄弟节点,那么假设和前一个节点的距离为20 - prevBrotherOffset = 10 + prevBrotherOffset = this.minOffset } // 和后一个兄弟节点的距离 let nextBrotherOffset = 0 if (nextBrother) { let nextNodeRect = this.getNodeRect(nextBrother) nextBrotherOffset = nextNodeRect.top - nodeRect.bottom - nextBrotherOffset = nextBrotherOffset >= 10 ? nextBrotherOffset / 2 : 0 + nextBrotherOffset = nextBrotherOffset >= this.minOffset ? nextBrotherOffset / 2 : 0 } else { - nextBrotherOffset = 10 + nextBrotherOffset = this.minOffset } if (nodeRect.left <= x && nodeRect.right >= x) { // 检测兄弟节点位置 @@ -265,11 +274,11 @@ class Drag extends Base { y >= nodeRect.top && y <= nodeRect.top + oneFourthHeight if (checkIsPrevNode) { this.prevNode = node - let size = nextBrotherOffset > 0 ? nextBrotherOffset : 5 + let size = nextBrotherOffset > 0 ? Math.min(nextBrotherOffset, nodeDragPlaceholderMaxSize) : 5 this.placeholder.size(node.width, size).move(nodeRect.originLeft, nodeRect.originBottom) } else if (checkIsNextNode) { this.nextNode = node - let size = prevBrotherOffset > 0 ? prevBrotherOffset : 5 + let size = prevBrotherOffset > 0 ? Math.min(prevBrotherOffset, nodeDragPlaceholderMaxSize) : 5 this.placeholder.size(node.width, size).move(nodeRect.originLeft, nodeRect.originTop - size) } } diff --git a/simple-mind-map/src/plugins/MiniMap.js b/simple-mind-map/src/plugins/MiniMap.js index a4033584..0127c2dd 100644 --- a/simple-mind-map/src/plugins/MiniMap.js +++ b/simple-mind-map/src/plugins/MiniMap.js @@ -25,6 +25,11 @@ class MiniMap { let { svg, rect, origWidth, origHeight, scaleX, scaleY } = this.mindMap.getSvgData() // 计算数据 + const elRect = this.mindMap.elRect + rect.x -= elRect.left + rect.x2 -= elRect.left + rect.y -= elRect.top + rect.y2 -= elRect.top let boxRatio = boxWidth / boxHeight let actWidth = 0 let actHeight = 0 @@ -55,19 +60,28 @@ class MiniMap { bottom: 0 } viewBoxStyle.left = - Math.max(0, (-_rectX / _rectWidth) * actWidth) + miniMapBoxLeft + 'px' + Math.max(0, (-_rectX / _rectWidth) * actWidth) + miniMapBoxLeft viewBoxStyle.right = Math.max(0, ((_rectX2 - origWidth) / _rectWidth) * actWidth) + - miniMapBoxLeft + - 'px' + miniMapBoxLeft viewBoxStyle.top = - Math.max(0, (-_rectY / _rectHeight) * actHeight) + miniMapBoxTop + 'px' + Math.max(0, (-_rectY / _rectHeight) * actHeight) + miniMapBoxTop viewBoxStyle.bottom = Math.max(0, ((_rectY2 - origHeight) / _rectHeight) * actHeight) + - miniMapBoxTop + - 'px' - + miniMapBoxTop + + if (viewBoxStyle.top > miniMapBoxTop + actHeight) { + viewBoxStyle.top = miniMapBoxTop + actHeight + } + if (viewBoxStyle.left > miniMapBoxLeft + actWidth) { + viewBoxStyle.left = miniMapBoxLeft + actWidth + } + + Object.keys(viewBoxStyle).forEach((key) => { + viewBoxStyle[key] = viewBoxStyle[key] + 'px' + }) + this.removeNodeContent(svg) return { svgHTML: svg.svg(), // 小地图html diff --git a/simple-mind-map/src/plugins/NodeImgAdjust.js b/simple-mind-map/src/plugins/NodeImgAdjust.js index 753d2ef2..0d6f7951 100644 --- a/simple-mind-map/src/plugins/NodeImgAdjust.js +++ b/simple-mind-map/src/plugins/NodeImgAdjust.js @@ -191,6 +191,12 @@ class NodeImgAdjust { btnRemove.addEventListener('click', e => { this.mindMap.execCommand('SET_NODE_IMAGE',this.node,{url:null}); }); + btnEl.addEventListener('click', e => { + e.stopPropagation() + }) + btnEl.addEventListener('mousedown', (e) => { + e.stopPropagation() + }) } // 鼠标按钮按下事件 diff --git a/simple-mind-map/src/plugins/Painter.js b/simple-mind-map/src/plugins/Painter.js new file mode 100644 index 00000000..50ee08e5 --- /dev/null +++ b/simple-mind-map/src/plugins/Painter.js @@ -0,0 +1,76 @@ +import { nodeDataNoStylePropList } from '../constants/constant' + +// 格式刷插件 +class Painter { + constructor({ mindMap }) { + this.mindMap = mindMap + this.isInPainter = false + this.painterNode = null + this.bindEvent() + } + + bindEvent() { + this.painterOneNode = this.painterOneNode.bind(this) + this.onEndPainter = this.onEndPainter.bind(this) + this.mindMap.on('node_click', this.painterOneNode) + this.mindMap.on('draw_click', this.onEndPainter) + } + + unBindEvent() { + this.mindMap.off('node_click', this.painterOneNode) + this.mindMap.off('draw_click', this.onEndPainter) + } + + // 开始格式刷 + startPainter() { + if (this.mindMap.opt.readonly) return + let activeNodeList = this.mindMap.renderer.activeNodeList + if (activeNodeList.length <= 0) return + this.painterNode = activeNodeList[0] + this.isInPainter = true + this.mindMap.emit('painter_start') + } + + // 结束格式刷 + endPainter() { + this.painterNode = null + this.isInPainter = false + } + + onEndPainter() { + this.endPainter() + this.mindMap.emit('painter_end') + } + + // 格式刷某个节点 + painterOneNode(node) { + if ( + !node || + !this.isInPainter || + !this.painterNode || + !node || + node === this.painterNode + ) + return + const style = {} + const painterNodeData = this.painterNode.nodeData.data + Object.keys(painterNodeData).forEach(key => { + if (!nodeDataNoStylePropList.includes(key)) { + style[key] = painterNodeData[key] + } + }) + node.setStyles(style) + if (painterNodeData.activeStyle) { + node.setStyles(painterNodeData.activeStyle, true) + } + } + + // 插件被移除前做的事情 + beforePluginRemove() { + this.unBindEvent() + } +} + +Painter.instanceName = 'painter' + +export default Painter diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index 64c6bdcf..7563c38b 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -172,7 +172,11 @@ class RichText { this.textEditNode.addEventListener('click', e => { e.stopPropagation() }) - document.body.appendChild(this.textEditNode) + this.textEditNode.addEventListener('mousedown', (e) => { + e.stopPropagation() + }) + const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body + targetNode.appendChild(this.textEditNode) } // 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见 let bgColor = node.style.merge('fillColor') diff --git a/simple-mind-map/src/plugins/Search.js b/simple-mind-map/src/plugins/Search.js index 5844ff00..c3bf654f 100644 --- a/simple-mind-map/src/plugins/Search.js +++ b/simple-mind-map/src/plugins/Search.js @@ -1,4 +1,4 @@ -import { bfsWalk, getTextFromHtml, isUndef } from '../utils/index' +import { bfsWalk, getTextFromHtml, isUndef, replaceHtmlText } from '../utils/index' // 搜索插件 class Search { @@ -15,12 +15,19 @@ class Search { this.currentIndex = -1 // 不要复位搜索文本 this.notResetSearchText = false + // 是否自动跳转下一个匹配节点 + this.isJumpNext = false this.onDataChange = this.onDataChange.bind(this) this.mindMap.on('data_change', this.onDataChange) } // 节点数据改变了,需要重新搜索 onDataChange() { + if (this.isJumpNext) { + this.isJumpNext = false + this.search(this.searchText) + return + } if (this.notResetSearchText) { this.notResetSearchText = false return @@ -29,7 +36,7 @@ class Search { } // 搜索 - search(text, callback) { + search(text, callback = () => {}) { if (isUndef(text)) return this.endSearch() text = String(text) this.isSearching = true @@ -88,13 +95,16 @@ class Search { } // 替换当前节点 - replace(replaceText) { + replace(replaceText, jumpNext = false) { if ( - isUndef(replaceText) || + replaceText === null || + replaceText === undefined || !this.isSearching || this.matchNodeList.length <= 0 ) return + // 自动跳转下一个匹配节点 + this.isJumpNext = jumpNext replaceText = String(replaceText) let currentNode = this.matchNodeList[this.currentIndex] if (!currentNode) return @@ -115,7 +125,8 @@ class Search { // 替换所有 replaceAll(replaceText) { if ( - isUndef(replaceText) || + replaceText === null || + replaceText === undefined || !this.isSearching || this.matchNodeList.length <= 0 ) @@ -141,9 +152,10 @@ class Search { getReplacedText(node, searchText, replaceText) { let { richText, text } = node.nodeData.data if (richText) { - text = getTextFromHtml(text) + return replaceHtmlText(text, searchText, replaceText) + } else { + return text.replaceAll(searchText, replaceText) } - return text.replaceAll(searchText, replaceText) } // 发送事件 diff --git a/simple-mind-map/src/plugins/TouchEvent.js b/simple-mind-map/src/plugins/TouchEvent.js index 2d4f78d1..ea8681b4 100644 --- a/simple-mind-map/src/plugins/TouchEvent.js +++ b/simple-mind-map/src/plugins/TouchEvent.js @@ -59,10 +59,10 @@ class TouchEvent { let cy = (touch1ClientY + touch2ClientY) / 2 if (distance > this.doubleTouchmoveDistance) { // 放大 - this.mindMap.view.enlarge(cx, cy) + this.mindMap.view.enlarge(cx, cy, true) } else { // 缩小 - this.mindMap.view.narrow(cx, cy) + this.mindMap.view.narrow(cx, cy, true) } this.doubleTouchmoveDistance = distance } diff --git a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js index 9d0722bb..26c31fdb 100644 --- a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js +++ b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js @@ -47,7 +47,8 @@ function showEditTextBox(g) { this.textEditNode.addEventListener('click', e => { e.stopPropagation() }) - document.body.appendChild(this.textEditNode) + const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body + targetNode.appendChild(this.textEditNode) } let { associativeLineTextFontSize, diff --git a/simple-mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js index c9d9bfa6..6b94a3c8 100644 --- a/simple-mind-map/src/themes/default.js +++ b/simple-mind-map/src/themes/default.js @@ -18,6 +18,8 @@ export default { lineDasharray: 'none', // 连线风格 lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线(curve)、直线(straight)、直连(direct) + // 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型,设为true后,都为 { 型 + rootLineKeepSameInCurve: true, // 概要连线的粗细 generalizationLineWidth: 1, // 概要连线的颜色 diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index 2be4cc22..9c1745d0 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -539,4 +539,59 @@ export const getVisibleColorFromTheme = (themeConfig) => { return color } } +} + +// 将

形式的节点富文本内容转换成
换行的文本 +let nodeRichTextToTextWithWrapEl = null +export const nodeRichTextToTextWithWrap = (html) => { + if (!nodeRichTextToTextWithWrapEl) { + nodeRichTextToTextWithWrapEl = document.createElement('div') + } + nodeRichTextToTextWithWrapEl.innerHTML = html + const childNodes = nodeRichTextToTextWithWrapEl.childNodes + let res = '' + for(let i = 0; i < childNodes.length; i++) { + const node = childNodes[i] + if (node.nodeType === 1) {// 元素节点 + if (node.tagName.toLowerCase() === 'p') { + res += node.textContent + '\n' + } else { + res += node.textContent + } + } else if (node.nodeType === 3) {// 文本节点 + res += node.nodeValue + } + } + return res.replace(/\n$/, '') +} + +// 将
换行的文本转换成

形式的节点富文本内容 +let textToNodeRichTextWithWrapEl = null +export const textToNodeRichTextWithWrap = (html) => { + if (!textToNodeRichTextWithWrapEl) { + textToNodeRichTextWithWrapEl = document.createElement('div') + } + textToNodeRichTextWithWrapEl.innerHTML = html + const childNodes = textToNodeRichTextWithWrapEl.childNodes + let list = [] + let str = '' + for(let i = 0; i < childNodes.length; i++) { + const node = childNodes[i] + if (node.nodeType === 1) {// 元素节点 + if (node.tagName.toLowerCase() === 'br') { + list.push(str) + str = '' + } else { + str += node.textContent + } + } else if (node.nodeType === 3) {// 文本节点 + str += node.nodeValue + } + } + if (str) { + list.push(str) + } + return list.map((item) => { + return `

${item}

` + }).join('') } \ No newline at end of file diff --git a/web/src/assets/avatar/才镇.jpg b/web/src/assets/avatar/才镇.jpg new file mode 100644 index 00000000..c442a058 Binary files /dev/null and b/web/src/assets/avatar/才镇.jpg differ diff --git a/web/src/config/en.js b/web/src/config/en.js index 97aa75d4..9d20c2ec 100644 --- a/web/src/config/en.js +++ b/web/src/config/en.js @@ -102,6 +102,18 @@ export const lineStyleList = [ } ] +// 曲线风格中,根节点样式是否和其他节点保持一致 +export const rootLineKeepSameInCurveList = [ + { + name: 'Bracket', + value: false + }, + { + name: 'Brace', + value: true + } +] + // 图片重复方式 export const backgroundRepeatList = [ { diff --git a/web/src/config/index.js b/web/src/config/index.js index d7a5991d..385fc236 100644 --- a/web/src/config/index.js +++ b/web/src/config/index.js @@ -10,6 +10,7 @@ import { fontFamilyList as fontFamilyListZh, borderDasharrayList as borderDasharrayListZh, lineStyleList as lineStyleListZh, + rootLineKeepSameInCurveList as rootLineKeepSameInCurveListZh, backgroundRepeatList as backgroundRepeatListZh, backgroundPositionList as backgroundPositionListZh, shortcutKeyList as shortcutKeyListZh, @@ -22,6 +23,7 @@ import { fontFamilyList as fontFamilyListEn, borderDasharrayList as borderDasharrayListEn, lineStyleList as lineStyleListEn, + rootLineKeepSameInCurveList as rootLineKeepSameInCurveListEn, backgroundRepeatList as backgroundRepeatListEn, backgroundPositionList as backgroundPositionListEn, shortcutKeyList as shortcutKeyListEn, @@ -46,6 +48,11 @@ const lineStyleList = { en: lineStyleListEn } +const rootLineKeepSameInCurveList = { + zh: rootLineKeepSameInCurveListZh, + en: rootLineKeepSameInCurveListEn +} + const backgroundRepeatList = { zh: backgroundRepeatListZh, en: backgroundRepeatListEn @@ -93,6 +100,7 @@ export { fontFamilyList, borderDasharrayList, lineStyleList, + rootLineKeepSameInCurveList, backgroundRepeatList, backgroundPositionList, backgroundSizeList, diff --git a/web/src/config/zh.js b/web/src/config/zh.js index 6f8d0965..21fccc0a 100644 --- a/web/src/config/zh.js +++ b/web/src/config/zh.js @@ -157,6 +157,18 @@ export const lineStyleList = [ } ] +// 曲线风格中,根节点样式是否和其他节点保持一致 +export const rootLineKeepSameInCurveList = [ + { + name: '括号', + value: false + }, + { + name: '大括号', + value: true + } +] + // 图片重复方式 export const backgroundRepeatList = [ { diff --git a/web/src/lang/en_us.js b/web/src/lang/en_us.js index a89f26ae..6aa781d4 100644 --- a/web/src/lang/en_us.js +++ b/web/src/lang/en_us.js @@ -46,7 +46,8 @@ export default { associativeLineActiveColor: 'Active color', mousewheelZoomActionReverse: 'Mouse Wheel Zoom', mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back', - mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back' + mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back', + rootStyle: 'Root Node' }, color: { moreColor: 'More color' @@ -202,6 +203,7 @@ export default { export: 'Export', shortcutKey: 'Shortcut key', associativeLine: 'Associative line', + painter: 'Painter' }, edit: { newFeatureNoticeTitle: 'New feature reminder', diff --git a/web/src/lang/zh_cn.js b/web/src/lang/zh_cn.js index 910dedfa..2abef680 100644 --- a/web/src/lang/zh_cn.js +++ b/web/src/lang/zh_cn.js @@ -46,7 +46,8 @@ export default { associativeLineActiveColor: '激活颜色', mousewheelZoomActionReverse: '鼠标滚轮缩放', mousewheelZoomActionReverse1: '向前缩小向后放大', - mousewheelZoomActionReverse2: '向前放大向后缩小' + mousewheelZoomActionReverse2: '向前放大向后缩小', + rootStyle: '根节点' }, color: { moreColor: '更多颜色' @@ -202,6 +203,7 @@ export default { export: '导出', shortcutKey: '快捷键', associativeLine: '关联线', + painter: '格式刷' }, edit: { newFeatureNoticeTitle: '新特性提醒', diff --git a/web/src/pages/Doc/en/constructor/index.md b/web/src/pages/Doc/en/constructor/index.md index b5a3eaca..1e4264f7 100644 --- a/web/src/pages/Doc/en/constructor/index.md +++ b/web/src/pages/Doc/en/constructor/index.md @@ -71,6 +71,7 @@ const mindMap = new MindMap({ | isUseCustomNodeContent(v0.6.3+) | Boolean | false | Whether to customize node content | | | customCreateNodeContent(v0.6.3+) | Function/null | null | If `isUseCustomNodeContent` is set to `true`, then this option needs to be used to pass in a method that receives the node instance `node` as a parameter (if you want to obtain data for that node, you can use `node.nodeData.data`). You need to return the custom node content element, which is the DOM node. If a node does not require customization, you can return `null` | | | mouseScaleCenterUseMousePosition(v0.6.4-fix.1+) | Boolean | true | Is the mouse zoom centered around the current position of the mouse, otherwise centered around the canvas | | +| customInnerElsAppendTo(v0.6.12+) | null/HTMLElement | null | Specify the location where some internal elements (node text editing element, node note display element, associated line text editing element, node image adjustment button element) are added, and default to document.body | | ### Watermark config @@ -242,7 +243,7 @@ Listen to an event. Event list: | rich_text_selection_change(v0.4.0+) | Available when the `RichText` plugin is registered. Triggered when the text selection area changes when the node is edited | hasRange(Whether there is a selection)、rectInfo(Size and location information of the selected area)、formatInfo(Text formatting information of the selected area) | | transforming-dom-to-images(v0.4.0+) | Available when the `RichText` plugin is registered. When there is a `DOM` node in `svg`, the `DOM` node will be converted to an image when exporting to an image. This event will be triggered during the conversion process. You can use this event to prompt the user about the node to which you are currently converting | index(Index of the node currently converted to)、len(Total number of nodes to be converted) | | node_dragging(v0.4.5+) | Triggered when a node is dragged | node(The currently dragged node) | -| node_dragend(v0.4.5+) | Triggered when the node is dragged and ends | | +| node_dragend(v0.4.5+) | Triggered when the node is dragged and ends | { overlapNodeUid, prevNodeUid, nextNodeUid }(v0.6.12+,The node uid to which the node is moved this time, for example, if it is moved to node A, then the overlayNodeUid is the uid of node A. If it is moved to the front of node B, then the nextNodeUid is the uid of node B. You can obtain the node instance through the mindMap. extender.findNodeByUid(uid) method) | | associative_line_click(v0.4.5+) | Triggered when an associated line is clicked | path(Connector node)、clickPath(Invisible click line node)、node(Start node)、toNode(Target node) | | svg_mouseenter(v0.5.1+) | Triggered when the mouse moves into the SVG canvas | e(event object) | | svg_mouseleave(v0.5.1+) | Triggered when the mouse moves out of the SVG canvas | e(event object) | diff --git a/web/src/pages/Doc/en/constructor/index.vue b/web/src/pages/Doc/en/constructor/index.vue index c8f70d61..b3940329 100644 --- a/web/src/pages/Doc/en/constructor/index.vue +++ b/web/src/pages/Doc/en/constructor/index.vue @@ -357,6 +357,13 @@ Is the mouse zoom centered around the current position of the mouse, otherwise centered around the canvas + +customInnerElsAppendTo(v0.6.12+) +null/HTMLElement +null +Specify the location where some internal elements (node text editing element, node note display element, associated line text editing element, node image adjustment button element) are added, and default to document.body + +

Watermark config

@@ -697,7 +704,7 @@ poor performance and should be used sparingly.

node_dragend(v0.4.5+) Triggered when the node is dragged and ends - +{ overlapNodeUid, prevNodeUid, nextNodeUid }(v0.6.12+,The node uid to which the node is moved this time, for example, if it is moved to node A, then the overlayNodeUid is the uid of node A. If it is moved to the front of node B, then the nextNodeUid is the uid of node B. You can obtain the node instance through the mindMap. extender.findNodeByUid(uid) method) associative_line_click(v0.4.5+) diff --git a/web/src/pages/Doc/en/introduction/index.md b/web/src/pages/Doc/en/introduction/index.md index 8e2d7be8..cb5328cc 100644 --- a/web/src/pages/Doc/en/introduction/index.md +++ b/web/src/pages/Doc/en/introduction/index.md @@ -167,4 +167,8 @@ Open source is not easy. If this project is helpful to you, you can invite the a

千帆

+
+ +

才镇

+
\ No newline at end of file diff --git a/web/src/pages/Doc/en/introduction/index.vue b/web/src/pages/Doc/en/introduction/index.vue index d225cc43..3275b07f 100644 --- a/web/src/pages/Doc/en/introduction/index.vue +++ b/web/src/pages/Doc/en/introduction/index.vue @@ -126,6 +126,10 @@ full screen, support mini map

千帆

+
+ +

才镇

+
diff --git a/web/src/pages/Doc/zh/constructor/index.md b/web/src/pages/Doc/zh/constructor/index.md index e4738fd2..45bba08b 100644 --- a/web/src/pages/Doc/zh/constructor/index.md +++ b/web/src/pages/Doc/zh/constructor/index.md @@ -71,6 +71,7 @@ const mindMap = new MindMap({ | isUseCustomNodeContent(v0.6.3+) | Boolean | false | 是否自定义节点内容 | | | customCreateNodeContent(v0.6.3+) | Function/null | null | 如果`isUseCustomNodeContent`设为`true`,那么需要使用该选项传入一个方法,接收节点实例`node`为参数(如果要获取该节点的数据,可以通过`node.nodeData.data`),需要返回自定义节点内容元素,也就是DOM节点,如果某个节点不需要自定义,那么返回`null`即可 | | | mouseScaleCenterUseMousePosition(v0.6.4-fix.1+) | Boolean | true | 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点 | | +| customInnerElsAppendTo(v0.6.12+) | null/HTMLElement | null | 指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下 | | ### 水印配置 @@ -237,7 +238,7 @@ mindMap.setTheme('主题名称') | rich_text_selection_change(v0.4.0+) | 当注册了`RichText`插件时可用。当节点编辑时,文本选区发生改变时触发 | hasRange(是否存在选区)、rectInfo(选区的尺寸和位置信息)、formatInfo(选区的文本格式化信息) | | transforming-dom-to-images(v0.4.0+) | 当注册了`RichText`插件时可用。当`svg`中存在`DOM`节点时,导出为图片时会将`DOM`节点转换为图片,转换过程中会触发该事件,可用通过该事件给用户提示,告知目前转换到的节点 | index(当前转换到的节点索引)、len(一共需要转换的节点数量) | | node_dragging(v0.4.5+) | 当某个节点被拖拽时触发 | node(当前被拖拽的节点) | -| node_dragend(v0.4.5+) | 节点被拖拽结束时触发 | | +| node_dragend(v0.4.5+) | 节点被拖拽结束时触发 | { overlapNodeUid, prevNodeUid, nextNodeUid }(v0.6.12+,本次节点移动到的节点uid,比如本次移动到了节点A上,那么overlapNodeUid就是节点A的uid,如果移动到了B节点的前面,那么nextNodeUid就是节点B的uid,你可以通过mindMap.renderer.findNodeByUid(uid)方法来获取节点实例) | | associative_line_click(v0.4.5+) | 点击某条关联线时触发 | path(连接线节点)、clickPath(不可见的点击线节点)、node(起始节点)、toNode(目标节点) | | svg_mouseenter(v0.5.1+) | 鼠标移入svg画布时触发 | e(事件对象) | | svg_mouseleave(v0.5.1+) | 鼠标移出svg画布时触发 | e(事件对象) | diff --git a/web/src/pages/Doc/zh/constructor/index.vue b/web/src/pages/Doc/zh/constructor/index.vue index a8a8e3ec..2d8b941c 100644 --- a/web/src/pages/Doc/zh/constructor/index.vue +++ b/web/src/pages/Doc/zh/constructor/index.vue @@ -357,6 +357,13 @@ 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点 + +customInnerElsAppendTo(v0.6.12+) +null/HTMLElement +null +指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下 + +

水印配置

@@ -690,7 +697,7 @@ mindMap.setTheme('主题名称') node_dragend(v0.4.5+) 节点被拖拽结束时触发 - +{ overlapNodeUid, prevNodeUid, nextNodeUid }(v0.6.12+,本次节点移动到的节点uid,比如本次移动到了节点A上,那么overlapNodeUid就是节点A的uid,如果移动到了B节点的前面,那么nextNodeUid就是节点B的uid,你可以通过mindMap.renderer.findNodeByUid(uid)方法来获取节点实例) associative_line_click(v0.4.5+) diff --git a/web/src/pages/Doc/zh/introduction/index.md b/web/src/pages/Doc/zh/introduction/index.md index ce12fcb1..bfa0c6e3 100644 --- a/web/src/pages/Doc/zh/introduction/index.md +++ b/web/src/pages/Doc/zh/introduction/index.md @@ -160,4 +160,8 @@

千帆

+
+ +

才镇

+
\ No newline at end of file diff --git a/web/src/pages/Doc/zh/introduction/index.vue b/web/src/pages/Doc/zh/introduction/index.vue index 63379869..ad8379bc 100644 --- a/web/src/pages/Doc/zh/introduction/index.vue +++ b/web/src/pages/Doc/zh/introduction/index.vue @@ -8,16 +8,16 @@

特性

仓库目录介绍

1.simple-mind-map

@@ -25,11 +25,11 @@

2.web

使用simple-mind-map库,基于vue2.xElementUI搭建的在线思维导图。特性:

提供文档页面服务。

3.dist

@@ -120,6 +120,10 @@

千帆

+
+ +

才镇

+
diff --git a/web/src/pages/Edit/components/BaseStyle.vue b/web/src/pages/Edit/components/BaseStyle.vue index 45afb6d4..213f6847 100644 --- a/web/src/pages/Edit/components/BaseStyle.vue +++ b/web/src/pages/Edit/components/BaseStyle.vue @@ -164,6 +164,28 @@ +
+ {{ $t('baseStyle.rootStyle') }} + + + + +
{{ $t('baseStyle.lineOfOutline') }}
@@ -630,7 +652,7 @@ diff --git a/web/src/pages/Edit/components/NodeHyperlink.vue b/web/src/pages/Edit/components/NodeHyperlink.vue index 32620758..92b0c11b 100644 --- a/web/src/pages/Edit/components/NodeHyperlink.vue +++ b/web/src/pages/Edit/components/NodeHyperlink.vue @@ -1,6 +1,6 @@