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/index.html b/index.html index 8ee26756..95aa32de 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ 思绪思维导图
\ No newline at end of file + } \ No newline at end of file diff --git a/qrcode.jpg b/qrcode.jpg index 6f53473e..b39a4ac2 100644 Binary files a/qrcode.jpg and b/qrcode.jpg differ diff --git a/simple-mind-map/full.js b/simple-mind-map/full.js index 2c78b8be..3d140c83 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 ExportXMind from './src/plugins/ExportXMind.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(ExportXMind) .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/index.js b/simple-mind-map/index.js index 9f373ebe..c5b6edc6 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -164,6 +164,7 @@ class MindMap { this.renderer.clearAllActive() this.opt.theme = theme this.render(null, CONSTANTS.CHANGE_THEME) + this.emit('view_theme_change', theme) } // 获取当前主题 diff --git a/simple-mind-map/package-lock.json b/simple-mind-map/package-lock.json index 29f719ea..ee730af9 100644 --- a/simple-mind-map/package-lock.json +++ b/simple-mind-map/package-lock.json @@ -1,17 +1,17 @@ { "name": "simple-mind-map", - "version": "0.6.7", + "version": "0.6.12", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.6.7", + "version": "0.6.12", "license": "MIT", "dependencies": { "@svgdotjs/svg.js": "^3.0.16", "deepmerge": "^1.5.2", + "dom-to-image-more": "^3.1.6", "eventemitter3": "^4.0.7", - "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "jszip": "^3.10.1", "mdast-util-from-markdown": "^1.3.0", @@ -255,6 +255,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true, "engines": { "node": ">= 0.6.0" } @@ -411,6 +412,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, "dependencies": { "utrie": "^1.0.2" } @@ -516,6 +518,11 @@ "node": ">=6.0.0" } }, + "node_modules/dom-to-image-more": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-3.1.6.tgz", + "integrity": "sha512-VMO0jNme32T06mWtkOC9QXfj+1npoJxkaTFW0DCwBLguwBKMjqwndiDANxDnbZ0kvNEecwxkv0Zmgdr96cGtAA==" + }, "node_modules/dompurify": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", @@ -937,6 +944,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" @@ -2142,6 +2150,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, "dependencies": { "utrie": "^1.0.2" } @@ -2206,6 +2215,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, "dependencies": { "base64-arraybuffer": "^1.0.2" } @@ -2461,7 +2471,8 @@ "base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true }, "brace-expansion": { "version": "1.1.11", @@ -2576,6 +2587,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, "requires": { "utrie": "^1.0.2" } @@ -2648,6 +2660,11 @@ "esutils": "^2.0.2" } }, + "dom-to-image-more": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-3.1.6.tgz", + "integrity": "sha512-VMO0jNme32T06mWtkOC9QXfj+1npoJxkaTFW0DCwBLguwBKMjqwndiDANxDnbZ0kvNEecwxkv0Zmgdr96cGtAA==" + }, "dompurify": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", @@ -2966,6 +2983,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, "requires": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" @@ -3749,6 +3767,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, "requires": { "utrie": "^1.0.2" } @@ -3800,6 +3819,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, "requires": { "base64-arraybuffer": "^1.0.2" } diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index 8a58a94a..7d5c467e 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -1,6 +1,6 @@ { "name": "simple-mind-map", - "version": "0.6.11-fix.1", + "version": "0.6.13", "description": "一个简单的web在线思维导图", "authors": [ { @@ -26,8 +26,8 @@ "dependencies": { "@svgdotjs/svg.js": "^3.0.16", "deepmerge": "^1.5.2", + "dom-to-image-more": "^3.1.6", "eventemitter3": "^4.0.7", - "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "jszip": "^3.10.1", "mdast-util-from-markdown": "^1.3.0", 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..dfd07c83 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -122,5 +122,17 @@ export const defaultOpt = { // 是否开启自定义节点内容 isUseCustomNodeContent: false, // 自定义返回节点内容的方法 - customCreateNodeContent: null + customCreateNodeContent: null, + // 指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下 + customInnerElsAppendTo: null, + // 拖拽元素时,指示元素新位置的块的最大高度 + nodeDragPlaceholderMaxSize: 20, + // 是否允许创建一个隐藏的输入框,该输入框会在节点激活时聚焦,用于粘贴数据和自动进入文本编辑状态 + enableCreateHiddenInput: true, + // 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式 + // 该配置在enableCreateHiddenInput设为true时生效 + enableAutoEnterTextEditWhenKeydown: true, + // 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果 + // 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果 + richTextEditFakeInPlace: false } diff --git a/simple-mind-map/src/core/command/KeyCommand.js b/simple-mind-map/src/core/command/KeyCommand.js index e894516c..4b493fd6 100644 --- a/simple-mind-map/src/core/command/KeyCommand.js +++ b/simple-mind-map/src/core/command/KeyCommand.js @@ -108,6 +108,11 @@ export default class KeyCommand { return arr } + // 判断是否按下了组合键 + hasCombinationKey(e) { + return e.ctrlKey || e.metaKey || e.altKey || e.shiftKey + } + // 获取快捷键对应的键值数组 getKeyCodeArr(key) { let keyArr = key.split(/\s*\+\s*/) diff --git a/simple-mind-map/src/core/event/Event.js b/simple-mind-map/src/core/event/Event.js index 3eb36d51..f1313b37 100644 --- a/simple-mind-map/src/core/event/Event.js +++ b/simple-mind-map/src/core/event/Event.js @@ -10,6 +10,7 @@ class Event extends EventEmitter { this.mindMap = opt.mindMap this.isLeftMousedown = false this.isRightMousedown = false + this.isMiddleMousedown = false this.mousedownPos = { x: 0, y: 0 @@ -92,6 +93,8 @@ class Event extends EventEmitter { this.isLeftMousedown = true } else if (e.which === 3) { this.isRightMousedown = true + } else if (e.which === 2) { + this.isMiddleMousedown = true } this.mousedownPos.x = e.clientX this.mousedownPos.y = e.clientY @@ -107,9 +110,10 @@ class Event extends EventEmitter { this.mousemoveOffset.y = e.clientY - this.mousedownPos.y this.emit('mousemove', e, this) if ( - useLeftKeySelectionRightKeyDrag + this.isMiddleMousedown || + (useLeftKeySelectionRightKeyDrag ? this.isRightMousedown - : this.isLeftMousedown + : this.isLeftMousedown) ) { e.preventDefault() this.emit('drag', e, this) @@ -120,6 +124,7 @@ class Event extends EventEmitter { onMouseup(e) { this.isLeftMousedown = false this.isRightMousedown = false + this.isMiddleMousedown = false this.emit('mouseup', e, this) } diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index bc2c4439..8944dba9 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -48,7 +48,7 @@ class Render { this.themeConfig = this.mindMap.themeConfig this.draw = this.mindMap.draw // 渲染树,操作过程中修改的都是这里的数据 - this.renderTree = merge({},this.mindMap.opt.data || {}) + this.renderTree = merge({}, this.mindMap.opt.data || {}) // 是否重新渲染 this.reRender = false // 是否正在渲染中 @@ -112,6 +112,13 @@ class Render { this.mindMap.on('paste', data => { this.onPaste(data) }) + // let timer = null + // this.mindMap.on('view_data_change', () => { + // clearTimeout(timer) + // timer = setTimeout(() => { + // this.render() + // }, 300) + // }) } // 注册命令 @@ -153,9 +160,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 +869,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 +1178,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..92499d98 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, isMobile } from '../../utils' // 节点文字编辑类 export default class TextEdit { @@ -12,6 +12,8 @@ export default class TextEdit { this.textEditNode = null // 隐藏的文本输入框 this.hiddenInputEl = null + // 节点激活时默认聚焦到隐藏输入框 + this.enableFocus = true // 文本编辑框是否显示 this.showTextEdit = false // 如果编辑过程中缩放画布了,那么缓存当前编辑的内容 @@ -65,7 +67,9 @@ export default class TextEdit { // 创建一个隐藏的文本输入框 createHiddenInput() { - if (this.hiddenInputEl) return + const { enableCreateHiddenInput, enableAutoEnterTextEditWhenKeydown } = + this.mindMap.opt + if (this.hiddenInputEl || isMobile() || !enableCreateHiddenInput) return this.hiddenInputEl = document.createElement('input') this.hiddenInputEl.type = 'text' this.hiddenInputEl.style.cssText = ` @@ -73,6 +77,25 @@ export default class TextEdit { left: -99999px; top: -99999px; ` + // 监听按键事件 + if (enableAutoEnterTextEditWhenKeydown) { + this.hiddenInputEl.addEventListener('keydown', e => { + const activeNodeList = this.mindMap.renderer.activeNodeList + if (activeNodeList.length <= 0 || activeNodeList.length > 1) return + const node = activeNodeList[0] + // 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式 + const keyCode = e.keyCode + if ( + node && + (keyCode === 229 || + (keyCode >= 65 && keyCode <= 90) || + (keyCode >= 48 && keyCode <= 57)) && + !this.mindMap.keyCommand.hasCombinationKey(e) + ) { + this.show(node) + } + }) + } // 监听粘贴事件 this.hiddenInputEl.addEventListener('paste', async event => { event.preventDefault() @@ -97,7 +120,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 } // 注册临时快捷键 @@ -136,7 +169,7 @@ export default class TextEdit { this.mindMap.richText.showEditText(node, rect, isInserting) return } - this.showEditTextBox(node, rect) + this.showEditTextBox(node, rect, isInserting) } // 处理画布缩放 @@ -154,7 +187,7 @@ export default class TextEdit { } // 显示文本编辑框 - showEditTextBox(node, rect) { + showEditTextBox(node, rect, isInserting) { this.mindMap.emit('before_show_text_edit') this.registerTmpShortcut() if (!this.textEditNode) { @@ -167,7 +200,12 @@ 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') @@ -193,12 +231,27 @@ export default class TextEdit { } this.showTextEdit = true // 选中文本 - if (!this.cacheEditingText) { + // if (!this.cacheEditingText) { + // this.selectNodeText() + // } + if (isInserting) { this.selectNodeText() + } else { + this.focus() } 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() diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index 4a5a6ac2..e614e36b 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -1,6 +1,5 @@ import Style from './Style' import Shape from './Shape' -import { asyncRun, nodeToHTML } from '../../../utils' import { G, Rect, ForeignObject, SVG } from '@svgdotjs/svg.js' import nodeGeneralizationMethods from './nodeGeneralization' import nodeExpandBtnMethods from './nodeExpandBtn' @@ -98,6 +97,8 @@ class Node { this.isMultipleChoice = false // 是否需要重新layout this.needLayout = false + // 当前是否是隐藏状态 + this.isHide = false // 概要相关方法 Object.keys(nodeGeneralizationMethods).forEach(item => { this[item] = nodeGeneralizationMethods[item].bind(this) @@ -368,7 +369,13 @@ class Node { }) } this.group.add(this._unVisibleRectRegionNode) - this.renderer.layout.renderExpandBtnRect(this._unVisibleRectRegionNode, this.expandBtnSize, width, height, this) + this.renderer.layout.renderExpandBtnRect( + this._unVisibleRectRegionNode, + this.expandBtnSize, + width, + height, + this + ) } } @@ -385,10 +392,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 && e.which !== 2 && !this.mindMap.opt.readonly) { e.stopPropagation() } // 多选和取消多选 @@ -414,7 +421,7 @@ class Node { this.mindMap.emit('node_mousedown', this, e) }) this.group.on('mouseup', e => { - if (!this.isRoot) { + if (!this.isRoot && e.which !== 2 && !this.mindMap.opt.readonly) { e.stopPropagation() } this.mindMap.emit('node_mouseup', this, e) @@ -476,9 +483,7 @@ class Node { if (!this.group) { return } - let { - alwaysShowExpandBtn - } = this.mindMap.opt + let { alwaysShowExpandBtn } = this.mindMap.opt if (alwaysShowExpandBtn) { // 需要移除展开收缩按钮 if (this._expandBtn && this.nodeData.children.length <= 0) { @@ -500,11 +505,48 @@ class Node { this.renderGeneralization() // 更新节点位置 let t = this.group.transform() + // // 如果上次不在可视区内,且本次也不在,那么直接返回 + // let { left: ox, top: oy } = this.getNodePosInClient( + // t.translateX, + // t.translateY + // ) + // let oldIsInClient = + // ox > 0 && oy > 0 && ox < this.mindMap.width && oy < this.mindMap.height + // let { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top) + // let newIsNotInClient = + // nx + this.width < 0 || + // ny + this.height < 0 || + // nx > this.mindMap.width || + // ny > this.mindMap.height + // if (!oldIsInClient && newIsNotInClient) { + // if (!this.isHide) { + // this.isHide = true + // this.group.hide() + // } + // return + // } + // // 如果当前是隐藏状态,那么先显示 + // if (this.isHide) { + // this.isHide = false + // this.group.show() + // } // 如果节点位置没有变化,则返回 if (this.left === t.translateX && this.top === t.translateY) return this.group.translate(this.left - t.translateX, this.top - t.translateY) } + // 获取节点相当于画布的位置 + getNodePosInClient(_left, _top) { + let drawTransform = this.mindMap.draw.transform() + let { scaleX, scaleY, translateX, translateY } = drawTransform + let left = _left * scaleX + translateX + let top = _top * scaleY + translateY + return { + left, + top + } + } + // 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置 reRender() { let sizeChange = this.getSize() @@ -559,18 +601,14 @@ class Node { this.nodeData.data.expand !== false ) { let index = 0 - asyncRun( - this.children.map(item => { - return () => { - item.render(() => { - index++ - if (index >= this.children.length) { - callback() - } - }) + this.children.forEach(item => { + item.render(() => { + index++ + if (index >= this.children.length) { + callback() } }) - ) + }) } else { callback() } @@ -592,13 +630,9 @@ class Node { this.removeLine() // 子节点 if (this.children && this.children.length) { - asyncRun( - this.children.map(item => { - return () => { - item.remove() - } - }) - ) + this.children.forEach(item => { + item.remove() + }) } } @@ -624,13 +658,9 @@ class Node { } // 子节点 if (this.children && this.children.length) { - asyncRun( - this.children.map(item => { - return () => { - item.hide() - } - }) - ) + this.children.forEach(item => { + item.hide() + }) } } @@ -650,13 +680,9 @@ class Node { } // 子节点 if (this.children && this.children.length) { - asyncRun( - this.children.map(item => { - return () => { - item.show() - } - }) - ) + this.children.forEach(item => { + item.show() + }) } } diff --git a/simple-mind-map/src/core/render/node/Style.js b/simple-mind-map/src/core/render/node/Style.js index 99e7def9..9cd452d2 100644 --- a/simple-mind-map/src/core/render/node/Style.js +++ b/simple-mind-map/src/core/render/node/Style.js @@ -17,7 +17,7 @@ class Style { // 设置新样式 let { backgroundColor, backgroundImage, backgroundRepeat, backgroundPosition, backgroundSize } = themeConfig el.style.backgroundColor = backgroundColor - if (backgroundImage) { + if (backgroundImage && backgroundImage !== 'none') { el.style.backgroundImage = `url(${backgroundImage})` el.style.backgroundRepeat = backgroundRepeat el.style.backgroundPosition = backgroundPosition 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/core/view/View.js b/simple-mind-map/src/core/view/View.js index 2f89bf66..0f16e52c 100644 --- a/simple-mind-map/src/core/view/View.js +++ b/simple-mind-map/src/core/view/View.js @@ -82,15 +82,20 @@ class View { // 鼠标滚轮,向上和向左,都是缩小 case CONSTANTS.DIR.UP: case CONSTANTS.DIR.LEFT: - mousewheelZoomActionReverse ? this.enlarge(cx, cy, isTouchPad) : this.narrow(cx, cy, isTouchPad) + mousewheelZoomActionReverse + ? this.enlarge(cx, cy, isTouchPad) + : this.narrow(cx, cy, isTouchPad) break // 鼠标滚轮,向下和向右,都是放大 case CONSTANTS.DIR.DOWN: case CONSTANTS.DIR.RIGHT: - mousewheelZoomActionReverse ? this.narrow(cx, cy, isTouchPad) : this.enlarge(cx, cy, isTouchPad) + mousewheelZoomActionReverse + ? this.narrow(cx, cy, isTouchPad) + : this.enlarge(cx, cy, isTouchPad) break } - } else {// 鼠标滚轮事件控制画布移动 + } else { + // 鼠标滚轮事件控制画布移动 let step = mousewheelMoveStep if (isTouchPad) { step = 5 @@ -147,6 +152,7 @@ class View { // 平移x,y方向 translateXY(x, y) { + if (x === 0 && y === 0) return this.x += x this.y += y this.transform() @@ -154,24 +160,28 @@ class View { // 平移x方向 translateX(step) { + if (step === 0) return this.x += step this.transform() } // 平移x方式到 translateXTo(x) { + if (x === 0) return this.x = x this.transform() } // 平移y方向 translateY(step) { + if (step === 0) return this.y += step this.transform() } // 平移y方向到 translateYTo(y) { + if (y === 0) return this.y = y this.transform() } 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/parse/xmind.js b/simple-mind-map/src/parse/xmind.js index ed8810c2..cdb752d2 100644 --- a/simple-mind-map/src/parse/xmind.js +++ b/simple-mind-map/src/parse/xmind.js @@ -123,6 +123,7 @@ const transformOldXmind = content => { let elements = data.elements let root = null let getRoot = arr => { + if (!arr) return for (let i = 0; i < arr.length; i++) { if (!root && arr[i].name === 'topic') { root = arr[i] @@ -142,9 +143,10 @@ const transformOldXmind = content => { } let walk = (node, newNode) => { let nodeElements = node.elements + let nodeTitle = getItemByName(nodeElements, 'title') newNode.data = { // 节点内容 - text: getItemByName(nodeElements, 'title').elements[0].text + text: nodeTitle && nodeTitle.elements && nodeTitle.elements[0].text } try { // 节点备注 diff --git a/simple-mind-map/src/plugins/Drag.js b/simple-mind-map/src/plugins/Drag.js index 78a4b8aa..fd99c5bf 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 + }) } // 创建克隆节点 @@ -196,9 +204,10 @@ class Drag extends Base { // 检测重叠节点 checkOverlapNode() { - if (!this.drawTransform) { + if (!this.drawTransform || !this.placeholder) { 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/ExportXMind.js b/simple-mind-map/src/plugins/ExportXMind.js index 42ea665c..aeccf60c 100644 --- a/simple-mind-map/src/plugins/ExportXMind.js +++ b/simple-mind-map/src/plugins/ExportXMind.js @@ -12,6 +12,11 @@ class ExportXMind { const zipData = await xmind.transformToXmind(data, name) return zipData } + + // 获取解析器 + getXmind() { + return xmind + } } ExportXMind.instanceName = 'doExportXMind' 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 37fce4b7..4999f4bc 100644 --- a/simple-mind-map/src/plugins/NodeImgAdjust.js +++ b/simple-mind-map/src/plugins/NodeImgAdjust.js @@ -82,7 +82,7 @@ class NodeImgAdjust { this.createResizeBtnEl() } this.setHandleElRect() - document.body.appendChild(this.handleEl) + this.handleEl.style.display = 'block' this.isShowHandleEl = true } @@ -90,7 +90,7 @@ class NodeImgAdjust { hideHandleEl() { if (!this.isShowHandleEl) return this.isShowHandleEl = false - document.body.removeChild(this.handleEl) + this.handleEl.style.display = 'none' this.handleEl.style.backgroundImage = `` this.handleEl.style.width = 0 this.handleEl.style.height = 0 @@ -121,8 +121,10 @@ class NodeImgAdjust { this.handleEl.style.cssText = ` pointer-events: none; position: fixed; + display:none; background-size: cover; ` + this.handleEl.className = 'node-img-handle' // 调整按钮元素 const btnEl = document.createElement('div') btnEl.innerHTML = btnsSvg.imgAdjust @@ -139,7 +141,7 @@ class NodeImgAdjust { align-items: center; cursor: nwse-resize; ` - this.handleEl.appendChild(btnEl) + btnEl.className = 'node-image-resize' // 给按钮元素绑定事件 btnEl.addEventListener('mouseenter', () => { // 移入按钮,会触发节点图片的移出事件,所以需要再次显示按钮 @@ -151,8 +153,50 @@ class NodeImgAdjust { this.hideHandleEl() }) btnEl.addEventListener('mousedown', e => { + e.stopPropagation() this.onMousedown(e) }) + btnEl.addEventListener('mouseup', e => { + setTimeout(() => { + //点击后直接松开异常处理; 其他事件响应之后处理 + this.hideHandleEl() + this.isAdjusted = false + }, 0) + }) + btnEl.addEventListener('click', e => { + e.stopPropagation() + }) + this.handleEl.appendChild(btnEl) + // 删除按钮 + const btnRemove = document.createElement('div') + this.handleEl.prepend(btnRemove) + btnRemove.className = 'node-image-remove' + btnRemove.innerHTML = btnsSvg.remove + btnRemove.style.cssText = ` + position: absolute; + right: 0;top:0;color:#fff; + pointer-events: auto; + background-color: rgba(0, 0, 0, 0.3); + width: ${this.resizeBtnSize}px; + height: ${this.resizeBtnSize}px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + ` + btnRemove.addEventListener('mouseenter', e => { + this.showHandleEl() + }) + btnRemove.addEventListener('mouseleave', e => { + if (this.isMousedown) return + this.hideHandleEl() + }) + btnRemove.addEventListener('click', e => { + this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null }) + }) + // 添加元素到页面 + const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body + targetNode.appendChild(this.handleEl) } // 鼠标按钮按下事件 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 ef74be49..7b818e4e 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -1,7 +1,12 @@ import Quill from 'quill' import 'quill/dist/quill.snow.css' -import html2canvas from 'html2canvas' -import { walk, getTextFromHtml, isWhite, getVisibleColorFromTheme } from '../utils' +import domtoimage from 'dom-to-image-more' +import { + walk, + getTextFromHtml, + isWhite, + getVisibleColorFromTheme +} from '../utils' import { CONSTANTS } from '../constants/constant' let extended = false @@ -150,6 +155,12 @@ class RichText { if (this.showTextEdit) { return } + const { + richTextEditFakeInPlace, + customInnerElsAppendTo, + nodeTextEditZIndex, + textAutoWrapWidth + } = this.mindMap.opt this.node = node this.isInserting = isInserting if (!rect) rect = node._textData.node.node.getBoundingClientRect() @@ -163,34 +174,60 @@ class RichText { let scaleX = rect.width / originWidth let scaleY = rect.height / originHeight // 内边距 - const paddingX = 6 - const paddingY = 4 + let paddingX = 6 + let paddingY = 4 + if (richTextEditFakeInPlace) { + let paddingValue = node.getPaddingVale() + paddingX = paddingValue.paddingX + paddingY = paddingValue.paddingY + } if (!this.textEditNode) { this.textEditNode = document.createElement('div') this.textEditNode.classList.add('smm-richtext-node-edit-wrap') - this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;padding: ${paddingY}px ${paddingX}px;` + this.textEditNode.style.cssText = ` + position:fixed; + box-sizing: border-box; + box-shadow: 0 0 20px rgba(0,0,0,.5); + outline: none; + word-break: + break-all;padding: ${paddingY}px ${paddingX}px; + ` this.textEditNode.addEventListener('click', e => { e.stopPropagation() }) - document.body.appendChild(this.textEditNode) + this.textEditNode.addEventListener('mousedown', e => { + e.stopPropagation() + }) + const targetNode = customInnerElsAppendTo || document.body + targetNode.appendChild(this.textEditNode) } // 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见 let bgColor = node.style.merge('fillColor') let color = node.style.merge('color') this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px` this.textEditNode.style.marginTop = `-${paddingY * scaleY}px` - this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex + this.textEditNode.style.zIndex = nodeTextEditZIndex this.textEditNode.style.backgroundColor = - bgColor === 'transparent' ? isWhite(color) ? getVisibleColorFromTheme(this.mindMap.themeConfig) : '#fff' : bgColor + bgColor === 'transparent' + ? isWhite(color) + ? getVisibleColorFromTheme(this.mindMap.themeConfig) + : '#fff' + : bgColor this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px' this.textEditNode.style.minHeight = originHeight + 'px' this.textEditNode.style.left = rect.left + 'px' this.textEditNode.style.top = rect.top + 'px' this.textEditNode.style.display = 'block' - this.textEditNode.style.maxWidth = - this.mindMap.opt.textAutoWrapWidth + paddingX * 2 + 'px' + this.textEditNode.style.maxWidth = textAutoWrapWidth + paddingX * 2 + 'px' this.textEditNode.style.transform = `scale(${scaleX}, ${scaleY})` this.textEditNode.style.transformOrigin = 'left top' + if (richTextEditFakeInPlace) { + this.textEditNode.style.borderRadius = + (node.style.merge('borderRadius') || 5) + 'px' + if (node.style.merge('shape') == 'roundedRectangle') { + this.textEditNode.style.borderRadius = (node.height || 50) + 'px' + } + } if (!node.nodeData.data.richText) { // 还不是富文本的情况 let text = node.nodeData.data.text.split(/\n/gim).join('
') @@ -498,11 +535,16 @@ class RichText { } } walk(node) - let canvas = await html2canvas(el, { - backgroundColor: null - }) + + // 如果使用html2canvas + // let canvas = await html2canvas(el, { + // backgroundColor: null + // }) + // return canvas.toDataURL() + + const res = await domtoimage.toPng(el) this.mindMap.el.removeChild(el) - return canvas.toDataURL() + return res } // 将所有节点转换成非富文本节点 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..1c91f100 100644 --- a/simple-mind-map/src/plugins/TouchEvent.js +++ b/simple-mind-map/src/plugins/TouchEvent.js @@ -6,7 +6,7 @@ class TouchEvent { this.touchesNum = 0 this.singleTouchstartEvent = null this.clickNum = 0 - this.doubleTouchmoveDistance = 0 + this.touchStartScaleView = null this.bindEvent() } @@ -33,6 +33,7 @@ class TouchEvent { // 手指按下事件 onTouchstart(e) { this.touchesNum = e.touches.length + this.touchStartScaleView = null if (this.touchesNum === 1) { let touch = e.touches[0] this.singleTouchstartEvent = touch @@ -53,18 +54,46 @@ class TouchEvent { let oy = touch1.clientY - touch2.clientY let distance = Math.sqrt(Math.pow(ox, 2) + Math.pow(oy, 2)) // 以两指中心点进行缩放 - let { x: touch1ClientX, y: touch1ClientY } = this.mindMap.toPos(touch1.clientX, touch1.clientY) - let { x: touch2ClientX, y: touch2ClientY } = this.mindMap.toPos(touch2.clientX, touch2.clientY) + let { x: touch1ClientX, y: touch1ClientY } = this.mindMap.toPos( + touch1.clientX, + touch1.clientY + ) + let { x: touch2ClientX, y: touch2ClientY } = this.mindMap.toPos( + touch2.clientX, + touch2.clientY + ) let cx = (touch1ClientX + touch2ClientX) / 2 let cy = (touch1ClientY + touch2ClientY) / 2 - if (distance > this.doubleTouchmoveDistance) { - // 放大 - this.mindMap.view.enlarge(cx, cy) - } else { - // 缩小 - this.mindMap.view.narrow(cx, cy) + // 手势缩放,基于最开始的位置进行缩放(基于前一个位置缩放不是线性关系); 缩放同时支持位置拖动 + const view = this.mindMap.view + if (!this.touchStartScaleView) { + this.touchStartScaleView = { + distance: distance, + scale: view.scale, + x: view.x, + y: view.y, + cx: cx, + cy: cy + } + return } - this.doubleTouchmoveDistance = distance + const viewBefore = this.touchStartScaleView + const scale = viewBefore.scale * (distance / viewBefore.distance) + if (Math.abs(distance - viewBefore.distance) <= 10) { + scale = viewBefore.scale + } + const ratio = 1 - scale / viewBefore.scale + view.scale = scale < 0.1 ? 0.1 : scale + view.x = + viewBefore.x + + (cx - viewBefore.x) * ratio + + (cx - viewBefore.cx) * scale + view.y = + viewBefore.y + + (cy - viewBefore.y) * ratio + + (cy - viewBefore.cy) * scale + view.transform() + this.mindMap.emit('scale', scale) } } @@ -91,7 +120,7 @@ class TouchEvent { } this.touchesNum = 0 this.singleTouchstartEvent = null - this.doubleTouchmoveDistance = 0 + this.touchStartScaleView = null } // 发送鼠标事件 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/svg/btns.js b/simple-mind-map/src/svg/btns.js index 9b132737..a4b67ed0 100644 --- a/simple-mind-map/src/svg/btns.js +++ b/simple-mind-map/src/svg/btns.js @@ -4,11 +4,15 @@ const open = `` +// 删除按钮 +const remove = `` + // 图片调整按钮 const imgAdjust = `` export default { open, close, + remove, imgAdjust } 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..c934eca6 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -455,25 +455,25 @@ export const loadImage = imgFile => { } // 移除字符串中的html实体 -export const removeHTMLEntities = (str) => { - [[' ', ' ']].forEach((item) => { +export const removeHTMLEntities = str => { + ;[[' ', ' ']].forEach(item => { str = str.replaceAll(item[0], item[1]) }) return str } // 获取一个数据的类型 -export const getType = (data) => { +export const getType = data => { return Object.prototype.toString.call(data).slice(7, -1) } // 判断一个数据是否是null和undefined和空字符串 -export const isUndef = (data) => { +export const isUndef = data => { return data === null || data === undefined || data === '' } // 移除html字符串中节点的内联样式 -export const removeHtmlStyle = (html) => { +export const removeHtmlStyle = html => { return html.replaceAll(/(<[^\s]+)\s+style=["'][^'"]+["']\s*(>)/g, '$1$2') } @@ -485,12 +485,12 @@ export const addHtmlStyle = (html, tag, style) => { // 检查一个字符串是否是富文本字符 let checkIsRichTextEl = null -export const checkIsRichText = (str) => { +export const checkIsRichText = str => { if (!checkIsRichTextEl) { checkIsRichTextEl = document.createElement('div') } checkIsRichTextEl.innerHTML = str - for (let c = checkIsRichTextEl.childNodes, i = c.length; i--;) { + for (let c = checkIsRichTextEl.childNodes, i = c.length; i--; ) { if (c[i].nodeType == 1) return true } return false @@ -503,13 +503,20 @@ export const replaceHtmlText = (html, searchText, replaceText) => { replaceHtmlTextEl = document.createElement('div') } replaceHtmlTextEl.innerHTML = html - let walk = (root) => { + let walk = root => { let childNodes = root.childNodes - childNodes.forEach((node) => { - if (node.nodeType === 1) {// 元素节点 + childNodes.forEach(node => { + if (node.nodeType === 1) { + // 元素节点 walk(node) - } else if (node.nodeType === 3) {// 文本节点 - root.replaceChild(document.createTextNode(node.nodeValue.replaceAll(searchText, replaceText)), node) + } else if (node.nodeType === 3) { + // 文本节点 + root.replaceChild( + document.createTextNode( + node.nodeValue.replaceAll(searchText, replaceText) + ), + node + ) } }) } @@ -518,25 +525,110 @@ export const replaceHtmlText = (html, searchText, replaceText) => { } // 判断一个颜色是否是白色 -export const isWhite = (color) => { +export const isWhite = color => { color = String(color).replaceAll(/\s+/g, '') - return ['#fff', '#ffffff', '#FFF', '#FFFFFF', 'rgb(255,255,255)'].includes(color) || /rgba\(255,255,255,[^)]+\)/.test(color) + return ( + ['#fff', '#ffffff', '#FFF', '#FFFFFF', 'rgb(255,255,255)'].includes( + color + ) || /rgba\(255,255,255,[^)]+\)/.test(color) + ) } // 判断一个颜色是否是透明 -export const isTransparent = (color) => { +export const isTransparent = color => { color = String(color).replaceAll(/\s+/g, '') - return ['', 'transparent'].includes(color) || /rgba\(\d+,\d+,\d+,0\)/.test(color) + return ( + ['', 'transparent'].includes(color) || /rgba\(\d+,\d+,\d+,0\)/.test(color) + ) } // 从当前主题里获取一个非透明非白色的颜色 -export const getVisibleColorFromTheme = (themeConfig) => { +export const getVisibleColorFromTheme = themeConfig => { let { lineColor, root, second, node } = themeConfig - let list = [lineColor, root.fillColor, root.color, second.fillColor, second.color, node.fillColor, node.color, root.borderColor, second.borderColor, node.borderColor] - for(let i = 0; i < list.length; i++) { + let list = [ + lineColor, + root.fillColor, + root.color, + second.fillColor, + second.color, + node.fillColor, + node.color, + root.borderColor, + second.borderColor, + node.borderColor + ] + for (let i = 0; i < list.length; i++) { let color = list[i] if (!isTransparent(color) && !isWhite(color)) { return color } } -} \ No newline at end of file +} + +// 将

形式的节点富文本内容转换成\n换行的文本 +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('') +} + +// 判断是否是移动端环境 +export const isMobile = () => { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent + ) +} diff --git a/web/.env.library b/web/.env.library new file mode 100644 index 00000000..886c19c2 --- /dev/null +++ b/web/.env.library @@ -0,0 +1 @@ +NODE_ENV=library \ No newline at end of file diff --git a/web/package.json b/web/package.json index cc6af0f8..1c1d38db 100644 --- a/web/package.json +++ b/web/package.json @@ -20,7 +20,7 @@ "electron:serve": "vue-cli-service electron:serve", "postinstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps", - "buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js && esbuild ../simple-mind-map/full.js --bundle --minify --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.min.js", + "buildLibrary": "vue-cli-service build --mode library --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js && esbuild ../simple-mind-map/full.js --bundle --minify --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.min.js", "format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*", "buildDoc": "node ./scripts/buildDoc.js", "autoBuildDoc": "node ./scripts/autoBuildDoc.js", 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/assets/img/catalogOrganization.jpg b/web/src/assets/img/catalogOrganization.jpg deleted file mode 100644 index 7978d11f..00000000 Binary files a/web/src/assets/img/catalogOrganization.jpg and /dev/null differ diff --git a/web/src/assets/img/iconList.jpg b/web/src/assets/img/docs/iconList.jpg similarity index 100% rename from web/src/assets/img/iconList.jpg rename to web/src/assets/img/docs/iconList.jpg diff --git a/web/src/assets/img/fishbone.jpg b/web/src/assets/img/fishbone.jpg deleted file mode 100644 index a52bd6ec..00000000 Binary files a/web/src/assets/img/fishbone.jpg and /dev/null differ diff --git a/web/src/assets/img/block1.png b/web/src/assets/img/index/block1.png similarity index 100% rename from web/src/assets/img/block1.png rename to web/src/assets/img/index/block1.png diff --git a/web/src/assets/img/block3.png b/web/src/assets/img/index/block3.png similarity index 100% rename from web/src/assets/img/block3.png rename to web/src/assets/img/index/block3.png diff --git a/web/src/assets/img/block4.png b/web/src/assets/img/index/block4.png similarity index 100% rename from web/src/assets/img/block4.png rename to web/src/assets/img/index/block4.png diff --git a/web/src/assets/img/split.png b/web/src/assets/img/index/split.png similarity index 100% rename from web/src/assets/img/split.png rename to web/src/assets/img/index/split.png diff --git a/web/src/assets/img/logicalStructure.jpg b/web/src/assets/img/logicalStructure.jpg deleted file mode 100644 index 998922a4..00000000 Binary files a/web/src/assets/img/logicalStructure.jpg and /dev/null differ diff --git a/web/src/assets/img/mindMap.jpg b/web/src/assets/img/mindMap.jpg deleted file mode 100644 index 9a35f185..00000000 Binary files a/web/src/assets/img/mindMap.jpg and /dev/null differ diff --git a/web/src/assets/img/organizationStructure.jpg b/web/src/assets/img/organizationStructure.jpg deleted file mode 100644 index 0159e807..00000000 Binary files a/web/src/assets/img/organizationStructure.jpg and /dev/null differ diff --git a/web/src/assets/img/catalogOrganization.png b/web/src/assets/img/structures/catalogOrganization.png similarity index 100% rename from web/src/assets/img/catalogOrganization.png rename to web/src/assets/img/structures/catalogOrganization.png diff --git a/web/src/assets/img/fishbone.png b/web/src/assets/img/structures/fishbone.png similarity index 100% rename from web/src/assets/img/fishbone.png rename to web/src/assets/img/structures/fishbone.png diff --git a/web/src/assets/img/logicalStructure.png b/web/src/assets/img/structures/logicalStructure.png similarity index 100% rename from web/src/assets/img/logicalStructure.png rename to web/src/assets/img/structures/logicalStructure.png diff --git a/web/src/assets/img/mindMap.png b/web/src/assets/img/structures/mindMap.png similarity index 100% rename from web/src/assets/img/mindMap.png rename to web/src/assets/img/structures/mindMap.png diff --git a/web/src/assets/img/organizationStructure.png b/web/src/assets/img/structures/organizationStructure.png similarity index 100% rename from web/src/assets/img/organizationStructure.png rename to web/src/assets/img/structures/organizationStructure.png diff --git a/web/src/assets/img/timeline.png b/web/src/assets/img/structures/timeline.png similarity index 100% rename from web/src/assets/img/timeline.png rename to web/src/assets/img/structures/timeline.png diff --git a/web/src/assets/img/timeline2.png b/web/src/assets/img/structures/timeline2.png similarity index 100% rename from web/src/assets/img/timeline2.png rename to web/src/assets/img/structures/timeline2.png diff --git a/web/src/assets/img/verticalTimeline.png b/web/src/assets/img/structures/verticalTimeline.png similarity index 100% rename from web/src/assets/img/verticalTimeline.png rename to web/src/assets/img/structures/verticalTimeline.png diff --git a/web/src/assets/img/autumn.jpg b/web/src/assets/img/themes/autumn.jpg similarity index 100% rename from web/src/assets/img/autumn.jpg rename to web/src/assets/img/themes/autumn.jpg diff --git a/web/src/assets/img/avocado.jpg b/web/src/assets/img/themes/avocado.jpg similarity index 100% rename from web/src/assets/img/avocado.jpg rename to web/src/assets/img/themes/avocado.jpg diff --git a/web/src/assets/img/blackGold.jpg b/web/src/assets/img/themes/blackGold.jpg similarity index 100% rename from web/src/assets/img/blackGold.jpg rename to web/src/assets/img/themes/blackGold.jpg diff --git a/web/src/assets/img/blackHumour.jpg b/web/src/assets/img/themes/blackHumour.jpg similarity index 100% rename from web/src/assets/img/blackHumour.jpg rename to web/src/assets/img/themes/blackHumour.jpg diff --git a/web/src/assets/img/blueSky.jpg b/web/src/assets/img/themes/blueSky.jpg similarity index 100% rename from web/src/assets/img/blueSky.jpg rename to web/src/assets/img/themes/blueSky.jpg diff --git a/web/src/assets/img/brainImpairedPink.jpg b/web/src/assets/img/themes/brainImpairedPink.jpg similarity index 100% rename from web/src/assets/img/brainImpairedPink.jpg rename to web/src/assets/img/themes/brainImpairedPink.jpg diff --git a/web/src/assets/img/classic.jpg b/web/src/assets/img/themes/classic.jpg similarity index 100% rename from web/src/assets/img/classic.jpg rename to web/src/assets/img/themes/classic.jpg diff --git a/web/src/assets/img/classic2.jpg b/web/src/assets/img/themes/classic2.jpg similarity index 100% rename from web/src/assets/img/classic2.jpg rename to web/src/assets/img/themes/classic2.jpg diff --git a/web/src/assets/img/classic3.jpg b/web/src/assets/img/themes/classic3.jpg similarity index 100% rename from web/src/assets/img/classic3.jpg rename to web/src/assets/img/themes/classic3.jpg diff --git a/web/src/assets/img/classic4.jpg b/web/src/assets/img/themes/classic4.jpg similarity index 100% rename from web/src/assets/img/classic4.jpg rename to web/src/assets/img/themes/classic4.jpg diff --git a/web/src/assets/img/classicBlue.jpg b/web/src/assets/img/themes/classicBlue.jpg similarity index 100% rename from web/src/assets/img/classicBlue.jpg rename to web/src/assets/img/themes/classicBlue.jpg diff --git a/web/src/assets/img/classicGreen.jpg b/web/src/assets/img/themes/classicGreen.jpg similarity index 100% rename from web/src/assets/img/classicGreen.jpg rename to web/src/assets/img/themes/classicGreen.jpg diff --git a/web/src/assets/img/coffee.jpg b/web/src/assets/img/themes/coffee.jpg similarity index 100% rename from web/src/assets/img/coffee.jpg rename to web/src/assets/img/themes/coffee.jpg diff --git a/web/src/assets/img/courseGreen.jpg b/web/src/assets/img/themes/courseGreen.jpg similarity index 100% rename from web/src/assets/img/courseGreen.jpg rename to web/src/assets/img/themes/courseGreen.jpg diff --git a/web/src/assets/img/dark.jpg b/web/src/assets/img/themes/dark.jpg similarity index 100% rename from web/src/assets/img/dark.jpg rename to web/src/assets/img/themes/dark.jpg diff --git a/web/src/assets/img/dark2.jpg b/web/src/assets/img/themes/dark2.jpg similarity index 100% rename from web/src/assets/img/dark2.jpg rename to web/src/assets/img/themes/dark2.jpg diff --git a/web/src/assets/img/darkNightLceBlade.jpg b/web/src/assets/img/themes/darkNightLceBlade.jpg similarity index 100% rename from web/src/assets/img/darkNightLceBlade.jpg rename to web/src/assets/img/themes/darkNightLceBlade.jpg diff --git a/web/src/assets/img/default.jpg b/web/src/assets/img/themes/default.jpg similarity index 100% rename from web/src/assets/img/default.jpg rename to web/src/assets/img/themes/default.jpg diff --git a/web/src/assets/img/earthYellow.jpg b/web/src/assets/img/themes/earthYellow.jpg similarity index 100% rename from web/src/assets/img/earthYellow.jpg rename to web/src/assets/img/themes/earthYellow.jpg diff --git a/web/src/assets/img/freshGreen.jpg b/web/src/assets/img/themes/freshGreen.jpg similarity index 100% rename from web/src/assets/img/freshGreen.jpg rename to web/src/assets/img/themes/freshGreen.jpg diff --git a/web/src/assets/img/freshRed.jpg b/web/src/assets/img/themes/freshRed.jpg similarity index 100% rename from web/src/assets/img/freshRed.jpg rename to web/src/assets/img/themes/freshRed.jpg diff --git a/web/src/assets/img/gold.jpg b/web/src/assets/img/themes/gold.jpg similarity index 100% rename from web/src/assets/img/gold.jpg rename to web/src/assets/img/themes/gold.jpg diff --git a/web/src/assets/img/greenLeaf.jpg b/web/src/assets/img/themes/greenLeaf.jpg similarity index 100% rename from web/src/assets/img/greenLeaf.jpg rename to web/src/assets/img/themes/greenLeaf.jpg diff --git a/web/src/assets/img/lateNightOffice.jpg b/web/src/assets/img/themes/lateNightOffice.jpg similarity index 100% rename from web/src/assets/img/lateNightOffice.jpg rename to web/src/assets/img/themes/lateNightOffice.jpg diff --git a/web/src/assets/img/lemonBubbles.jpg b/web/src/assets/img/themes/lemonBubbles.jpg similarity index 100% rename from web/src/assets/img/lemonBubbles.jpg rename to web/src/assets/img/themes/lemonBubbles.jpg diff --git a/web/src/assets/img/minions.jpg b/web/src/assets/img/themes/minions.jpg similarity index 100% rename from web/src/assets/img/minions.jpg rename to web/src/assets/img/themes/minions.jpg diff --git a/web/src/assets/img/mint.jpg b/web/src/assets/img/themes/mint.jpg similarity index 100% rename from web/src/assets/img/mint.jpg rename to web/src/assets/img/themes/mint.jpg diff --git a/web/src/assets/img/morandi.jpg b/web/src/assets/img/themes/morandi.jpg similarity index 100% rename from web/src/assets/img/morandi.jpg rename to web/src/assets/img/themes/morandi.jpg diff --git a/web/src/assets/img/neonLamp.jpg b/web/src/assets/img/themes/neonLamp.jpg similarity index 100% rename from web/src/assets/img/neonLamp.jpg rename to web/src/assets/img/themes/neonLamp.jpg diff --git a/web/src/assets/img/orangeJuice.jpg b/web/src/assets/img/themes/orangeJuice.jpg similarity index 100% rename from web/src/assets/img/orangeJuice.jpg rename to web/src/assets/img/themes/orangeJuice.jpg diff --git a/web/src/assets/img/oreo.jpg b/web/src/assets/img/themes/oreo.jpg similarity index 100% rename from web/src/assets/img/oreo.jpg rename to web/src/assets/img/themes/oreo.jpg diff --git a/web/src/assets/img/pinkGrape.jpg b/web/src/assets/img/themes/pinkGrape.jpg similarity index 100% rename from web/src/assets/img/pinkGrape.jpg rename to web/src/assets/img/themes/pinkGrape.jpg diff --git a/web/src/assets/img/redSpirit.jpg b/web/src/assets/img/themes/redSpirit.jpg similarity index 100% rename from web/src/assets/img/redSpirit.jpg rename to web/src/assets/img/themes/redSpirit.jpg diff --git a/web/src/assets/img/romanticPurple.jpg b/web/src/assets/img/themes/romanticPurple.jpg similarity index 100% rename from web/src/assets/img/romanticPurple.jpg rename to web/src/assets/img/themes/romanticPurple.jpg diff --git a/web/src/assets/img/rose.jpg b/web/src/assets/img/themes/rose.jpg similarity index 100% rename from web/src/assets/img/rose.jpg rename to web/src/assets/img/themes/rose.jpg diff --git a/web/src/assets/img/seaBlueLine.jpg b/web/src/assets/img/themes/seaBlueLine.jpg similarity index 100% rename from web/src/assets/img/seaBlueLine.jpg rename to web/src/assets/img/themes/seaBlueLine.jpg diff --git a/web/src/assets/img/shallowSea.jpg b/web/src/assets/img/themes/shallowSea.jpg similarity index 100% rename from web/src/assets/img/shallowSea.jpg rename to web/src/assets/img/themes/shallowSea.jpg diff --git a/web/src/assets/img/simpleBlack.jpg b/web/src/assets/img/themes/simpleBlack.jpg similarity index 100% rename from web/src/assets/img/simpleBlack.jpg rename to web/src/assets/img/themes/simpleBlack.jpg diff --git a/web/src/assets/img/skyGreen.jpg b/web/src/assets/img/themes/skyGreen.jpg similarity index 100% rename from web/src/assets/img/skyGreen.jpg rename to web/src/assets/img/themes/skyGreen.jpg diff --git a/web/src/assets/img/vitalityOrange.jpg b/web/src/assets/img/themes/vitalityOrange.jpg similarity index 100% rename from web/src/assets/img/vitalityOrange.jpg rename to web/src/assets/img/themes/vitalityOrange.jpg diff --git a/web/src/assets/img/timeline.jpg b/web/src/assets/img/timeline.jpg deleted file mode 100644 index 06cd682a..00000000 Binary files a/web/src/assets/img/timeline.jpg and /dev/null differ diff --git a/web/src/assets/img/timeline2.jpg b/web/src/assets/img/timeline2.jpg deleted file mode 100644 index 4d0019e5..00000000 Binary files a/web/src/assets/img/timeline2.jpg and /dev/null differ diff --git a/web/src/assets/img/verticalTimeline.jpg b/web/src/assets/img/verticalTimeline.jpg deleted file mode 100644 index 5fc5d353..00000000 Binary files a/web/src/assets/img/verticalTimeline.jpg and /dev/null differ diff --git a/web/src/config/constant.js b/web/src/config/constant.js index a2e0e0e7..897bf38e 100644 --- a/web/src/config/constant.js +++ b/web/src/config/constant.js @@ -1,56 +1,56 @@ // 布局结构图片映射 export const layoutImgMap = { - logicalStructure: require('../assets/img/logicalStructure.png'), - mindMap: require('../assets/img/mindMap.png'), - organizationStructure: require('../assets/img/organizationStructure.png'), - catalogOrganization: require('../assets/img/catalogOrganization.png'), - timeline: require('../assets/img/timeline.png'), - timeline2: require('../assets/img/timeline2.png'), - fishbone: require('../assets/img/fishbone.png'), - verticalTimeline: require('../assets/img/verticalTimeline.png'), + logicalStructure: require('../assets/img/structures/logicalStructure.png'), + mindMap: require('../assets/img/structures/mindMap.png'), + organizationStructure: require('../assets/img/structures/organizationStructure.png'), + catalogOrganization: require('../assets/img/structures/catalogOrganization.png'), + timeline: require('../assets/img/structures/timeline.png'), + timeline2: require('../assets/img/structures/timeline2.png'), + fishbone: require('../assets/img/structures/fishbone.png'), + verticalTimeline: require('../assets/img/structures/verticalTimeline.png'), } // 主题图片映射 export const themeMap = { - default: require('../assets/img/default.jpg'), - classic: require('../assets/img/classic.jpg'), - minions: require('../assets/img/minions.jpg'), - pinkGrape: require('../assets/img/pinkGrape.jpg'), - mint: require('../assets/img/mint.jpg'), - gold: require('../assets/img/gold.jpg'), - vitalityOrange: require('../assets/img/vitalityOrange.jpg'), - greenLeaf: require('../assets/img/greenLeaf.jpg'), - dark2: require('../assets/img/dark2.jpg'), - skyGreen: require('../assets/img/skyGreen.jpg'), - classic2: require('../assets/img/classic2.jpg'), - classic3: require('../assets/img/classic3.jpg'), - classic4: require('../assets/img/classic4.jpg'), - classicGreen: require('../assets/img/classicGreen.jpg'), - classicBlue: require('../assets/img/classicBlue.jpg'), - blueSky: require('../assets/img/blueSky.jpg'), - brainImpairedPink: require('../assets/img/brainImpairedPink.jpg'), - dark: require('../assets/img/dark.jpg'), - earthYellow: require('../assets/img/earthYellow.jpg'), - freshGreen: require('../assets/img/freshGreen.jpg'), - freshRed: require('../assets/img/freshRed.jpg'), - romanticPurple: require('../assets/img/romanticPurple.jpg'), - simpleBlack: require('../assets/img/simpleBlack.jpg'), - courseGreen: require('../assets/img/courseGreen.jpg'), - coffee: require('../assets/img/coffee.jpg'), - redSpirit: require('../assets/img/redSpirit.jpg'), - blackHumour: require('../assets/img/blackHumour.jpg'), - lateNightOffice: require('../assets/img/lateNightOffice.jpg'), - blackGold: require('../assets/img/blackGold.jpg'), - autumn: require('../assets/img/autumn.jpg'), - avocado: require('../assets/img/avocado.jpg'), - orangeJuice: require('../assets/img/orangeJuice.jpg'), - oreo: require('../assets/img/oreo.jpg'), - shallowSea: require('../assets/img/shallowSea.jpg'), - lemonBubbles: require('../assets/img/lemonBubbles.jpg'), - rose: require('../assets/img/rose.jpg'), - seaBlueLine: require('../assets/img/seaBlueLine.jpg'), - neonLamp: require('../assets/img/neonLamp.jpg'), - darkNightLceBlade: require('../assets/img/darkNightLceBlade.jpg'), - morandi: require('../assets/img/morandi.jpg'), + default: require('../assets/img/themes/default.jpg'), + classic: require('../assets/img/themes/classic.jpg'), + minions: require('../assets/img/themes/minions.jpg'), + pinkGrape: require('../assets/img/themes/pinkGrape.jpg'), + mint: require('../assets/img/themes/mint.jpg'), + gold: require('../assets/img/themes/gold.jpg'), + vitalityOrange: require('../assets/img/themes/vitalityOrange.jpg'), + greenLeaf: require('../assets/img/themes/greenLeaf.jpg'), + dark2: require('../assets/img/themes/dark2.jpg'), + skyGreen: require('../assets/img/themes/skyGreen.jpg'), + classic2: require('../assets/img/themes/classic2.jpg'), + classic3: require('../assets/img/themes/classic3.jpg'), + classic4: require('../assets/img/themes/classic4.jpg'), + classicGreen: require('../assets/img/themes/classicGreen.jpg'), + classicBlue: require('../assets/img/themes/classicBlue.jpg'), + blueSky: require('../assets/img/themes/blueSky.jpg'), + brainImpairedPink: require('../assets/img/themes/brainImpairedPink.jpg'), + dark: require('../assets/img/themes/dark.jpg'), + earthYellow: require('../assets/img/themes/earthYellow.jpg'), + freshGreen: require('../assets/img/themes/freshGreen.jpg'), + freshRed: require('../assets/img/themes/freshRed.jpg'), + romanticPurple: require('../assets/img/themes/romanticPurple.jpg'), + simpleBlack: require('../assets/img/themes/simpleBlack.jpg'), + courseGreen: require('../assets/img/themes/courseGreen.jpg'), + coffee: require('../assets/img/themes/coffee.jpg'), + redSpirit: require('../assets/img/themes/redSpirit.jpg'), + blackHumour: require('../assets/img/themes/blackHumour.jpg'), + lateNightOffice: require('../assets/img/themes/lateNightOffice.jpg'), + blackGold: require('../assets/img/themes/blackGold.jpg'), + autumn: require('../assets/img/themes/autumn.jpg'), + avocado: require('../assets/img/themes/avocado.jpg'), + orangeJuice: require('../assets/img/themes/orangeJuice.jpg'), + oreo: require('../assets/img/themes/oreo.jpg'), + shallowSea: require('../assets/img/themes/shallowSea.jpg'), + lemonBubbles: require('../assets/img/themes/lemonBubbles.jpg'), + rose: require('../assets/img/themes/rose.jpg'), + seaBlueLine: require('../assets/img/themes/seaBlueLine.jpg'), + neonLamp: require('../assets/img/themes/neonLamp.jpg'), + darkNightLceBlade: require('../assets/img/themes/darkNightLceBlade.jpg'), + morandi: require('../assets/img/themes/morandi.jpg'), } \ No newline at end of file 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/electron/fileHandle.js b/web/src/electron/fileHandle.js index 15d39c21..32dad3c8 100644 --- a/web/src/electron/fileHandle.js +++ b/web/src/electron/fileHandle.js @@ -50,7 +50,7 @@ export const bindFileHandleEvent = ({ mainWindow, initOpenFileQueue }) => { win.loadURL( process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche/edit/' + id ) - if (!process.env.IS_TEST) win.webContents.openDevTools() + // if (!process.env.IS_TEST) win.webContents.openDevTools() } else { // Load the index.html when not in development win.loadURL('app://./index.html/#/workbenche/edit/' + id) diff --git a/web/src/lang/en_us.js b/web/src/lang/en_us.js index f0fdc10e..046bc966 100644 --- a/web/src/lang/en_us.js +++ b/web/src/lang/en_us.js @@ -46,7 +46,11 @@ 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', + associativeLineText: 'Associative line text', + fontFamily: 'Font family', + fontSize: 'Font size' }, color: { moreColor: 'More color' @@ -203,7 +207,8 @@ export default { export: 'Export', shortcutKey: 'Shortcut key', associativeLine: 'Associative line', - save: 'Save' + save: 'Save', + painter: 'Painter' }, edit: { newFeatureNoticeTitle: 'New feature reminder', diff --git a/web/src/lang/zh_cn.js b/web/src/lang/zh_cn.js index 63ec39ac..d017ca9c 100644 --- a/web/src/lang/zh_cn.js +++ b/web/src/lang/zh_cn.js @@ -46,7 +46,11 @@ export default { associativeLineActiveColor: '激活颜色', mousewheelZoomActionReverse: '鼠标滚轮缩放', mousewheelZoomActionReverse1: '向前缩小向后放大', - mousewheelZoomActionReverse2: '向前放大向后缩小' + mousewheelZoomActionReverse2: '向前放大向后缩小', + rootStyle: '根节点', + associativeLineText: '关联线文字', + fontFamily: '字体', + fontSize: '字号' }, color: { moreColor: '更多颜色' @@ -203,7 +207,8 @@ export default { export: '导出', shortcutKey: '快捷键', associativeLine: '关联线', - save: '保存' + save: '保存', + painter: '格式刷' }, edit: { newFeatureNoticeTitle: '新特性提醒', diff --git a/web/src/pages/Doc/catalogList.js b/web/src/pages/Doc/catalogList.js index 7aca8d52..b14774f4 100644 --- a/web/src/pages/Doc/catalogList.js +++ b/web/src/pages/Doc/catalogList.js @@ -11,7 +11,7 @@ let langList = [ } ] let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog'] -let CourseList = new Array(21).fill(0).map((_, index) => { +let CourseList = new Array(22).fill(0).map((_, index) => { return 'course' + (index + 1) }) let APIList = [ @@ -33,6 +33,7 @@ let APIList = [ 'touchEvent', 'nodeImgAdjust', 'search', + 'painter', 'xmind', 'markdown', 'utils' diff --git a/web/src/pages/Doc/en/changelog/index.md b/web/src/pages/Doc/en/changelog/index.md index 8de9b46f..9d59c2bc 100644 --- a/web/src/pages/Doc/en/changelog/index.md +++ b/web/src/pages/Doc/en/changelog/index.md @@ -1,5 +1,81 @@ # Changelog +## 0.6.13 + +Fix: + +> 1.Fix the issue of the inability to drag the canvas while holding down the middle mouse button on a node in read-only mode. +> +> 2.Fixed the issue of probabilistic error reporting after quickly dragging nodes several times. +> +> 3.Fix the issue of pulling up the input method during operations such as activating nodes on the mobile end, expanding and collapsing. +> +> 4.Fix the issue where an exception request is initiated when the background image in the theme configuration is none. + +New: + +> 1.Mobile gesture scaling optimization: Scale according to a linear relationship, and adjust the canvas position with double finger displacement. +> +> 2.Remove the logic of asynchronous rendering nodes and improve the speed of creating new nodes. +> +> 3.The export of images has been changed from the html2canvas library to the dom to image more library to address the issue of missing text styles in exporting rich text nodes. +> +> 4.When a non rich text input box enters the editing state, it is deselected by default. +> +> 5.When there is an activation node, it supports automatically entering text editing mode when pressing the Chinese, numeric, or English buttons. + +Demo: + +> 1.Add anti shake operations when saving view data to optimize performance. +> +> 2.Some time-consuming operations add loading effects. +> +> 3.Improve the dark mode of right-click menus and rich text toolbars. + +## 0.6.12 + +Fix: + +> 1.Fix the issue where the indicator in the mini map will also move out of the mini map area when the mind map is completely moved out of the visible area. +> +> 2.Fix the issue of overly sensitive dual finger scaling on the mobile end. +> +> 3.Fix the issue of holding down nodes while dragging the canvas in read-only mode. +> +> 4.Fix the issue of incorrect rendering of the mini map when the distance between the mind map and the top left corner of the browser window is not 0. +> +> 5.Fix the issue of the prompt block being too large for the new location when moving nodes. +> +> 6.Fix the issue where search cannot be replaced with empty characters. +> +> 7.Fixed the issue of missing line breaks after searching and replacing in rich text mode. +> +> 8.Fixed the issue of missing focus in the input box when clicking on text editing in the outline. + +New: + +> 1.Adding a callback parameter to the node move end event (node_drag) can obtain the uid of the move to the node. +> +> 2.Support specifying the location to which internal elements are added through configuration. +> +> 3.Support the format brush function. +> +> 4.Under the curve style, the connection line style of the root node supports consistency with other nodes. +> +> 5.Search supports continuous replacement. +> +> 6.Add and delete button for node image. +> +> 7.Support dragging the canvas while holding down the middle mouse button. + +Demo: + +> 1.Provide an application takeover mode to facilitate docking with one's own storage services; Supports setting static resource paths at runtime. +> +> 2.Refactoring outline: 1. No longer use the text style that comes with the node; 2. Support full screen editing of the outline; 3. The outline supports dragging and moving nodes; 4. The outline supports deleting nodes. +> +> 3.Fix the issue of interface dark mode not updating in the scenario of importing data. + ## 0.6.11-fix.1 Fix: 1.Fixed the issue of invisible editing when node text is white. diff --git a/web/src/pages/Doc/en/changelog/index.vue b/web/src/pages/Doc/en/changelog/index.vue index 1b919758..a8d90a51 100644 --- a/web/src/pages/Doc/en/changelog/index.vue +++ b/web/src/pages/Doc/en/changelog/index.vue @@ -1,6 +1,56 @@ diff --git a/web/src/pages/Doc/en/keyCommand/index.md b/web/src/pages/Doc/en/keyCommand/index.md index 2ea06cc9..fb4b4753 100644 --- a/web/src/pages/Doc/en/keyCommand/index.md +++ b/web/src/pages/Doc/en/keyCommand/index.md @@ -58,4 +58,12 @@ Save the current registered shortcut data, then clear the shortcut data > v0.2.3+ -Restore saved shortcut data, then clear the cache data \ No newline at end of file +Restore saved shortcut data, then clear the cache data + +### hasCombinationKey(e) + +> v0.6.13+ + +- `e`: Event object. + +Determine if the combination key has been pressed. \ No newline at end of file diff --git a/web/src/pages/Doc/en/keyCommand/index.vue b/web/src/pages/Doc/en/keyCommand/index.vue index 48d83d0d..748b10f1 100644 --- a/web/src/pages/Doc/en/keyCommand/index.vue +++ b/web/src/pages/Doc/en/keyCommand/index.vue @@ -46,6 +46,14 @@ the shortcut will be removed

