diff --git a/index.html b/index.html index 95aa32de..3f8309c1 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ 思绪思维导图
\ No newline at end of file + } diff --git a/simple-mind-map/package-lock.json b/simple-mind-map/package-lock.json index ee730af9..a3858abc 100644 --- a/simple-mind-map/package-lock.json +++ b/simple-mind-map/package-lock.json @@ -1,11 +1,11 @@ { "name": "simple-mind-map", - "version": "0.6.12", + "version": "0.6.13", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.6.12", + "version": "0.6.13", "license": "MIT", "dependencies": { "@svgdotjs/svg.js": "^3.0.16", diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index 7d5c467e..bbbec57d 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -1,6 +1,6 @@ { "name": "simple-mind-map", - "version": "0.6.13", + "version": "0.6.14", "description": "一个简单的web在线思维导图", "authors": [ { diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js index dfd07c83..7453122d 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -127,12 +127,29 @@ export const defaultOpt = { customInnerElsAppendTo: null, // 拖拽元素时,指示元素新位置的块的最大高度 nodeDragPlaceholderMaxSize: 20, - // 是否允许创建一个隐藏的输入框,该输入框会在节点激活时聚焦,用于粘贴数据和自动进入文本编辑状态 - enableCreateHiddenInput: true, // 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式 - // 该配置在enableCreateHiddenInput设为true时生效 - enableAutoEnterTextEditWhenKeydown: true, + // 开启该特性后,需要给你的输入框绑定keydown事件,并禁止冒泡 + enableAutoEnterTextEditWhenKeydown: false, // 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果 // 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果 - richTextEditFakeInPlace: false + richTextEditFakeInPlace: false, + // 自定义对剪贴板文本的处理。当按ctrl+v粘贴时会读取用户剪贴板中的文本和图片,默认只会判断文本是否是普通文本和simple-mind-map格式的节点数据,如果你想处理其他思维导图的数据,比如processon、zhixi等,那么可以传递一个函数,接受当前剪贴板中的文本为参数,返回处理后的数据,可以返回两种类型: + /* + 1.返回一个纯文本,那么会直接以该文本创建一个子节点 + + 2.返回一个节点对象,格式如下: + { + // 代表是simple-mind-map格式的数据 + simpleMindMap: true, + // 节点数据,同simple-mind-map节点数据格式 + data: { + data: { + text: '' + }, + children: [] + } + } + */ + // 如果你的处理逻辑存在异步逻辑,也可以返回一个promise + customHandleClipboardText: null } diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 8944dba9..02e11ff9 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -12,7 +12,8 @@ import { simpleDeepClone, walk, bfsWalk, - loadImage + loadImage, + isUndef } from '../../utils' import { shapeList } from './node/Shape' import { lineStyleProps } from '../../themes/default' @@ -108,10 +109,6 @@ class Render { this.mindMap.execCommand('CLEAR_ACTIVE_NODE') } }) - // 粘贴事件 - this.mindMap.on('paste', data => { - this.onPaste(data) - }) // let timer = null // this.mindMap.on('view_data_change', () => { // clearTimeout(timer) @@ -273,8 +270,7 @@ class Render { this.copy = this.copy.bind(this) this.mindMap.keyCommand.addShortcut('Control+c', this.copy) this.mindMap.keyCommand.addShortcut('Control+v', () => { - // 隐藏输入框可能会失去焦点,所以要重新聚焦 - this.textEdit.focusHiddenInput() + this.onPaste() }) this.cut = this.cut.bind(this) this.mindMap.keyCommand.addShortcut('Control+x', this.cut) @@ -449,7 +445,12 @@ class Render { } // 插入同级节点,多个节点只会操作第一个节点 - insertNode(openEdit = true, appointNodes = [], appointData = null) { + insertNode( + openEdit = true, + appointNodes = [], + appointData = null, + appointChildren = [] + ) { appointNodes = this.formatAppointNodes(appointNodes) if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return @@ -485,14 +486,19 @@ class Render { resetRichText: isRichText, ...(appointData || {}) }, - children: [] + children: [...appointChildren] }) this.mindMap.render() } } // 插入子节点 - insertChildNode(openEdit = true, appointNodes = [], appointData = null) { + insertChildNode( + openEdit = true, + appointNodes = [], + appointData = null, + appointChildren = [] + ) { appointNodes = this.formatAppointNodes(appointNodes) if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return @@ -523,7 +529,7 @@ class Render { resetRichText: isRichText, ...(appointData || {}) }, - children: [] + children: [...appointChildren] }) // 插入子节点时自动展开子节点 node.nodeData.data.expand = true @@ -591,15 +597,29 @@ class Render { // 复制节点 copy() { this.beingCopyData = this.copyNode() + this.setCoptyDataToClipboard(this.beingCopyData) } // 剪切节点 cut() { this.mindMap.execCommand('CUT_NODE', copyData => { this.beingCopyData = copyData + this.setCoptyDataToClipboard(copyData) }) } + // 将粘贴或剪切的数据设置到用户剪切板中 + setCoptyDataToClipboard(data) { + if (navigator.clipboard) { + navigator.clipboard.writeText( + JSON.stringify({ + simpleMindMap: true, + data + }) + ) + } + } + // 粘贴节点 paste() { if (this.beingCopyData) { @@ -608,7 +628,28 @@ class Render { } // 粘贴事件 - async onPaste({ text, img }) { + async onPaste() { + // 读取剪贴板的文字和图片 + let text = null + let img = null + if (navigator.clipboard) { + try { + text = await navigator.clipboard.readText() + const items = await navigator.clipboard.read() + if (items && items.length > 0) { + for (const clipboardItem of items) { + for (const type of clipboardItem.types) { + if (/^image\//.test(type)) { + img = await clipboardItem.getType(type) + break + } + } + } + } + } catch (error) { + console.log(error) + } + } // 检查剪切板数据是否有变化 // 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法 const imgSize = img ? img.size : 0 @@ -626,9 +667,47 @@ class Render { if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) { // 存在文本,则创建子节点 if (text) { - this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], { - text - }) + // 判断粘贴的是否是simple-mind-map的数据 + let smmData = null + let useDefault = true + // 用户自定义处理 + if (this.mindMap.opt.customHandleClipboardText) { + try { + const res = await this.mindMap.opt.customHandleClipboardText(text) + if (!isUndef(res)) { + useDefault = false + if (typeof res === 'object' && res.simpleMindMap) { + smmData = res.data + } else { + text = String(res) + } + } + } catch (error) {} + } + // 默认处理 + if (useDefault) { + try { + const parsedData = JSON.parse(text) + if (parsedData && parsedData.simpleMindMap) { + smmData = parsedData.data + } + } catch (error) {} + } + if (smmData) { + this.mindMap.execCommand( + 'INSERT_CHILD_NODE', + false, + [], + { + ...smmData.data + }, + [...smmData.children] + ) + } else { + this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], { + text + }) + } } // 存在图片,则添加到当前激活节点 if (img) { @@ -1027,7 +1106,8 @@ class Render { } // 设置节点图片 - setNodeImage(node, { url, title, width, height, custom = false }) { + setNodeImage(node, data) { + const { url, title, width, height, custom = false } = data || { url: '', title: '', width: 0, height: 0, custom: false } this.setNodeDataRender(node, { image: url, imageTitle: title || '', diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index 92499d98..7ca0cc8e 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -10,16 +10,11 @@ export default class TextEdit { this.currentNode = null // 文本编辑框 this.textEditNode = null - // 隐藏的文本输入框 - this.hiddenInputEl = null - // 节点激活时默认聚焦到隐藏输入框 - this.enableFocus = true // 文本编辑框是否显示 this.showTextEdit = false // 如果编辑过程中缩放画布了,那么缓存当前编辑的内容 this.cacheEditingText = '' this.bindEvent() - this.createHiddenInput() } // 事件 @@ -51,10 +46,6 @@ export default class TextEdit { this.mindMap.on('before_node_active', () => { this.hideEditTextBox() }) - // 节点激活事件 - this.mindMap.on('node_active', () => { - this.focusHiddenInput() - }) // 注册编辑快捷键 this.mindMap.keyCommand.addShortcut('F2', () => { if (this.renderer.activeNodeList.length <= 0) { @@ -63,74 +54,29 @@ export default class TextEdit { this.show(this.renderer.activeNodeList[0]) }) this.mindMap.on('scale', this.onScale) - } - - // 创建一个隐藏的文本输入框 - createHiddenInput() { - 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 = ` - position: fixed; - left: -99999px; - top: -99999px; - ` - // 监听按键事件 - if (enableAutoEnterTextEditWhenKeydown) { - this.hiddenInputEl.addEventListener('keydown', e => { + // // 监听按键事件,判断是否自动进入文本编辑模式 + if (this.mindMap.opt.enableAutoEnterTextEditWhenKeydown) { + window.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) - ) { + if (node && this.checkIsAutoEnterTextEditKey(e)) { this.show(node) } }) } - // 监听粘贴事件 - this.hiddenInputEl.addEventListener('paste', async event => { - event.preventDefault() - const text = (event.clipboardData || window.clipboardData).getData('text') - const files = event.clipboardData.files - let img = null - if (files.length > 0) { - for (let i = 0; i < files.length; i++) { - if (/^image\//.test(files[i].type)) { - img = files[i] - break - } - } - } - this.mindMap.emit('paste', { - text, - img - }) - }) - document.body.appendChild(this.hiddenInputEl) } - // 让隐藏的文本输入框聚焦 - focusHiddenInput() { - if (this.hiddenInputEl && this.enableFocus) this.hiddenInputEl.focus() - } - - // 关闭默认聚焦 - stopFocusOnNodeActive() { - this.enableFocus = false - } - - // 开启默认聚焦 - openFocusOnNodeActive() { - this.enableFocus = true + // 判断是否是自动进入文本编模式的按钮 + checkIsAutoEnterTextEditKey(e) { + const keyCode = e.keyCode + return ( + (keyCode === 229 || + (keyCode >= 65 && keyCode <= 90) || + (keyCode >= 48 && keyCode <= 57)) && + !this.mindMap.keyCommand.hasCombinationKey(e) + ) } // 注册临时快捷键 @@ -188,6 +134,7 @@ export default class TextEdit { // 显示文本编辑框 showEditTextBox(node, rect, isInserting) { + if (this.showTextEdit) return this.mindMap.emit('before_show_text_edit') this.registerTmpShortcut() if (!this.textEditNode) { @@ -203,6 +150,11 @@ export default class TextEdit { this.textEditNode.addEventListener('mousedown', e => { e.stopPropagation() }) + this.textEditNode.addEventListener('keydown', e => { + if (this.checkIsAutoEnterTextEditKey(e)) { + e.stopPropagation() + } + }) const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body targetNode.appendChild(this.textEditNode) diff --git a/simple-mind-map/src/parse/xmind.js b/simple-mind-map/src/parse/xmind.js index cdb752d2..b852067d 100644 --- a/simple-mind-map/src/parse/xmind.js +++ b/simple-mind-map/src/parse/xmind.js @@ -64,13 +64,13 @@ const transformXmind = async (content, files) => { } // 图片 if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) { + // 处理异步逻辑 + let resolve = null + let promise = new Promise(_resolve => { + resolve = _resolve + }) + waitLoadImageList.push(promise) try { - // 处理异步逻辑 - let resolve = null - let promise = new Promise(_resolve => { - resolve = _resolve - }) - waitLoadImageList.push(promise) // 读取图片 let imageType = /\.([^.]+)$/.exec(node.image.src)[1] let imageBase64 = diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index 7b818e4e..c5830b44 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -198,6 +198,11 @@ class RichText { this.textEditNode.addEventListener('mousedown', e => { e.stopPropagation() }) + this.textEditNode.addEventListener('keydown', e => { + if (this.mindMap.renderer.textEdit.checkIsAutoEnterTextEditKey(e)) { + e.stopPropagation() + } + }) const targetNode = customInnerElsAppendTo || document.body targetNode.appendChild(this.textEditNode) } diff --git a/web/src/assets/icon-font/iconfont.css b/web/src/assets/icon-font/iconfont.css index fe9033b0..dc17607d 100644 --- a/web/src/assets/icon-font/iconfont.css +++ b/web/src/assets/icon-font/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 2479351 */ - src: url('iconfont.woff2?t=1690537337895') format('woff2'), - url('iconfont.woff?t=1690537337895') format('woff'), - url('iconfont.ttf?t=1690537337895') format('truetype'); + src: url('iconfont.woff2?t=1691822758372') format('woff2'), + url('iconfont.woff?t=1691822758372') format('woff'), + url('iconfont.ttf?t=1691822758372') format('truetype'); } .iconfont { @@ -13,6 +13,10 @@ -moz-osx-font-smoothing: grayscale; } +.icontouming:before { + content: "\e60c"; +} + .iconlieri:before { content: "\e60b"; } diff --git a/web/src/assets/icon-font/iconfont.ttf b/web/src/assets/icon-font/iconfont.ttf index 4b0d897e..028f19ae 100644 Binary files a/web/src/assets/icon-font/iconfont.ttf and b/web/src/assets/icon-font/iconfont.ttf differ diff --git a/web/src/assets/icon-font/iconfont.woff b/web/src/assets/icon-font/iconfont.woff index 09e3ebb3..3a255b1f 100644 Binary files a/web/src/assets/icon-font/iconfont.woff and b/web/src/assets/icon-font/iconfont.woff differ diff --git a/web/src/assets/icon-font/iconfont.woff2 b/web/src/assets/icon-font/iconfont.woff2 index 10ffcf6c..972819c3 100644 Binary files a/web/src/assets/icon-font/iconfont.woff2 and b/web/src/assets/icon-font/iconfont.woff2 differ diff --git a/web/src/config/zh.js b/web/src/config/zh.js index 21fccc0a..c33c1cdf 100644 --- a/web/src/config/zh.js +++ b/web/src/config/zh.js @@ -97,7 +97,8 @@ export const colorList = [ '#0C797D', '#0062B1', '#653294', - '#AB149E' + '#AB149E', + 'transparent' ] // 边框宽度 diff --git a/web/src/lang/en_us.js b/web/src/lang/en_us.js index 046bc966..e02a193d 100644 --- a/web/src/lang/en_us.js +++ b/web/src/lang/en_us.js @@ -78,7 +78,10 @@ export default { level6: 'Level6', zenMode: 'Zen mode', fitCanvas: 'Fit canvas', - create: 'Create new file' + create: 'Create new file', + removeImage: 'Remove image', + removeHyperlink: 'Remove hyperlink', + removeNote: 'Remove note' }, count: { words: 'Words', @@ -168,6 +171,7 @@ export default { italic: 'Italic', textDecoration: 'Text decoration', underline: 'Underline', + none: 'None', lineThrough: 'Line through', overline: 'Overline', border: 'Border', diff --git a/web/src/lang/zh_cn.js b/web/src/lang/zh_cn.js index d017ca9c..b53d9ea4 100644 --- a/web/src/lang/zh_cn.js +++ b/web/src/lang/zh_cn.js @@ -78,7 +78,10 @@ export default { level6: '六级主题', zenMode: '禅模式', fitCanvas: '适应画布', - create: '新建文件' + create: '新建文件', + removeImage: '移除图片', + removeHyperlink: '移除超链接', + removeNote: '移除备注' }, count: { words: '字数', @@ -167,6 +170,7 @@ export default { addFontWeight: '加粗', italic: '斜体', textDecoration: '划线', + none: '无', underline: '下划线', lineThrough: '中划线', overline: '上划线', diff --git a/web/src/pages/Doc/en/changelog/index.md b/web/src/pages/Doc/en/changelog/index.md index 9d59c2bc..1a61428a 100644 --- a/web/src/pages/Doc/en/changelog/index.md +++ b/web/src/pages/Doc/en/changelog/index.md @@ -1,5 +1,31 @@ # Changelog +## 0.6.14 + +New: + +> 1.Remove and create hidden input boxes, and copy and paste them through navigator. clipboard; Support cross browser pasting of mind map node data; Support custom processing of text data in the clipboard. + +Demo: + +> 1.Fix the issue of enabling input to automatically enter text editing mode and conflicting with other input boxes. +> +> 2.Fix the issue of not being able to delete node images in the node image pop-up window. +> +> 3.Fixed an issue where the text decoration line style of nodes cannot be removed in the node style sidebar. +> +> 4.The color selector supports selecting transparent colors. +> +> 5.Fix the issue of importing mind map data without updating the sidebar data when the basic style sidebar is open. +> +> 6.Fixed the issue of not focusing when modifying the text of one node in the outline and then clicking on other nodes. +> +> 7.Fixed an issue where the node and word count statistics in the bottom left corner were not updated after exiting Zen mode. +> +> 8.Support deleting hyperlinks and notes of nodes from the right-click menu. +> +> 9.Support pasting node data of Zhixi Mind Map. + ## 0.6.13 Fix: diff --git a/web/src/pages/Doc/en/changelog/index.vue b/web/src/pages/Doc/en/changelog/index.vue index a8d90a51..460ae8d8 100644 --- a/web/src/pages/Doc/en/changelog/index.vue +++ b/web/src/pages/Doc/en/changelog/index.vue @@ -1,6 +1,23 @@