diff --git a/index.html b/index.html index ad7a6fcf..2e49ce9e 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -一个简单的web思维导图实现
\ No newline at end of file +一个简单的web思维导图实现
\ No newline at end of file diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index fb5f002c..da5beaaa 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -101,7 +101,9 @@ const defaultOpt = { // 节点备注浮层的z-index nodeNoteTooltipZIndex: 3000, // 是否在点击了画布外的区域时结束节点文本的编辑状态 - isEndNodeTextEditOnClickOuter: true + isEndNodeTextEditOnClickOuter: true, + // 最大历史记录数 + maxHistoryCount: 1000 } // 思维导图 diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index fab46370..d37349b2 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -1,6 +1,6 @@ { "name": "simple-mind-map", - "version": "0.5.5-fix.2", + "version": "0.5.6", "description": "一个简单的web在线思维导图", "authors": [ { diff --git a/simple-mind-map/src/Command.js b/simple-mind-map/src/Command.js index 7b49ef0d..b1c35c57 100644 --- a/simple-mind-map/src/Command.js +++ b/simple-mind-map/src/Command.js @@ -84,6 +84,10 @@ class Command { // 删除当前历史指针后面的数据 this.history = this.history.slice(0, this.activeHistoryIndex + 1) this.history.push(simpleDeepClone(data)) + // 历史记录数超过最大数量 + if (this.history.length > this.mindMap.opt.maxHistoryCount) { + this.history.shift() + } this.activeHistoryIndex = this.history.length - 1 this.mindMap.emit('data_change', this.removeDataUid(data)) this.mindMap.emit( diff --git a/simple-mind-map/src/Node.js b/simple-mind-map/src/Node.js index 528f1635..3a787edc 100644 --- a/simple-mind-map/src/Node.js +++ b/simple-mind-map/src/Node.js @@ -389,7 +389,7 @@ class Node { // 右键菜单事件 this.group.on('contextmenu', e => { // 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件 - if (this.mindMap.opt.readonly || this.isGeneralization || e.ctrlKey) { + if (this.mindMap.opt.readonly || e.ctrlKey) {// || this.isGeneralization return } e.stopPropagation() diff --git a/simple-mind-map/src/Render.js b/simple-mind-map/src/Render.js index aac78ecd..8a696e8e 100644 --- a/simple-mind-map/src/Render.js +++ b/simple-mind-map/src/Render.js @@ -250,6 +250,7 @@ class Render { // 渲染 render(callback = () => {}, source) { + let t = Date.now() // 如果当前还没有渲染完毕,不再触发渲染 if (this.isRendering) { // 等待当前渲染完毕后再进行一次渲染 @@ -281,7 +282,7 @@ class Render { // 更新根节点 this.root = root // 渲染节点 - this.root.render(() => { + const onEnd = () => { this.isRendering = false this.mindMap.emit('node_tree_render_end') callback && callback() @@ -289,6 +290,18 @@ class Render { this.hasWaitRendering = false this.render(callback, source) } + } + let { enableNodeTransitionMove, nodeTransitionMoveDuration } = + this.mindMap.opt + this.root.render(() => { + let dur = Date.now() - t + if (enableNodeTransitionMove && dur <= nodeTransitionMoveDuration) { + setTimeout(() => { + onEnd() + }, nodeTransitionMoveDuration - dur); + } else { + onEnd() + } }) }) this.mindMap.emit('node_active', null, this.activeNodeList) diff --git a/simple-mind-map/src/RichText.js b/simple-mind-map/src/RichText.js index 39a24920..f419f151 100644 --- a/simple-mind-map/src/RichText.js +++ b/simple-mind-map/src/RichText.js @@ -1,6 +1,5 @@ import Quill from 'quill' import 'quill/dist/quill.snow.css' -import './css/quill.css' import html2canvas from 'html2canvas' import { Image as SvgImage } from '@svgdotjs/svg.js' import { walk } from './utils' @@ -41,8 +40,43 @@ class RichText { this.range = null this.lastRange = null this.node = null + this.styleEl = null + this.cacheEditingText = '' this.initOpt() this.extendQuill() + this.appendCss() + } + + // 插入样式 + appendCss() { + let cssText = ` + .ql-editor { + overflow: hidden; + padding: 0; + height: auto; + line-height: normal; + } + + .ql-container { + height: auto; + font-size: inherit; + } + + .ql-container.ql-snow { + border: none; + } + ` + // .smm-richtext-node-wrap p { + // display: flex; + // } + + // .smm-richtext-node-edit-wrap p { + // display: flex; + // } + this.styleEl = document.createElement('style') + this.styleEl.type = 'text/css' + this.styleEl.innerHTML = cssText + document.head.appendChild(this.styleEl) } // 处理选项参数 @@ -96,9 +130,12 @@ class RichText { if (!rect) rect = node._textData.node.node.getBoundingClientRect() this.mindMap.emit('before_show_text_edit') this.mindMap.renderer.textEdit.registerTmpShortcut() + const paddingX = 5 + const paddingY = 3 if (!this.textEditNode) { this.textEditNode = document.createElement('div') - 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: 3px 5px;margin-left: -5px;margin-top: -3px;` + 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;margin-left: -${paddingX}px;margin-top: -${paddingY}px;` this.textEditNode.addEventListener('click', e => { e.stopPropagation() }) @@ -112,14 +149,14 @@ class RichText { let bgColor = node.style.merge('fillColor') this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex this.textEditNode.style.backgroundColor = bgColor === 'transparent' ? '#fff' : bgColor - this.textEditNode.style.minWidth = originWidth + 'px' + this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px' this.textEditNode.style.minHeight = originHeight + 'px' this.textEditNode.style.left = rect.left + (rect.width - originWidth) / 2 + 'px' this.textEditNode.style.top = rect.top + (rect.height - originHeight) / 2 + 'px' this.textEditNode.style.display = 'block' - this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px' + this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + paddingX * 2 + 'px' this.textEditNode.style.transform = `scale(${rect.width / originWidth}, ${ rect.height / originHeight })` @@ -127,9 +164,9 @@ class RichText { // 还不是富文本的情况 let text = node.nodeData.data.text.split(/\n/gim).join('
') let html = `

${text}

` - this.textEditNode.innerHTML = html + this.textEditNode.innerHTML = this.cacheEditingText || html } else { - this.textEditNode.innerHTML = node.nodeData.data.text + this.textEditNode.innerHTML = this.cacheEditingText || node.nodeData.data.text } this.initQuillEditor() document.querySelector('.ql-editor').style.minHeight = originHeight + 'px' @@ -139,6 +176,7 @@ class RichText { // 如果是非富文本的情况,需要手动应用文本样式 this.setTextStyleIfNotRichText(node) } + this.cacheEditingText = '' } // 如果是非富文本的情况,需要手动应用文本样式 @@ -155,14 +193,19 @@ class RichText { this.formatAllText(style) } + // 获取当前正在编辑的内容 + getEditText() { + let html = this.quill.container.firstChild.innerHTML + // 去除最后的空行 + return html.replace(/


<\/p>$/, '') + } + // 隐藏文本编辑控件,即完成编辑 hideEditText(nodes) { if (!this.showTextEdit) { return } - let html = this.quill.container.firstChild.innerHTML - // 去除最后的空行 - html = html.replace(/


<\/p>$/, '') + let html = this.getEditText() let list = nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList list.forEach(node => { this.mindMap.execCommand('SET_NODE_TEXT', node, html, true) @@ -458,6 +501,7 @@ class RichText { // 插件被移除前做的事情 beforePluginRemove() { this.transformAllNodesToNormalNode() + document.head.removeChild(this.styleEl) } } diff --git a/simple-mind-map/src/TextEdit.js b/simple-mind-map/src/TextEdit.js index b371f658..adb2c9de 100644 --- a/simple-mind-map/src/TextEdit.js +++ b/simple-mind-map/src/TextEdit.js @@ -6,16 +6,21 @@ export default class TextEdit { constructor(renderer) { this.renderer = renderer this.mindMap = renderer.mindMap + // 当前编辑的节点 + this.currentNode = null // 文本编辑框 this.textEditNode = null // 文本编辑框是否显示 this.showTextEdit = false + // 如果编辑过程中缩放画布了,那么缓存当前编辑的内容 + this.cacheEditingText = '' this.bindEvent() } // 事件 bindEvent() { this.show = this.show.bind(this) + this.onScale = this.onScale.bind(this) // 节点双击事件 this.mindMap.on('node_dblclick', this.show) // 点击事件 @@ -48,6 +53,7 @@ export default class TextEdit { } this.show(this.renderer.activeNodeList[0]) }) + this.mindMap.on('scale', this.onScale) } // 注册临时快捷键 @@ -60,6 +66,7 @@ export default class TextEdit { // 显示文本编辑框 show(node) { + this.currentNode = node let { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node) this.mindMap.view.translateXY(offsetLeft, offsetTop) let rect = node._textData.node.node.getBoundingClientRect() @@ -70,6 +77,19 @@ export default class TextEdit { this.showEditTextBox(node, rect) } + // 处理画布缩放 + onScale() { + if (!this.currentNode) return + if (this.mindMap.richText) { + this.mindMap.richText.cacheEditingText = this.mindMap.richText.getEditText() + this.mindMap.richText.showTextEdit = false + } else { + this.cacheEditingText = this.getEditText() + this.showTextEdit = false + } + this.show(this.currentNode) + } + // 显示文本编辑框 showEditTextBox(node, rect) { this.mindMap.emit('before_show_text_edit') @@ -89,7 +109,7 @@ export default class TextEdit { let scale = this.mindMap.view.scale let lineHeight = node.style.merge('lineHeight') let fontSize = node.style.merge('fontSize') - let textLines = node.nodeData.data.text.split(/\n/gim) + let textLines = (this.cacheEditingText || node.nodeData.data.text).split(/\n/gim) node.style.domText(this.textEditNode, scale, textLines.length) this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex this.textEditNode.innerHTML = textLines.join('
') @@ -104,7 +124,10 @@ export default class TextEdit { } this.showTextEdit = true // 选中文本 - this.selectNodeText() + if (!this.cacheEditingText) { + this.selectNodeText() + } + this.cacheEditingText = '' } // 选中文本 @@ -116,8 +139,14 @@ export default class TextEdit { selection.addRange(range) } + // 获取当前正在编辑的内容 + getEditText() { + return getStrWithBrFromHtml(this.textEditNode.innerHTML) + } + // 隐藏文本编辑框 hideEditTextBox() { + this.currentNode = null if (this.mindMap.richText) { return this.mindMap.richText.hideEditText() } @@ -125,7 +154,7 @@ export default class TextEdit { return } this.renderer.activeNodeList.forEach(node => { - let str = getStrWithBrFromHtml(this.textEditNode.innerHTML) + let str = this.getEditText() this.mindMap.execCommand('SET_NODE_TEXT', node, str) if (node.isGeneralization) { // 概要节点 diff --git a/simple-mind-map/src/css/quill.css b/simple-mind-map/src/css/quill.css deleted file mode 100644 index 7afdf142..00000000 --- a/simple-mind-map/src/css/quill.css +++ /dev/null @@ -1,11 +0,0 @@ -.ql-editor { - overflow: hidden; - padding: 0; - height: auto; - line-height: normal; -} - -.ql-container { - height: auto; - font-size: inherit; -} \ No newline at end of file diff --git a/simple-mind-map/src/utils/nodeCreateContents.js b/simple-mind-map/src/utils/nodeCreateContents.js index eafa8a6b..9c507af1 100644 --- a/simple-mind-map/src/utils/nodeCreateContents.js +++ b/simple-mind-map/src/utils/nodeCreateContents.js @@ -57,6 +57,7 @@ function createRichTextNode() { div.innerHTML = html div.style.cssText = `position: fixed; left: -999999px;` let el = div.children[0] + el.classList.add('smm-richtext-node-wrap') el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml') el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px' this.mindMap.el.appendChild(div) @@ -100,11 +101,13 @@ function createTextNode() { let lines = [] let line = [] while (arr.length) { - line.push(arr.shift()) - let text = line.join('') - if (measureText(text, textStyle).width >= maxWidth) { - lines.push(text) - line = [] + let str = arr.shift() + let text = [...line, str].join('') + if (measureText(text, textStyle).width <= maxWidth) { + line.push(str) + } else { + lines.push(line.join('')) + line = [str] } } if (line.length > 0) { diff --git a/web/src/pages/Doc/en/changelog/index.md b/web/src/pages/Doc/en/changelog/index.md index f4f6088a..15b6fa44 100644 --- a/web/src/pages/Doc/en/changelog/index.md +++ b/web/src/pages/Doc/en/changelog/index.md @@ -1,11 +1,21 @@ # Changelog +## 0.5.6 + +Fix: 1.Fix the issue of node position disorder during fast and multiple renderings in a short period of time. 2.Fix the issue of dragging the canvas while the node is being edited, causing the edit box and node to separate. + +New: 1.Add a maximum history limit. + ## 0.5.5-fix.1 Fix: 1.Fix the issue where the edit box is also outside the canvas when editing nodes outside the canvas. 2.After modifying the structure, reset the transformation to prevent the problem of sudden position changes during the first drag after switching the structure during scaling. optimization: 1.When multiple nodes are selected, as long as there is a cross between the node and the selection area, it is considered selected. +## 0.5.5-fix.2 + +Fix: 1.Fix mini map error. + ## 0.5.5 New: 1.Supports configuring the padding when exporting to PNG, SVG, or PDF. 2.Support the configuration of z-index for node text editing boxes and node comment floating layer elements. 3.Support clicking on areas outside the canvas to end node editing status. diff --git a/web/src/pages/Doc/en/changelog/index.vue b/web/src/pages/Doc/en/changelog/index.vue index c7e18164..8fc39958 100644 --- a/web/src/pages/Doc/en/changelog/index.vue +++ b/web/src/pages/Doc/en/changelog/index.vue @@ -1,9 +1,14 @@