v0.2.3+

Restore saved shortcut data, then clear the cache data

+

hasCombinationKey(e)

+
+

v0.6.13+

+
+ +

Determine if the combination key has been pressed.

diff --git a/web/src/pages/Doc/en/node/index.md b/web/src/pages/Doc/en/node/index.md index 0a0d9deb..74d54d3a 100644 --- a/web/src/pages/Doc/en/node/index.md +++ b/web/src/pages/Doc/en/node/index.md @@ -117,6 +117,12 @@ default `false` Modify a style of the node, a shortcut method for the `SET_NODE_STYLE` command +### setStyles(style, isActive) + +> v0.6.12+ + +Modify multiple styles of nodes, a shortcut method for the `SET_NODE_STYLES` command + ### getData(key) Get the specified value in the `data` object of the node's real data `nodeData`, diff --git a/web/src/pages/Doc/en/node/index.vue b/web/src/pages/Doc/en/node/index.vue index e892c349..eb479570 100644 --- a/web/src/pages/Doc/en/node/index.vue +++ b/web/src/pages/Doc/en/node/index.vue @@ -68,6 +68,11 @@ default false

setStyle(prop, value, isActive)

Modify a style of the node, a shortcut method for the SET_NODE_STYLE command

+

setStyles(style, isActive)

