diff --git a/index.html b/index.html index 8aaae6fa..3684dc48 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/simple-mind-map/index.js b/simple-mind-map/index.js index ec7cb0d9..5878bccd 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -7,10 +7,17 @@ import Style from './src/core/render/node/Style' import KeyCommand from './src/core/command/KeyCommand' import Command from './src/core/command/Command' import BatchExecution from './src/utils/BatchExecution' -import { layoutValueList, CONSTANTS, commonCaches } from './src/constants/constant' +import { + layoutValueList, + CONSTANTS, + commonCaches, + ERROR_TYPES +} from './src/constants/constant' import { SVG } from '@svgdotjs/svg.js' import { simpleDeepClone, getType } from './src/utils' -import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default' +import defaultTheme, { + checkIsNodeSizeIndependenceConfig +} from './src/themes/default' import { defaultOpt } from './src/constants/defaultOptions' // 思维导图 @@ -22,11 +29,13 @@ class MindMap { // 容器元素 this.el = this.opt.el + if (!this.el) throw new Error('缺少容器元素el') this.elRect = this.el.getBoundingClientRect() // 画布宽高 this.width = this.elRect.width this.height = this.elRect.height + if (this.width <= 0 || this.height <= 0) throw new Error('容器元素el的宽高不能为0') // 画布 this.svg = SVG().addTo(this.el).size(this.width, this.height) @@ -68,7 +77,7 @@ class MindMap { this.batchExecution = new BatchExecution() // 注册插件 - MindMap.pluginList.forEach((plugin) => { + MindMap.pluginList.forEach(plugin => { this.initPlugin(plugin) }) @@ -136,10 +145,10 @@ class MindMap { // 初始化缓存数据 initCache() { - Object.keys(commonCaches).forEach((key) => { + Object.keys(commonCaches).forEach(key => { let type = getType(commonCaches[key]) let value = '' - switch(type) { + switch (type) { case 'Boolean': value = false break @@ -164,7 +173,7 @@ class MindMap { this.renderer.clearAllActive() this.opt.theme = theme this.render(null, CONSTANTS.CHANGE_THEME) - this.emit('view_theme_change', theme) + this.emit('view_theme_change', theme) } // 获取当前主题 @@ -278,8 +287,12 @@ class MindMap { // 导出 async export(...args) { - let result = await this.doExport.export(...args) - return result + try { + let result = await this.doExport.export(...args) + return result + } catch (error) { + this.mindMap.opt.errorHandler(ERROR_TYPES.EXPORT_ERROR, error) + } } // 转换位置 @@ -327,7 +340,11 @@ class MindMap { // 克隆一份数据 let clone = svg.clone() // 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题 - if ((rect.width > origWidth || rect.height > origHeight) && this.watermark && this.watermark.hasWatermark()) { + if ( + (rect.width > origWidth || rect.height > origHeight) && + this.watermark && + this.watermark.hasWatermark() + ) { this.width = rect.width this.height = rect.height this.watermark.draw() @@ -388,7 +405,7 @@ class MindMap { // 销毁 destroy() { // 移除插件 - [...MindMap.pluginList].forEach((plugin) => { + ;[...MindMap.pluginList].forEach(plugin => { this[plugin.instanceName] = null }) // 解绑事件 @@ -408,8 +425,8 @@ MindMap.usePlugin = (plugin, opt = {}) => { MindMap.pluginList.push(plugin) return MindMap } -MindMap.hasPlugin = (plugin) => { - return MindMap.pluginList.findIndex((item) => { +MindMap.hasPlugin = plugin => { + return MindMap.pluginList.findIndex(item => { return item === plugin }) } diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index bbbec57d..1d6d6a9d 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -1,6 +1,6 @@ { "name": "simple-mind-map", - "version": "0.6.14", + "version": "0.6.15", "description": "一个简单的web在线思维导图", "authors": [ { diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index 269c2c12..f07b162f 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -332,4 +332,14 @@ export const nodeDataNoStylePropList = [ export const commonCaches = { measureCustomNodeContentSizeEl: null, measureRichtextNodeTextSizeEl: null +} + +// 错误类型 +export const ERROR_TYPES = { + READ_CLIPBOARD_ERROR: 'read_clipboard_error', + PARSE_PASTE_DATA_ERROR: 'parse_paste_data_error', + CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR: 'custom_handle_clipboard_text_error', + LOAD_CLIPBOARD_IMAGE_ERROR: 'load_clipboard_image_error', + BEFORE_TEXT_EDIT_ERROR: 'before_text_edit_error', + EXPORT_ERROR: 'export_error' } \ No newline at end of file diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js index ff057c76..693a67ef 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -149,5 +149,11 @@ export const defaultOpt = { } */ // 如果你的处理逻辑存在异步逻辑,也可以返回一个promise - customHandleClipboardText: null + customHandleClipboardText: null, + // 禁止鼠标滚轮缩放,你仍旧可以使用api进行缩放 + disableMouseWheelZoom: false, + // 错误处理函数 + errorHandler: (code, error) => { + console.error(code, error) + } } diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 02e11ff9..39506d75 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -17,7 +17,7 @@ import { } from '../../utils' import { shapeList } from './node/Shape' import { lineStyleProps } from '../../themes/default' -import { CONSTANTS } from '../../constants/constant' +import { CONSTANTS, ERROR_TYPES } from '../../constants/constant' // 布局列表 const layouts = { @@ -629,6 +629,7 @@ class Render { // 粘贴事件 async onPaste() { + const { errorHandler } = this.mindMap.opt // 读取剪贴板的文字和图片 let text = null let img = null @@ -647,7 +648,7 @@ class Render { } } } catch (error) { - console.log(error) + errorHandler(ERROR_TYPES.READ_CLIPBOARD_ERROR, error) } } // 检查剪切板数据是否有变化 @@ -682,7 +683,9 @@ class Render { text = String(res) } } - } catch (error) {} + } catch (error) { + errorHandler(ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR, error) + } } // 默认处理 if (useDefault) { @@ -691,7 +694,9 @@ class Render { if (parsedData && parsedData.simpleMindMap) { smmData = parsedData.data } - } catch (error) {} + } catch (error) { + errorHandler(ERROR_TYPES.PARSE_PASTE_DATA_ERROR, error) + } } if (smmData) { this.mindMap.execCommand( @@ -724,7 +729,7 @@ class Render { }) } } catch (error) { - console.log(error) + errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error) } } } else { diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index 0926d3e0..33569874 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -1,4 +1,5 @@ import { getStrWithBrFromHtml, checkNodeOuter } from '../../utils' +import { ERROR_TYPES } from '../../constants/constant' // 节点文字编辑类 export default class TextEdit { @@ -104,6 +105,7 @@ export default class TextEdit { isShow = await beforeTextEdit(node, isInserting) } catch (error) { isShow = false + this.mindMap.opt.errorHandler(ERROR_TYPES.BEFORE_TEXT_EDIT_ERROR, error) } if (!isShow) return } diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index e614e36b..f763b650 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -1,10 +1,11 @@ import Style from './Style' import Shape from './Shape' -import { G, Rect, ForeignObject, SVG } from '@svgdotjs/svg.js' +import { G, ForeignObject, SVG } from '@svgdotjs/svg.js' import nodeGeneralizationMethods from './nodeGeneralization' import nodeExpandBtnMethods from './nodeExpandBtn' import nodeCommandWrapsMethods from './nodeCommandWraps' import nodeCreateContentsMethods from './nodeCreateContents' +import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect' import { CONSTANTS } from '../../../constants/constant' // 节点类 @@ -107,6 +108,10 @@ class Node { Object.keys(nodeExpandBtnMethods).forEach(item => { this[item] = nodeExpandBtnMethods[item].bind(this) }) + // 展开收起按钮占位元素相关方法 + Object.keys(nodeExpandBtnPlaceholderRectMethods).forEach(item => { + this[item] = nodeExpandBtnPlaceholderRectMethods[item].bind(this) + }) // 命令的相关方法 Object.keys(nodeCommandWrapsMethods).forEach(item => { this[item] = nodeCommandWrapsMethods[item].bind(this) @@ -252,9 +257,11 @@ class Node { this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY) this.shapePadding.paddingX = shapePaddingX this.shapePadding.paddingY = shapePaddingY + // 边框宽度,因为边框是以中线向两端发散,所以边框会超出节点 + const borderWidth = this.getBorderWidth() return { - width: _width + paddingX * 2 + shapePaddingX * 2, - height: _height + paddingY * 2 + margin + shapePaddingY * 2 + width: _width + paddingX * 2 + shapePaddingX * 2 + borderWidth, + height: _height + paddingY * 2 + margin + shapePaddingY * 2 + borderWidth } } @@ -264,10 +271,12 @@ class Node { this.group.clear() let { width, height, textContentItemMargin } = this let { paddingY } = this.getPaddingVale() - paddingY += this.shapePadding.paddingY + const halfBorderWidth = this.getBorderWidth() / 2 + paddingY += this.shapePadding.paddingY + halfBorderWidth // 节点形状 this.shapeNode = this.shapeInstance.createShape() this.shapeNode.addClass('smm-node-shape') + this.shapeNode.translate(halfBorderWidth, halfBorderWidth) this.group.add(this.shapeNode) this.updateNodeShape() // 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示 @@ -358,27 +367,6 @@ class Node { this.group.add(textContentNested) } - // 渲染展开收起按钮的隐藏占位元素 - renderExpandBtnPlaceholderRect() { - if (!this.mindMap.opt.alwaysShowExpandBtn) { - let { width, height } = this - if (!this._unVisibleRectRegionNode) { - this._unVisibleRectRegionNode = new Rect() - this._unVisibleRectRegionNode.fill({ - color: 'transparent' - }) - } - this.group.add(this._unVisibleRectRegionNode) - this.renderer.layout.renderExpandBtnRect( - this._unVisibleRectRegionNode, - this.expandBtnSize, - width, - height, - this - ) - } - } - // 给节点绑定事件 bindGroupEvent() { // 单击事件,选中节点 @@ -588,10 +576,7 @@ class Node { this.needLayout = false this.layout() } - if (this.needRerenderExpandBtnPlaceholderRect) { - this.needRerenderExpandBtnPlaceholderRect = false - this.renderExpandBtnPlaceholderRect() - } + this.updateExpandBtnPlaceholderRect() this.update() } // 子节点 @@ -846,6 +831,11 @@ class Node { ) // 父级 } + // 获取节点非节点状态的边框大小 + getBorderWidth() { + return this.style.merge('borderWidth', false, false) || 0 + } + // 获取数据 getData(key) { return key ? this.nodeData.data[key] || '' : this.nodeData.data diff --git a/simple-mind-map/src/core/render/node/Shape.js b/simple-mind-map/src/core/render/node/Shape.js index d53d8881..edefc6b2 100644 --- a/simple-mind-map/src/core/render/node/Shape.js +++ b/simple-mind-map/src/core/render/node/Shape.js @@ -62,7 +62,10 @@ export default class Shape { // 创建形状节点 createShape() { const shape = this.node.getShape() + const borderWidth = this.node.getBorderWidth() let { width, height } = this.node + width -= borderWidth + height -= borderWidth let node = null // 矩形 if (shape === CONSTANTS.SHAPE.RECTANGLE) { diff --git a/simple-mind-map/src/core/render/node/nodeExpandBtnPlaceholderRect.js b/simple-mind-map/src/core/render/node/nodeExpandBtnPlaceholderRect.js new file mode 100644 index 00000000..23d51ff4 --- /dev/null +++ b/simple-mind-map/src/core/render/node/nodeExpandBtnPlaceholderRect.js @@ -0,0 +1,66 @@ +import { Rect } from '@svgdotjs/svg.js' + +// 渲染展开收起按钮的隐藏占位元素 +function renderExpandBtnPlaceholderRect() { + // 根节点或没有子节点不需要渲染 + if ( + !this.nodeData.children || + this.nodeData.children.length <= 0 || + this.isRoot + ) { + return + } + // 默认显示展开按钮的情况下也不需要渲染 + if (!this.mindMap.opt.alwaysShowExpandBtn) { + let { width, height } = this + if (!this._unVisibleRectRegionNode) { + this._unVisibleRectRegionNode = new Rect() + this._unVisibleRectRegionNode.fill({ + color: 'transparent' + }) + } + this.group.add(this._unVisibleRectRegionNode) + this.renderer.layout.renderExpandBtnRect( + this._unVisibleRectRegionNode, + this.expandBtnSize, + width, + height, + this + ) + } +} + +// 删除展开收起按钮的隐藏占位元素 +function clearExpandBtnPlaceholderRect() { + if (!this._unVisibleRectRegionNode) { + return + } + this._unVisibleRectRegionNode.remove() + this._unVisibleRectRegionNode = null +} + +// 更新展开收起按钮的隐藏占位元素 +function updateExpandBtnPlaceholderRect() { + // 布局改变需要重新渲染 + if (this.needRerenderExpandBtnPlaceholderRect) { + this.needRerenderExpandBtnPlaceholderRect = false + this.renderExpandBtnPlaceholderRect() + } + // 没有子节点到有子节点需要渲染 + if (this.nodeData.children && this.nodeData.children.length > 0) { + if (!this._unVisibleRectRegionNode) { + this.renderExpandBtnPlaceholderRect() + } + } else { + // 有子节点到没子节点,需要删除 + if (this._unVisibleRectRegionNode) { + this.clearExpandBtnPlaceholderRect() + } + } +} + +export default { + renderExpandBtnPlaceholderRect, + clearExpandBtnPlaceholderRect, + updateExpandBtnPlaceholderRect +} diff --git a/simple-mind-map/src/core/view/View.js b/simple-mind-map/src/core/view/View.js index 0f16e52c..83954566 100644 --- a/simple-mind-map/src/core/view/View.js +++ b/simple-mind-map/src/core/view/View.js @@ -65,7 +65,8 @@ class View { mousewheelAction, mouseScaleCenterUseMousePosition, mousewheelMoveStep, - mousewheelZoomActionReverse + mousewheelZoomActionReverse, + disableMouseWheelZoom } = this.mindMap.opt // 是否自定义鼠标滚轮事件 if ( @@ -76,8 +77,10 @@ class View { } // 鼠标滚轮事件控制缩放 if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM) { - let cx = mouseScaleCenterUseMousePosition ? e.clientX : undefined - let cy = mouseScaleCenterUseMousePosition ? e.clientY : undefined + if (disableMouseWheelZoom) return + const { x: clientX, y: clientY } = this.mindMap.toPos(e.clientX, e.clientY) + let cx = mouseScaleCenterUseMousePosition ? clientX : undefined + let cy = mouseScaleCenterUseMousePosition ? clientY : undefined switch (dir) { // 鼠标滚轮,向上和向左,都是缩小 case CONSTANTS.DIR.UP: diff --git a/simple-mind-map/src/parse/markdownTo.js b/simple-mind-map/src/parse/markdownTo.js index 38ab7f0f..e399bf22 100644 --- a/simple-mind-map/src/parse/markdownTo.js +++ b/simple-mind-map/src/parse/markdownTo.js @@ -28,7 +28,7 @@ const handleList = node => { } // 将markdown转换成节点树 -export const transformMarkdownTo = async md => { +export const transformMarkdownTo = md => { const tree = fromMarkdown(md) let root = { children: [] diff --git a/web/src/pages/Doc/en/changelog/index.md b/web/src/pages/Doc/en/changelog/index.md index 1a61428a..8c166830 100644 --- a/web/src/pages/Doc/en/changelog/index.md +++ b/web/src/pages/Doc/en/changelog/index.md @@ -1,5 +1,41 @@ # Changelog +## 0.6.15 + +新增: + +> 1.Export PDF supports pagination export based on image size. +> +> 2.Exporting PDF supports automatic direction adjustment based on aspect ratio. +> +> 3.Optimize the placeholder elements of the expand and collapse buttons: 1. Nodes without child nodes do not render this element; 2. Dynamically update the element based on the existence of child nodes. +> +> 4.Add a configuration that prohibits mouse wheel scaling. +> +> 5.Supports passing error handling functions. + +修复: + +> 1.Fix the issue of displaying exceptions when node text is empty. +> +> 2.Change the paddingX and paddingY of exported SVG graphics to single sided padding. +> +> 3.Fixed an issue where the mouse is not centered when zooming when the canvas is not 0 from the top left corner of the browser window. +> +> 4.Fix the issue of overlapping node borders. + +Demo: + +> 1.The bottom right corner supports jumping to related links. +> +> 2.Adjust the position of the mini map to solve the problem of being blocked by side buttons. +> +> 3.Fix the issue where the prompt in the upper right corner of the open local file cannot be closed. +> +> 4.Editing the outline separately is no longer linked to the canvas, optimizing the editing experience under large data volume. +> +> 5.The sidebar involves graphical options to increase visualization effects. + ## 0.6.14 New: diff --git a/web/src/pages/Doc/en/changelog/index.vue b/web/src/pages/Doc/en/changelog/index.vue index 460ae8d8..04d087b9 100644 --- a/web/src/pages/Doc/en/changelog/index.vue +++ b/web/src/pages/Doc/en/changelog/index.vue @@ -1,6 +1,30 @@