+
+

v0.6.12+

+
+

Modify multiple styles of nodes, a shortcut method for the SET_NODE_STYLES command

getData(key)

Get the specified value in the data object of the node's real data nodeData, if key is not passed, return the data object

diff --git a/web/src/pages/Doc/en/painter/index.md b/web/src/pages/Doc/en/painter/index.md new file mode 100644 index 00000000..a5d17b72 --- /dev/null +++ b/web/src/pages/Doc/en/painter/index.md @@ -0,0 +1,35 @@ +# Painter plugin + +> v0.6.12+ + +Node format brush plugin. + +## Register + +```js +import MindMap from 'simple-mind-map' +import Painter from 'simple-mind-map/src/plugins/Painter.js' +MindMap.usePlugin(Painter) +``` + +After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.painter`. + +## Event + +> You can use mindMap.on('event name', () => {}) method to listen events. + +### painter_start + +The event of painter start. + +### painter_end + +The event of painter end. + +## Method + +### startPainter() + +Start painter. + +After calling this method, if there is currently an active node, the first active node will be taken as the specified node by default. After clicking on other nodes, the style of that node will be applied to the other nodes being clicked. When clicking on the canvas, the format brushing operation ends. \ No newline at end of file diff --git a/web/src/pages/Doc/en/painter/index.vue b/web/src/pages/Doc/en/painter/index.vue new file mode 100644 index 00000000..e5bd963b --- /dev/null +++ b/web/src/pages/Doc/en/painter/index.vue @@ -0,0 +1,38 @@ + + + + + \ No newline at end of file diff --git a/web/src/pages/Doc/en/richText/index.md b/web/src/pages/Doc/en/richText/index.md index 2223ce35..b0579ff4 100644 --- a/web/src/pages/Doc/en/richText/index.md +++ b/web/src/pages/Doc/en/richText/index.md @@ -14,7 +14,9 @@ The principle of this plugin is to use [Quill](https://github.com/quilljs/quill) > > This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting `svg` as an image is very simple, Get the `svg` string, and then create the `blob` data of the `type=image/svg+xml` type. Then use the `URL.createObjectURL` method to generate the `data:url` data. Then create a `Image` tag, use the `data:url` as the `src` of the image, and finally draw the image on the `canvas` object for export, However, after testing, when the `DOM` node is embedded in the `svg`, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the `foreignObject` node in `svg`, using [html2canvas](https://github.com/niklasvh/html2canvas) Convert the `DOM` node in the `foreignObject` node into an image and then replace the `foreignObject` node. This method can work, but it is very time-consuming. Because the `html2canvas` conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export. -The version of `v0.5.7+` directly uses `html2canvas` to convert the entire `svg`, which is no longer an issue with speed. However, there is currently a bug where the color of the node does not take effect after export. +> The version of `v0.5.7+` directly uses `html2canvas` to convert the entire `svg`, which is no longer an issue with speed. However, there is currently a bug where the color of the node does not take effect after export. + +`V0.6.13+` version uses [dom-to-image-more](https://github.com/1904labs/dom-to-image-more) Replaced 'html2canvas' to address the issue of ineffective color export for nodes. ## Register diff --git a/web/src/pages/Doc/en/richText/index.vue b/web/src/pages/Doc/en/richText/index.vue index 7b645bd8..9b39eb20 100644 --- a/web/src/pages/Doc/en/richText/index.vue +++ b/web/src/pages/Doc/en/richText/index.vue @@ -14,7 +14,10 @@

The following prompts exist in versions prior to v0.5.6:

This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting svg as an image is very simple, Get the svg string, and then create the blob data of the type=image/svg+xml type. Then use the URL.createObjectURL method to generate the data:url data. Then create a Image tag, use the data:url as the src of the image, and finally draw the image on the canvas object for export, However, after testing, when the DOM node is embedded in the svg, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the foreignObject node in svg, using html2canvas Convert the DOM node in the foreignObject node into an image and then replace the foreignObject node. This method can work, but it is very time-consuming. Because the html2canvas conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.

+

The version of v0.5.7+ directly uses html2canvas to convert the entire svg, which is no longer an issue with speed. However, there is currently a bug where the color of the node does not take effect after export.

+
+

V0.6.13+ version uses dom-to-image-more Replaced 'html2canvas' to address the issue of ineffective color export for nodes.

Register

import MindMap from 'simple-mind-map'
 import RichText from 'simple-mind-map/src/plugins/RichText.js'
diff --git a/web/src/pages/Doc/en/search/index.md b/web/src/pages/Doc/en/search/index.md
index 9bb87174..215917f6 100644
--- a/web/src/pages/Doc/en/search/index.md
+++ b/web/src/pages/Doc/en/search/index.md
@@ -45,10 +45,12 @@ Search for node content, which can be called repeatedly. Each call will search a
 
 End search.
 
-### replace(replaceText)
+### replace(replaceText, jumpNext = false)
 
 - `replaceText`: Text to be replaced
 
+- `jumpNext`: v0.6.12+, Whether to automatically jump to the next matching node
+
 To replace the content of the current node, call the 'search' method after calling it to replace the content of the currently located matching node.
 
 ### replaceAll(replaceText)
diff --git a/web/src/pages/Doc/en/search/index.vue b/web/src/pages/Doc/en/search/index.vue
index 600bcaf8..c47b9d40 100644
--- a/web/src/pages/Doc/en/search/index.vue
+++ b/web/src/pages/Doc/en/search/index.vue
@@ -36,9 +36,14 @@ MindMap.usePlugin(Search)
 

Search for node content, which can be called repeatedly. Each call will search and locate to the next matching node. If the search text changes, it will be searched again.

endSearch()

End search.

-

replace(replaceText)

+

replace(replaceText, jumpNext = false)

To replace the content of the current node, call the 'search' method after calling it to replace the content of the currently located matching node.

replaceAll(replaceText)

diff --git a/web/src/pages/Doc/en/utils/index.md b/web/src/pages/Doc/en/utils/index.md index 71bb0f58..f259d80c 100644 --- a/web/src/pages/Doc/en/utils/index.md +++ b/web/src/pages/Doc/en/utils/index.md @@ -228,8 +228,28 @@ Determine whether a color is white. #### isTransparent(color) +> v0.6.11+ + Determine whether a color is transparent. +#### nodeRichTextToTextWithWrap(html) + +> v0.6.12+ + +Convert the rich text content of nodes in the form of `

` into text wrapped in `\n`. + +#### textToNodeRichTextWithWrap(html) + +> v0.6.12+ + +Convert the wrapped text of `
` into node rich text content in the form of `

`. + +#### isMobile() + +> v0.6.13+ + +Determine if it is a mobile environment. + ## Simulate CSS background in Canvas Import: diff --git a/web/src/pages/Doc/en/utils/index.vue b/web/src/pages/Doc/en/utils/index.vue index 1fa4504b..ce892c8b 100644 --- a/web/src/pages/Doc/en/utils/index.vue +++ b/web/src/pages/Doc/en/utils/index.vue @@ -160,7 +160,25 @@ and copying the data of the data object, example:

Determine whether a color is white.

isTransparent(color)

+
+

v0.6.11+

+

Determine whether a color is transparent.

+

nodeRichTextToTextWithWrap(html)

+
+

v0.6.12+

+
+

Convert the rich text content of nodes in the form of <p><span></span><p> into text wrapped in \n.

+

textToNodeRichTextWithWrap(html)

+
+

v0.6.12+

+
+

Convert the wrapped text of <br> into node rich text content in the form of <p><span></span><p>.

+

isMobile()

+
+

v0.6.13+

+
+

Determine if it is a mobile environment.

Simulate CSS background in Canvas

Import:

import drawBackgroundImageToCanvas from 'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'
diff --git a/web/src/pages/Doc/routerList.js b/web/src/pages/Doc/routerList.js
index 7cd50ec5..578b9aa2 100644
--- a/web/src/pages/Doc/routerList.js
+++ b/web/src/pages/Doc/routerList.js
@@ -1,3 +1,91 @@
-
-        export default [{"lang":"zh","children":[{"path":"associativeLine","title":"AssociativeLine 插件"},{"path":"batchExecution","title":"BatchExecution实例"},{"path":"changelog","title":"Changelog"},{"path":"client","title":"客户端"},{"path":"command","title":"Command实例"},{"path":"constructor","title":"构造函数"},{"path":"course1","title":"基本使用"},{"path":"course10","title":"主题"},{"path":"course11","title":"结构"},{"path":"course12","title":"如何渲染一个大纲"},{"path":"course13","title":"快捷键"},{"path":"course14","title":"如何渲染一个小地图"},{"path":"course15","title":"如何渲染一个右键菜单"},{"path":"course16","title":"如何渲染富文本的悬浮工具栏"},{"path":"course17","title":"导入和导出"},{"path":"course18","title":"如何持久化数据"},{"path":"course19","title":"插入和扩展节点图标"},{"path":"course2","title":"操作节点内容"},{"path":"course20","title":"如何自定义节点内容"},{"path":"course21","title":"如何复制、剪切、粘贴"},{"path":"course3","title":"插入/删除节点、前进回退"},{"path":"course4","title":"设置节点样式"},{"path":"course5","title":"设置基础样式"},{"path":"course6","title":"显示水印"},{"path":"course7","title":"开启节点自由拖拽"},{"path":"course8","title":"开启节点富文本编辑"},{"path":"course9","title":"修改鼠标滚轮的行为"},{"path":"deploy","title":"部署"},{"path":"doExport","title":"Export 插件"},{"path":"drag","title":"Drag插件"},{"path":"help1","title":"概要/关联线"},{"path":"help2","title":"客户端"},{"path":"introduction","title":"简介"},{"path":"keyCommand","title":"KeyCommand实例"},{"path":"keyboardNavigation","title":"KeyboardNavigation插件"},{"path":"markdown","title":"Markdown解析"},{"path":"miniMap","title":"MiniMap插件"},{"path":"node","title":"Node实例"},{"path":"nodeImgAdjust","title":"NodeImgAdjust插件"},{"path":"render","title":"Render实例"},{"path":"richText","title":"RichText插件"},{"path":"search","title":"Search 插件"},{"path":"select","title":"Select 插件 "},{"path":"start","title":"开始"},{"path":"touchEvent","title":"TouchEvent插件"},{"path":"translate","title":"参与翻译"},{"path":"utils","title":"内置工具方法"},{"path":"view","title":"View实例"},{"path":"watermark","title":"Watermark插件"},{"path":"xmind","title":"XMind解析"}]},{"lang":"en","children":[{"path":"associativeLine","title":"AssociativeLine plugin"},{"path":"batchExecution","title":"batchExecution instance"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"command instance"},{"path":"constructor","title":"Constructor"},{"path":"deploy","title":"Deploy"},{"path":"doExport","title":"Export plugin"},{"path":"drag","title":"Drag plugin"},{"path":"introduction","title":"Introduction"},{"path":"keyCommand","title":"KeyCommand instance"},{"path":"keyboardNavigation","title":"KeyboardNavigation plugin"},{"path":"markdown","title":"Markdown parse"},{"path":"miniMap","title":"MiniMap plugin"},{"path":"node","title":"Node instance"},{"path":"nodeImgAdjust","title":"NodeImgAdjust plugin"},{"path":"render","title":"Render instance"},{"path":"richText","title":"RichText plugin"},{"path":"search","title":"Search plugin"},{"path":"select","title":"Select plugin"},{"path":"start","title":"Start"},{"path":"touchEvent","title":"TouchEvent plugin"},{"path":"translate","title":"Participate in translation"},{"path":"utils","title":"Utility Methods"},{"path":"view","title":"View instance"},{"path":"watermark","title":"Watermark plugin"},{"path":"xmind","title":"XMind parse"}]}]
-    
\ No newline at end of file
+export default [
+  {
+    lang: 'zh',
+    children: [
+      { path: 'associativeLine', title: 'AssociativeLine 插件' },
+      { path: 'batchExecution', title: 'BatchExecution实例' },
+      { path: 'changelog', title: 'Changelog' },
+      { path: 'command', title: 'Command实例' },
+      { path: 'constructor', title: '构造函数' },
+      { path: 'course1', title: '基本使用' },
+      { path: 'course2', title: '操作节点内容' },
+      { path: 'course3', title: '插入/删除节点、前进回退' },
+      { path: 'course4', title: '设置节点样式' },
+      { path: 'course5', title: '设置基础样式' },
+      { path: 'course6', title: '显示水印' },
+      { path: 'course7', title: '开启节点自由拖拽' },
+      { path: 'course8', title: '开启节点富文本编辑' },
+      { path: 'course9', title: '修改鼠标滚轮的行为' },
+      { path: 'course10', title: '主题' },
+      { path: 'course11', title: '结构' },
+      { path: 'course12', title: '如何渲染一个大纲' },
+      { path: 'course13', title: '快捷键' },
+      { path: 'course14', title: '如何渲染一个小地图' },
+      { path: 'course15', title: '如何渲染一个右键菜单' },
+      { path: 'course16', title: '如何渲染富文本的悬浮工具栏' },
+      { path: 'course17', title: '导入和导出' },
+      { path: 'course18', title: '如何持久化数据' },
+      { path: 'course19', title: '插入和扩展节点图标' },
+      { path: 'course20', title: '如何自定义节点内容' },
+      { path: 'course21', title: '如何复制、剪切、粘贴' },
+      { path: 'course22', title: '如何实现搜索、替换' },
+      { path: 'doExport', title: 'Export 插件' },
+      { path: 'drag', title: 'Drag插件' },
+      { path: 'introduction', title: '简介' },
+      { path: 'keyCommand', title: 'KeyCommand实例' },
+      { path: 'keyboardNavigation', title: 'KeyboardNavigation插件' },
+      { path: 'markdown', title: 'Markdown解析' },
+      { path: 'miniMap', title: 'MiniMap插件' },
+      { path: 'node', title: 'Node实例' },
+      { path: 'render', title: 'Render实例' },
+      { path: 'richText', title: 'RichText插件' },
+      { path: 'select', title: 'Select 插件 ' },
+      { path: 'start', title: '开始' },
+      { path: 'translate', title: '参与翻译' },
+      { path: 'utils', title: '内置工具方法' },
+      { path: 'view', title: 'View实例' },
+      { path: 'watermark', title: 'Watermark插件' },
+      { path: 'xmind', title: 'XMind解析' },
+      { path: 'deploy', title: '部署' },
+      { path: 'client', title: '客户端' },
+      { path: 'touchEvent', title: 'TouchEvent插件' },
+      { path: 'nodeImgAdjust', title: 'NodeImgAdjust插件' },
+      { path: 'search', title: 'Search插件' },
+      { path: 'painter', title: 'Painter插件' },
+      { path: 'help1', title: '概要/关联线' },
+      { path: 'help2', title: '客户端' }
+    ]
+  },
+  {
+    lang: 'en',
+    children: [
+      { path: 'associativeLine', title: 'AssociativeLine plugin' },
+      { path: 'batchExecution', title: 'batchExecution instance' },
+      { path: 'changelog', title: 'Changelog' },
+      { path: 'command', title: 'command instance' },
+      { path: 'constructor', title: 'Constructor' },
+      { path: 'doExport', title: 'Export plugin' },
+      { path: 'drag', title: 'Drag plugin' },
+      { path: 'introduction', title: 'Introduction' },
+      { path: 'keyCommand', title: 'KeyCommand instance' },
+      { path: 'keyboardNavigation', title: 'KeyboardNavigation plugin' },
+      { path: 'markdown', title: 'Markdown parse' },
+      { path: 'miniMap', title: 'MiniMap plugin' },
+      { path: 'node', title: 'Node instance' },
+      { path: 'render', title: 'Render instance' },
+      { path: 'richText', title: 'RichText plugin' },
+      { path: 'select', title: 'Select plugin' },
+      { path: 'start', title: 'Start' },
+      { path: 'translate', title: 'Participate in translation' },
+      { path: 'utils', title: 'Utility Methods' },
+      { path: 'view', title: 'View instance' },
+      { path: 'watermark', title: 'Watermark plugin' },
+      { path: 'xmind', title: 'XMind parse' },
+      { path: 'deploy', title: 'Deploy' },
+      { path: 'touchEvent', title: 'TouchEvent plugin' },
+      { path: 'nodeImgAdjust', title: 'NodeImgAdjust plugin' },
+      { path: 'search', title: 'Search plugin' },
+      { path: 'painter', title: 'Painter plugin' }
+    ]
+  }
+]
diff --git a/web/src/pages/Doc/zh/changelog/index.md b/web/src/pages/Doc/zh/changelog/index.md
index a4c8e34f..e5177e54 100644
--- a/web/src/pages/Doc/zh/changelog/index.md
+++ b/web/src/pages/Doc/zh/changelog/index.md
@@ -1,5 +1,81 @@
 # Changelog
 
+## 0.6.13
+
+修复:
+
+> 1.修复只读模式下鼠标中键按住节点无法拖动画布的问题。 
+>
+> 2.修复快速拖动节点几次后会概率性报错的问题。 
+>
+> 3.修复在移动端激活节点、展开收起时等操作时会拉起输入法的问题。
+>
+> 4.修复主题配置中背景图片为none时会发起一个异常请求的问题。
+
+新增:
+
+> 1.移动端手势缩放优化: 按线性关系进行缩放、双指位移可以调整画布位置。 
+>
+> 2.去掉异步渲染节点的逻辑,提升创建新节点的速度。 
+>
+> 3.导出图片由html2canvas库改为使用dom-to-image-more库,解决导出富文本节点文字样式丢失的问题。 
+>
+> 4.非富文本输入框进入编辑状态时取消默认全选。
+>
+> 5.存在一个激活节点时,支持按下中文、数字、英文按键时自动进入文本编辑模式。
+
+Demo:
+
+> 1.保存视图数据时增加防抖操作,优化性能。
+>
+> 2.一些耗时的操作添加loading效果。
+>
+> 3.完善右键菜单和富文本工具条的暗黑模式。
+
+## 0.6.12
+
+修复:
+  
+> 1.修复当思维导图全部移出可视区域后小地图中的指示器也会移出小地图区域的问题。
+>
+> 2.修复移动端双指缩放过于灵敏的问题。
+>
+> 3.修复只读模式下按住节点无法拖动画布的问题。
+>
+> 4.修复当思维导图距浏览器窗口左上角不为0时,小地图渲染不正确的问题。
+>
+> 5.修复移动节点时新位置的提示块过大的问题。
+>
+> 6.修复搜索不能替换为空字符的问题。
+>
+> 7.修复富文本模式下,搜索替换后换行会丢失的问题。
+>
+> 8.修复大纲里点击文字编辑时输入框焦点丢失的问题。
+
+新增:
+
+> 1.节点移动结束事件(node_dragend)增加回调参数,可以获取到移动到节点的uid。
+>
+> 2.支持通过配置指定内部一些元素添加到的位置。
+>
+> 3.支持格式刷功能。
+>
+> 4.曲线风格下,根节点的连接线样式支持和其他节点保持一致。
+>
+> 5.搜索支持连续替换。
+>
+> 6.节点图片新增删除按钮。
+>
+> 7.支持按住鼠标中键拖动画布。
+
+Demo:
+
+> 1.提供应用接管模式,方便对接自己的存储服务;支持运行时设置静态资源路径。
+>
+> 2.重构大纲:1.不再使用节点自带的文本样式;2.支持全屏编辑大纲;3.大纲支持拖拽移动节点;4.大纲支持删除节点。
+>
+> 3.修复导入数据场景下界面暗黑模式没有更新的问题。
+
 ## 0.6.11-fix.1
 
 修复:1.修复节点文字为白色时编辑的时候看不见的问题。
diff --git a/web/src/pages/Doc/zh/changelog/index.vue b/web/src/pages/Doc/zh/changelog/index.vue
index 659371c1..ec0170e3 100644
--- a/web/src/pages/Doc/zh/changelog/index.vue
+++ b/web/src/pages/Doc/zh/changelog/index.vue
@@ -1,6 +1,56 @@
 
diff --git a/web/src/pages/Doc/zh/course18/index.md b/web/src/pages/Doc/zh/course18/index.md
index dd0f3981..8f89fe9a 100644
--- a/web/src/pages/Doc/zh/course18/index.md
+++ b/web/src/pages/Doc/zh/course18/index.md
@@ -1,5 +1,7 @@
 # 如何持久化数据
 
+> 目前提供了一种新方式,可以参考[对接自己的存储服务](https://wanglin2.github.io/mind-map/#/doc/zh/deploy/%E5%AF%B9%E6%8E%A5%E8%87%AA%E5%B7%B1%E7%9A%84%E5%AD%98%E5%82%A8%E6%9C%8D%E5%8A%A1)。
+
 在线`demo`的数据是存储在电脑本地的,也就是`localStorage`里,当然,你也可以存储到数据库中。
 
 ## 保存数据
diff --git a/web/src/pages/Doc/zh/course18/index.vue b/web/src/pages/Doc/zh/course18/index.vue
index 51eaeca0..63840324 100644
--- a/web/src/pages/Doc/zh/course18/index.vue
+++ b/web/src/pages/Doc/zh/course18/index.vue
@@ -1,6 +1,9 @@
 
diff --git a/web/src/pages/Doc/zh/keyCommand/index.md b/web/src/pages/Doc/zh/keyCommand/index.md
index c332bff0..906f5a48 100644
--- a/web/src/pages/Doc/zh/keyCommand/index.md
+++ b/web/src/pages/Doc/zh/keyCommand/index.md
@@ -53,4 +53,12 @@ mindMap.keyCommand.addShortcut('Control+Enter', () => {})
 
 > v0.2.3+
 
-恢复保存的快捷键数据,然后清空缓存数据
\ No newline at end of file
+恢复保存的快捷键数据,然后清空缓存数据
+
+### hasCombinationKey(e)
+
+> v0.6.13+
+
+- `e`:事件对象。
+
+判断是否按下了组合键。
\ No newline at end of file
diff --git a/web/src/pages/Doc/zh/keyCommand/index.vue b/web/src/pages/Doc/zh/keyCommand/index.vue
index 163180d4..7274b1d2 100644
--- a/web/src/pages/Doc/zh/keyCommand/index.vue
+++ b/web/src/pages/Doc/zh/keyCommand/index.vue
@@ -41,6 +41,14 @@ mindMap.keyCommand.addShortcut('Control+Enter
 

v0.2.3+

恢复保存的快捷键数据,然后清空缓存数据

+

hasCombinationKey(e)

+
+

v0.6.13+

+
+
    +
  • e:事件对象。
  • +
+

判断是否按下了组合键。

diff --git a/web/src/pages/Doc/zh/node/index.md b/web/src/pages/Doc/zh/node/index.md index 7e786c47..dd7ea6a9 100644 --- a/web/src/pages/Doc/zh/node/index.md +++ b/web/src/pages/Doc/zh/node/index.md @@ -116,6 +116,12 @@ 修改节点的某个样式,`SET_NODE_STYLE`命令的快捷方法 +### setStyles(style, isActive) + +> v0.6.12+ + +修改节点多个样式,`SET_NODE_STYLES`命令的快捷方法 + ### getData(key) 获取该节点真实数据`nodeData`的`data`对象里的指定值,`key`不传返回这个`data`对象 diff --git a/web/src/pages/Doc/zh/node/index.vue b/web/src/pages/Doc/zh/node/index.vue index 66c8434c..ffc2a5cc 100644 --- a/web/src/pages/Doc/zh/node/index.vue +++ b/web/src/pages/Doc/zh/node/index.vue @@ -67,6 +67,11 @@

isActive:获取的是否是激活状态的样式值,默认false

setStyle(prop, value, isActive)

修改节点的某个样式,SET_NODE_STYLE命令的快捷方法

+

setStyles(style, isActive)

+
+

v0.6.12+

+
+

修改节点多个样式,SET_NODE_STYLES命令的快捷方法

getData(key)

获取该节点真实数据nodeDatadata对象里的指定值,key不传返回这个data对象

setData(data)

diff --git a/web/src/pages/Doc/zh/painter/index.md b/web/src/pages/Doc/zh/painter/index.md new file mode 100644 index 00000000..b7a4e719 --- /dev/null +++ b/web/src/pages/Doc/zh/painter/index.md @@ -0,0 +1,35 @@ +# Painter 插件 + +> v0.6.12+ + +节点格式刷插件。 + +## 注册 + +```js +import MindMap from 'simple-mind-map' +import Painter from 'simple-mind-map/src/plugins/Painter.js' +MindMap.usePlugin(Painter) +``` + +注册完且实例化`MindMap`后可通过`mindMap.painter`获取到该实例。 + +## 事件 + +> 可以通过mindMap.on('事件名称', () => {})来监听事件。 + +### painter_start + +开始格式刷事件。 + +### painter_end + +结束格式刷事件。 + +## 方法 + +### startPainter() + +开始格式刷。 + +当调用了该方法后,如果当前存在激活节点,那么会默认取第一个激活的节点为指定节点,点击其他节点后,会把该节点的样式应用到被点击的其他节点,当点击画布后本次格式刷操作结束。 \ No newline at end of file diff --git a/web/src/pages/Doc/zh/painter/index.vue b/web/src/pages/Doc/zh/painter/index.vue new file mode 100644 index 00000000..d6a3ed15 --- /dev/null +++ b/web/src/pages/Doc/zh/painter/index.vue @@ -0,0 +1,38 @@ + + + + + \ No newline at end of file diff --git a/web/src/pages/Doc/zh/richText/index.md b/web/src/pages/Doc/zh/richText/index.md index dcca3e9d..1d5e27ee 100644 --- a/web/src/pages/Doc/zh/richText/index.md +++ b/web/src/pages/Doc/zh/richText/index.md @@ -14,7 +14,9 @@ > > 这样也造成了一个问题,就是导出为图片的功能受到了影响,原本将`svg`导出为图片的原理很简单,获取到`svg`字符串,然后创建为`type=image/svg+xml`类型的`blob`数据,再使用`URL.createObjectURL`方法生成`data:url`数据,再创建一个`Image`标签,将`data:url`作为该图片的`src`,最后再将这个图片绘制到`canvas`对象上进行导出,但是经过测试,当`svg`中嵌入了`DOM`节点,这种方式导出会出错,并且尝试了多种方式后都无法实现完美的导出效果,目前的方式是遍历`svg`中的`foreignObject`节点,使用[html2canvas](https://github.com/niklasvh/html2canvas)将`foreignObject`节点内的`DOM`节点转换为图片再替换掉`foreignObject`节点,这种方式可以工作,但是非常耗时,因为`html2canvas`转换一次的时间很长,导致转换一个节点都需要耗时差不多2秒,这样导致节点越多,转换时间越慢,所以如果无法忍受长时间的导出的话推荐不要使用该插件。 -`v0.5.7+`的版本直接使用`html2canvas`转换整个`svg`,速度不再是问题,但是目前存在一个`bug`,就是节点的颜色导出后不生效。 +> `v0.5.7+`的版本直接使用`html2canvas`转换整个`svg`,速度不再是问题,但是目前存在一个`bug`,就是节点的颜色导出后不生效。 + +`v0.6.13+`版本使用[dom-to-image-more](https://github.com/1904labs/dom-to-image-more)替换了`html2canvas`,解决了节点的颜色导出后不生效的问题。 ## 注册 diff --git a/web/src/pages/Doc/zh/richText/index.vue b/web/src/pages/Doc/zh/richText/index.vue index 510f1184..443ed88b 100644 --- a/web/src/pages/Doc/zh/richText/index.vue +++ b/web/src/pages/Doc/zh/richText/index.vue @@ -14,7 +14,10 @@

v0.5.6即以前的版本存在以下提示:

这样也造成了一个问题,就是导出为图片的功能受到了影响,原本将svg导出为图片的原理很简单,获取到svg字符串,然后创建为type=image/svg+xml类型的blob数据,再使用URL.createObjectURL方法生成data:url数据,再创建一个Image标签,将data:url作为该图片的src,最后再将这个图片绘制到canvas对象上进行导出,但是经过测试,当svg中嵌入了DOM节点,这种方式导出会出错,并且尝试了多种方式后都无法实现完美的导出效果,目前的方式是遍历svg中的foreignObject节点,使用html2canvasforeignObject节点内的DOM节点转换为图片再替换掉foreignObject节点,这种方式可以工作,但是非常耗时,因为html2canvas转换一次的时间很长,导致转换一个节点都需要耗时差不多2秒,这样导致节点越多,转换时间越慢,所以如果无法忍受长时间的导出的话推荐不要使用该插件。

+

v0.5.7+的版本直接使用html2canvas转换整个svg,速度不再是问题,但是目前存在一个bug,就是节点的颜色导出后不生效。

+
+

v0.6.13+版本使用dom-to-image-more替换了html2canvas,解决了节点的颜色导出后不生效的问题。

注册

import MindMap from 'simple-mind-map'
 import RichText from 'simple-mind-map/src/plugins/RichText.js'
diff --git a/web/src/pages/Doc/zh/search/index.md b/web/src/pages/Doc/zh/search/index.md
index 736ef63f..a35d3cbb 100644
--- a/web/src/pages/Doc/zh/search/index.md
+++ b/web/src/pages/Doc/zh/search/index.md
@@ -45,10 +45,12 @@ mindMap.on('search_info_change', (data) => {
 
 结束搜索。
 
-### replace(replaceText)
+### replace(replaceText, jumpNext = false)
 
 - `replaceText`:要进行替换的文本
 
+- `jumpNext`:v0.6.12+,是否自动跳转到下一个匹配节点
+
 替换当前节点内容,要在调用了`search`方法之后调用,会替换当前定位到的匹配节点内容。
 
 ### replaceAll(replaceText)
diff --git a/web/src/pages/Doc/zh/search/index.vue b/web/src/pages/Doc/zh/search/index.vue
index 997de619..2c8e21b1 100644
--- a/web/src/pages/Doc/zh/search/index.vue
+++ b/web/src/pages/Doc/zh/search/index.vue
@@ -36,9 +36,14 @@ MindMap.usePlugin(Search)
 

搜索节点内容,可以重复调用,每调一次,会搜索和定位到下一个匹配的节点。如果搜索文本改变了,那么会重新搜索。

endSearch()

结束搜索。

-

replace(replaceText)

+

replace(replaceText, jumpNext = false)

    -
  • replaceText:要进行替换的文本
  • +
  • +

    replaceText:要进行替换的文本

    +
  • +
  • +

    jumpNext:v0.6.12+,是否自动跳转到下一个匹配节点

    +

替换当前节点内容,要在调用了search方法之后调用,会替换当前定位到的匹配节点内容。

replaceAll(replaceText)

diff --git a/web/src/pages/Doc/zh/utils/index.md b/web/src/pages/Doc/zh/utils/index.md index d5b26ee5..c5e440b9 100644 --- a/web/src/pages/Doc/zh/utils/index.md +++ b/web/src/pages/Doc/zh/utils/index.md @@ -223,9 +223,27 @@ copyNodeTree({}, node) #### isTransparent(color) +> v0.6.11+ + 判断一个颜色是否是透明。 -> v0.6.11+ +#### nodeRichTextToTextWithWrap(html) + +> v0.6.12+ + +将`

`形式的节点富文本内容转换成`\n`换行的文本。 + +#### textToNodeRichTextWithWrap(html) + +> v0.6.12+ + +将`
`换行的文本转换成`

`形式的节点富文本内容。 + +#### isMobile() + +> v0.6.13+ + +判断是否是移动端环境。 ## 在canvas中模拟css的背景属性 diff --git a/web/src/pages/Doc/zh/utils/index.vue b/web/src/pages/Doc/zh/utils/index.vue index 6a37a0b1..9a1e1102 100644 --- a/web/src/pages/Doc/zh/utils/index.vue +++ b/web/src/pages/Doc/zh/utils/index.vue @@ -155,10 +155,25 @@

判断一个颜色是否是白色。

isTransparent(color)

-

判断一个颜色是否是透明。

v0.6.11+

+

判断一个颜色是否是透明。

+

nodeRichTextToTextWithWrap(html)

+
+

v0.6.12+

+
+

<p><span></span><p>形式的节点富文本内容转换成\n换行的文本。

+

textToNodeRichTextWithWrap(html)

+
+

v0.6.12+

+
+

<br>换行的文本转换成<p><span></span><p>形式的节点富文本内容。

+

isMobile()

+
+

v0.6.13+

+
+

判断是否是移动端环境。

在canvas中模拟css的背景属性

引入:

import drawBackgroundImageToCanvas from 'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'
diff --git a/web/src/pages/Edit/Index.vue b/web/src/pages/Edit/Index.vue
index bc81551a..60595d8f 100644
--- a/web/src/pages/Edit/Index.vue
+++ b/web/src/pages/Edit/Index.vue
@@ -1,5 +1,8 @@