From 10cb36829fa4080f6a578efb1ecbf416c16598b6 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Wed, 19 Jul 2023 09:01:47 +0800 Subject: [PATCH 01/43] Doc: update --- index.html | 2 +- web/src/pages/Doc/catalogList.js | 14 +++++ web/src/pages/Doc/components/CatalogBar.vue | 26 ++++---- web/src/pages/Doc/components/Sidebar.vue | 23 ++++--- web/src/pages/Doc/en/deploy/index.md | 67 ++++++++++++++++++++- web/src/pages/Doc/en/deploy/index.vue | 55 +++++++++++++++++ web/src/pages/Doc/routerList.js | 3 +- web/src/pages/Doc/zh/help1/index.md | 11 ++++ web/src/pages/Doc/zh/help1/index.vue | 21 +++++++ web/src/pages/Index/components/Header.vue | 5 ++ web/src/router.js | 46 ++++++++------ 11 files changed, 230 insertions(+), 43 deletions(-) create mode 100644 web/src/pages/Doc/en/deploy/index.vue create mode 100644 web/src/pages/Doc/zh/help1/index.md create mode 100644 web/src/pages/Doc/zh/help1/index.vue diff --git a/index.html b/index.html index 1b985c2b..a9fb8943 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -思绪思维导图
\ No newline at end of file +思绪思维导图
\ No newline at end of file diff --git a/web/src/pages/Doc/catalogList.js b/web/src/pages/Doc/catalogList.js index b709769d..ec15f6c1 100644 --- a/web/src/pages/Doc/catalogList.js +++ b/web/src/pages/Doc/catalogList.js @@ -36,6 +36,9 @@ let APIList = [ 'markdown', 'utils' ] +let helpList = new Array(1).fill(0).map((_, index) => { + return 'help' + (index + 1) +}) const createList = (lang, list) => { let langRouter = routerList.find(item => { @@ -62,28 +65,39 @@ export default { zh: [ { groupName: '开始', + type: 'doc', list: createList('zh', StartList) }, { groupName: '教程', + type: 'doc', list: createList('zh', CourseList) }, { groupName: 'API', + type: 'doc', list: createList('zh', APIList) + }, + { + groupName: '使用帮助', + type: 'help', + list: createList('zh', helpList) } ], en: [ { groupName: 'Start', + type: 'doc', list: createList('en', StartList) }, { groupName: 'Course', + type: 'doc', list: createList('zh', CourseList) }, { groupName: 'API', + type: 'doc', list: createList('en', APIList) } ] diff --git a/web/src/pages/Doc/components/CatalogBar.vue b/web/src/pages/Doc/components/CatalogBar.vue index 9399ef6a..d6106ade 100644 --- a/web/src/pages/Doc/components/CatalogBar.vue +++ b/web/src/pages/Doc/components/CatalogBar.vue @@ -66,18 +66,18 @@ export default { methods: { // 获取当前语言 initLang() { - let lang = /^\/doc\/([^\/]+)\//.exec(this.$route.path) - if (lang && lang[1]) { - this.lang = lang[1] + let lang = /^\/(doc|help)\/([^\/]+)\//.exec(this.$route.path) + if (lang && lang[2]) { + this.lang = lang[2] } }, // 初始化二级标题目录 initCatalogList(newPath, oldPath) { - let newPathRes = /^\/doc\/[^\/]+\/([^\/]+)/.exec(newPath) - let oldPathRes = /^\/doc\/[^\/]+\/([^\/]+)/.exec(oldPath) + let newPathRes = /^\/(doc|help)\/[^\/]+\/([^\/]+)/.exec(newPath) + let oldPathRes = /^\/(doc|help)\/[^\/]+\/([^\/]+)/.exec(oldPath) // 语言变了、章节变了,需要重新获取二级标题目录 - if ((!newPath && !oldPath) || newPathRes[1] !== oldPathRes[1]) { + if ((!newPath && !oldPath) || newPathRes[2] !== oldPathRes[2]) { this.$emit('scroll', 0) this.resetActive() let container = document.getElementById('doc') @@ -93,9 +93,9 @@ export default { // 如果url中存在二级标题,那么滚动到该标题所在位置 scrollToCatalog() { - let url = /^\/doc\/[^\/]+\/[^\/]+\/([^\/]+)($|\/)/.exec(this.$route.path) - if (url && url[1]) { - let h = decodeURIComponent(url[1]) + let url = /^\/(doc|help)\/[^\/]+\/[^\/]+\/([^\/]+)($|\/)/.exec(this.$route.path) + if (url && url[2]) { + let h = decodeURIComponent(url[2]) let item = this.list.find(item => { return item.title === h }) @@ -126,15 +126,15 @@ export default { let path = this.$route.path let url = '' if (!title) { - url = path.replace(/^(\/doc\/[^\/]+\/[^\/]+)($|\/|.*)$/, '$1') - } else if (/^\/doc\/[^\/]+\/[^\/]+($|\/)$/.test(path)) { + url = path.replace(/^(\/(doc|help)\/[^\/]+\/[^\/]+)($|\/|.*)$/, '$1') + } else if (/^\/(doc|help)\/[^\/]+\/[^\/]+($|\/)$/.test(path)) { url = path.replace( - /^(\/doc\/[^\/]+\/[^\/]+)($|\/)$/, + /^(\/(doc|help)\/[^\/]+\/[^\/]+)($|\/)$/, '$1/' + encodeURIComponent(title) ) } else { url = path.replace( - /^(\/doc\/[^\/]+\/[^\/]+\/)([^\/]+)($|\/)/, + /^(\/(doc|help)\/[^\/]+\/[^\/]+\/)([^\/]+)($|\/)/, (...args) => { return args[1] + encodeURIComponent(title) } diff --git a/web/src/pages/Doc/components/Sidebar.vue b/web/src/pages/Doc/components/Sidebar.vue index 4482aa3c..c9a16725 100644 --- a/web/src/pages/Doc/components/Sidebar.vue +++ b/web/src/pages/Doc/components/Sidebar.vue @@ -31,7 +31,8 @@ export default { return { groupList: [], lang: '', - currentPath: '' + currentPath: '', + type: ''// doc、help } }, created() { @@ -47,20 +48,24 @@ export default { if (item.path === this.currentPath) { return } - this.$router.push(`/doc/${this.lang}/${item.path}`) + this.$router.push(`/${this.type}/${this.lang}/${item.path}`) }, initCatalog() { // 目录列表 - let lang = /^\/doc\/([^\/]+)\//.exec(this.$route.path) - if (lang && lang[1]) { - this.lang = lang[1] - this.groupList = catalogList[this.lang] + let lang = /^\/(doc|help)\/([^\/]+)\//.exec(this.$route.path) + if (lang && lang[2]) { + this.type = lang[1]// 判断是开发文档还是帮助文档 + this.lang = lang[2] + // 过滤出对应文档的章节 + this.groupList = catalogList[this.lang].filter((item) => { + return item.type === this.type + }) } // 当前所在路径 - let path = /^\/doc\/[^\/]+\/([^\/]+)(\/|$)/.exec(this.$route.path) - if (path && path[1]) { - this.currentPath = path[1] + let path = /^\/(doc|help)\/[^\/]+\/([^\/]+)(\/|$)/.exec(this.$route.path) + if (path && path[2]) { + this.currentPath = path[2] } } } diff --git a/web/src/pages/Doc/en/deploy/index.md b/web/src/pages/Doc/en/deploy/index.md index 4a8d593f..44f11bb9 100644 --- a/web/src/pages/Doc/en/deploy/index.md +++ b/web/src/pages/Doc/en/deploy/index.md @@ -1 +1,66 @@ -# Deploy \ No newline at end of file +# Deploy + +The 'web' directory of this project provides a complete project developed based on the 'simple mind map' library, 'Vue2. x', and 'ElementUI'. The data is stored locally on the computer by default, and can also be manipulated locally on the computer. Originally intended as an online 'demo', it can also be directly used as an online version of the mind map application, online address: [https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/). + +If your network environment is slow to access the 'GitHub' service, you can also deploy it to your server. + +## Deploying to a static file server + +The project itself does not rely on the backend, so it can be deployed to a static file server. The following commands can be executed in sequence: + +```bash +git clone https://github.com/wanglin2/mind-map.git +cd mind-map +cd simple-mind-map +npm i +npm link +cd .. +cd web +npm i +npm link simple-mind-map +``` + +Then you can choose to start the local service: + +```bash +npm run serve +``` + +You can also directly package and generate construction products: + +```bash +npm run build +``` + +The packaged entry page 'index.html' can be found in the project root directory, and the corresponding static resources are located in the 'dist' directory under the root directory. The 'html' file will access the resources in the 'dist' directory through relative paths, such as 'dist/xxx'. You can directly upload these two files or directories to your static file server. In fact, this project is deployed to 'GitHub Pages' in this way. + +If you do not have any code modification requirements, it is also possible to directly copy these files from this repository. + +If you want to package 'index.html' into the 'dist' directory as well, you can modify the 'scripts.build' command in the 'web/package.json' file to delete '&& node ../copy.js' from 'vue-cli-service build && node ../copy.js'. + +If you want to modify the directory for packaging output, you can modify the 'outputDir' configuration of the 'web/vue.config.js' file to the path you want to output. + +If you want to modify the path of the 'index. html' file referencing static resources, you can modify the 'publicPath' configuration of the 'web/vue.config.js' file. + +In addition, the default route used is 'hash ', which means that there will be '#'in the path. If you want to use the 'history' route, you can modify the 'web/src/router.js' file to: + +```js +const router = new VueRouter({ + routes +}) +``` + +Change to: + +```js +const router = new VueRouter({ + mode: 'history', + routes +}) +``` + +However, this requires backend support, as our application is a single page client application. If the backend is not properly configured, users will return 404 when accessing sub routes directly in the browser. Therefore, you need to add a candidate resource on the server that covers all situations: if the 'URL' cannot match any static resources, the same 'index. html' page should be returned. + +## Docker + +In writing... \ No newline at end of file diff --git a/web/src/pages/Doc/en/deploy/index.vue b/web/src/pages/Doc/en/deploy/index.vue new file mode 100644 index 00000000..b12696d2 --- /dev/null +++ b/web/src/pages/Doc/en/deploy/index.vue @@ -0,0 +1,55 @@ + + + + + \ No newline at end of file diff --git a/web/src/pages/Doc/routerList.js b/web/src/pages/Doc/routerList.js index 1f08e498..cc378a54 100644 --- a/web/src/pages/Doc/routerList.js +++ b/web/src/pages/Doc/routerList.js @@ -47,7 +47,8 @@ export default [ { path: 'deploy', title: '部署' }, { path: 'client', title: '客户端' }, { path: 'touchEvent', title: 'TouchEvent插件' }, - { path: 'nodeImgAdjust', title: 'NodeImgAdjust插件' } + { path: 'nodeImgAdjust', title: 'NodeImgAdjust插件' }, + { path: 'help1', title: '概要/关联线' } ] }, { diff --git a/web/src/pages/Doc/zh/help1/index.md b/web/src/pages/Doc/zh/help1/index.md new file mode 100644 index 00000000..ba382983 --- /dev/null +++ b/web/src/pages/Doc/zh/help1/index.md @@ -0,0 +1,11 @@ +# 概要/关联线 + +## 概要 + +可以选中一个节点添加概要,如果想给多个节点添加一个概要,只能通过给它们的父节点添加来实现。 + +概要节点后面无法再添加节点,后续该特性也不会支持。 + +## 关联线 + +关联线添加完后要删除,需要先点击选中关联线,然后按删除键即可。 \ No newline at end of file diff --git a/web/src/pages/Doc/zh/help1/index.vue b/web/src/pages/Doc/zh/help1/index.vue new file mode 100644 index 00000000..a7a6fba9 --- /dev/null +++ b/web/src/pages/Doc/zh/help1/index.vue @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/web/src/pages/Index/components/Header.vue b/web/src/pages/Index/components/Header.vue index a778b750..d1bc9784 100644 --- a/web/src/pages/Index/components/Header.vue +++ b/web/src/pages/Index/components/Header.vue @@ -25,6 +25,7 @@ > 客户端 +
Date: Fri, 21 Jul 2023 14:10:40 +0800 Subject: [PATCH 03/43] =?UTF-8?q?Demo:=E4=BF=AE=E5=A4=8D=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F-=E8=AE=BE=E7=BD=AE=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=A4=96=E8=BE=B9=E8=B7=9D=E6=9C=AA=E4=BF=9D=E5=AD=98=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/Edit/components/BaseStyle.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/src/pages/Edit/components/BaseStyle.vue b/web/src/pages/Edit/components/BaseStyle.vue index 9c558d9e..3de1096a 100644 --- a/web/src/pages/Edit/components/BaseStyle.vue +++ b/web/src/pages/Edit/components/BaseStyle.vue @@ -879,6 +879,12 @@ export default { } this.data.theme.config[this.marginActiveTab][type] = value this.mindMap.setThemeConfig(this.data.theme.config) + storeConfig({ + theme: { + template: this.mindMap.getTheme(), + config: this.data.theme.config + } + }) }, // 切换显示水印与否 From a76ec0dad896fa2667f0fa84293acb474bc6af60 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Fri, 21 Jul 2023 17:40:09 +0800 Subject: [PATCH 04/43] =?UTF-8?q?Feat=EF=BC=9A=E4=BF=AE=E6=94=B9=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E3=80=81=E5=89=AA=E5=88=87=E3=80=81=E7=B2=98=E8=B4=B4?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=9B=E6=94=AF=E6=8C=81=E7=B2=98=E8=B4=B4?= =?UTF-8?q?=E5=89=AA=E5=88=87=E6=9D=BF=E4=B8=AD=E7=9A=84=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/src/constants/constant.js | 4 + .../src/core/command/KeyCommand.js | 7 +- simple-mind-map/src/core/render/Render.js | 152 +++++++++++++++--- simple-mind-map/src/core/render/TextEdit.js | 58 ++++++- simple-mind-map/src/utils/index.js | 21 ++- web/src/pages/Edit/components/Contextmenu.vue | 45 +----- 6 files changed, 217 insertions(+), 70 deletions(-) diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index 64826939..5bf95bad 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -213,6 +213,10 @@ export const CONSTANTS = { TOP: 'top', RIGHT: 'right', BOTTOM: 'bottom' + }, + PASTE_TYPE: { + CLIP_BOARD: 'clipBoard', + CANVAS: 'canvas' } } diff --git a/simple-mind-map/src/core/command/KeyCommand.js b/simple-mind-map/src/core/command/KeyCommand.js index cc7a772b..e894516c 100644 --- a/simple-mind-map/src/core/command/KeyCommand.js +++ b/simple-mind-map/src/core/command/KeyCommand.js @@ -57,8 +57,11 @@ export default class KeyCommand { } Object.keys(this.shortcutMap).forEach(key => { if (this.checkKey(e, key)) { - e.stopPropagation() - e.preventDefault() + // 粘贴事件不组织,因为要监听paste事件 + if (!this.checkKey(e, 'Control+v')) { + e.stopPropagation() + e.preventDefault() + } this.shortcutMap[key].forEach(fn => { fn() }) diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 5385960a..1e2664ea 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -7,7 +7,13 @@ import Timeline from '../../layouts/Timeline' import VerticalTimeline from '../../layouts/VerticalTimeline' import Fishbone from '../../layouts/Fishbone' import TextEdit from './TextEdit' -import { copyNodeTree, simpleDeepClone, walk, bfsWalk } from '../../utils' +import { + copyNodeTree, + simpleDeepClone, + walk, + bfsWalk, + loadImage +} from '../../utils' import { shapeList } from './node/Shape' import { lineStyleProps } from '../../themes/default' import { CONSTANTS } from '../../constants/constant' @@ -29,7 +35,7 @@ const layouts = { // 竖向时间轴 [CONSTANTS.LAYOUT.VERTICAL_TIMELINE]: VerticalTimeline, // 鱼骨图 - [CONSTANTS.LAYOUT.FISHBONE]: Fishbone, + [CONSTANTS.LAYOUT.FISHBONE]: Fishbone } // 渲染 @@ -60,6 +66,12 @@ class Render { this.root = null // 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改 this.textEdit = new TextEdit(this) + // 当前复制的数据 + this.lastBeingCopyData = null + this.beingCopyData = null + this.beingPasteText = '' + this.beingPasteImgSize = 0 + this.currentBeingPasteType = '' // 布局 this.setLayout() // 绑定事件 @@ -82,18 +94,24 @@ class Render { // 绑定事件 bindEvent() { // 点击事件 - this.mindMap.on('draw_click', (e) => { + this.mindMap.on('draw_click', e => { // 清除激活状态 let isTrueClick = true let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt if (useLeftKeySelectionRightKeyDrag) { let mousedownPos = this.mindMap.event.mousedownPos - isTrueClick = Math.abs(e.clientX - mousedownPos.x) <= 5 && Math.abs(e.clientY - mousedownPos.y) <= 5 + isTrueClick = + Math.abs(e.clientX - mousedownPos.x) <= 5 && + Math.abs(e.clientY - mousedownPos.y) <= 5 } if (isTrueClick && this.activeNodeList.length > 0) { this.mindMap.execCommand('CLEAR_ACTIVE_NODE') } }) + // 粘贴事件 + this.mindMap.on('paste', data => { + this.onPaste(data) + }) } // 注册命令 @@ -242,6 +260,14 @@ class Render { // 下移节点 this.mindMap.keyCommand.addShortcut('Control+Down', this.downNode) // 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现,可参考demo + this.copy = this.copy.bind(this) + this.mindMap.keyCommand.addShortcut('Control+c', this.copy) + this.mindMap.keyCommand.addShortcut('Control+v', () => { + // 隐藏输入框可能会失去焦点,所以要重新聚焦 + this.textEdit.focusHiddenInput() + }) + this.cut = this.cut.bind(this) + this.mindMap.keyCommand.addShortcut('Control+x', this.cut) } // 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突 @@ -281,7 +307,7 @@ class Render { // 计算布局 this.layout.doLayout(root => { // 删除本次渲染时不再需要的节点 - Object.keys(this.lastNodeCache).forEach((uid) => { + Object.keys(this.lastNodeCache).forEach(uid => { if (!this.nodeCache[uid]) { this.lastNodeCache[uid].destroy() if (this.lastNodeCache[uid].parent) { @@ -301,7 +327,10 @@ class Render { this.render(callback, source) } else { // 触发一次保存,因为修改了渲染树的数据 - if (this.mindMap.richText && [CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)) { + if ( + this.mindMap.richText && + [CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source) + ) { this.mindMap.command.addHistory() } } @@ -406,7 +435,7 @@ class Render { // 规范指定节点数据 formatAppointNodes(appointNodes) { if (!appointNodes) return [] - return Array.isArray(appointNodes) ? appointNodes: [appointNodes] + return Array.isArray(appointNodes) ? appointNodes : [appointNodes] } // 插入同级节点,多个节点只会操作第一个节点 @@ -415,7 +444,10 @@ class Render { if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return } - let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt + let { + defaultInsertSecondLevelNodeText, + defaultInsertBelowSecondLevelNodeText + } = this.mindMap.opt let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList let first = list[0] if (first.isGeneralization) { @@ -424,7 +456,10 @@ class Render { if (first.isRoot) { this.insertChildNode(openEdit, appointNodes, appointData) } else { - let text = first.layerIndex === 1 ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText + let text = + first.layerIndex === 1 + ? defaultInsertSecondLevelNodeText + : defaultInsertBelowSecondLevelNodeText if (first.layerIndex === 1) { first.parent.destroy() } @@ -451,7 +486,10 @@ class Render { if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) { return } - let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt + let { + defaultInsertSecondLevelNodeText, + defaultInsertBelowSecondLevelNodeText + } = this.mindMap.opt let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList list.forEach(node => { if (node.isGeneralization) { @@ -460,7 +498,9 @@ class Render { if (!node.nodeData.children) { node.nodeData.children = [] } - let text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText + let text = node.isRoot + ? defaultInsertSecondLevelNodeText + : defaultInsertBelowSecondLevelNodeText let isRichText = !!this.mindMap.richText node.nodeData.children.push({ inserting: openEdit, @@ -536,13 +576,81 @@ class Render { this.mindMap.render() } + // 复制节点 + copy() { + this.beingCopyData = this.copyNode() + } + + // 剪切节点 + cut() { + this.mindMap.execCommand('CUT_NODE', copyData => { + this.beingCopyData = copyData + }) + } + + // 粘贴节点 + paste() { + if (this.beingCopyData) { + this.mindMap.execCommand('PASTE_NODE', this.beingCopyData) + } + } + + // 粘贴事件 + async onPaste({ text, img }) { + // 检查剪切板数据是否有变化 + // 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法 + const imgSize = img ? img.size : 0 + if (this.beingPasteText !== text || this.beingPasteImgSize !== imgSize) { + this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CLIP_BOARD + this.beingPasteText = text + this.beingPasteImgSize = imgSize + } + // 检查要粘贴的节点数据是否有变化,节点优先级高于剪切板 + if (this.lastBeingCopyData !== this.beingCopyData) { + this.lastBeingCopyData = this.beingCopyData + this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CANVAS + } + // 粘贴剪切板的数据 + if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) { + // 存在文本,则创建子节点 + if (text) { + this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], { + text + }) + } + // 存在图片,则添加到当前激活节点 + if (img) { + try { + let imgData = await loadImage(img) + if (this.activeNodeList.length > 0) { + this.activeNodeList.forEach(node => { + this.mindMap.execCommand('SET_NODE_IMAGE', node, { + url: imgData.url, + title: '', + width: imgData.size.width, + height: imgData.size.height + }) + }) + } + } catch (error) { + console.log(error) + } + } + } else { + // 粘贴节点数据 + this.paste() + } + } + // 将节点移动到另一个节点的前面 insertBefore(node, exist) { if (node.isRoot) { return } // 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新 - let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1) + let nodeLayerChanged = + (node.layerIndex === 1 && exist.layerIndex !== 1) || + (node.layerIndex !== 1 && exist.layerIndex === 1) // 移动节点 let nodeParent = node.parent let nodeBorthers = nodeParent.children @@ -579,7 +687,9 @@ class Render { return } // 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新 - let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1) + let nodeLayerChanged = + (node.layerIndex === 1 && exist.layerIndex !== 1) || + (node.layerIndex !== 1 && exist.layerIndex === 1) // 移动节点 let nodeParent = node.parent let nodeBorthers = nodeParent.children @@ -619,7 +729,7 @@ class Render { } let isAppointNodes = appointNodes.length > 0 let list = isAppointNodes ? appointNodes : this.activeNodeList - let root = list.find((node) => { + let root = list.find(node => { return node.isRoot }) if (root) { @@ -705,7 +815,7 @@ class Render { // 粘贴节点到节点 pasteNode(data) { - if (this.activeNodeList.length <= 0) { + if (this.activeNodeList.length <= 0 || !data) { return } this.activeNodeList.forEach(item => { @@ -997,7 +1107,7 @@ class Render { if (targetNode) { targetNode.active() this.moveNodeToCenter(targetNode) - } + } }) } @@ -1038,7 +1148,7 @@ class Render { this.mindMap.view.setScale(1) } - // 展开到指定uid的节点 + // 展开到指定uid的节点 expandToNodeUid(uid, callback = () => {}) { let parentsList = [] const cache = {} @@ -1047,11 +1157,11 @@ class Render { parentsList = parent ? [...cache[parent.data.uid], parent] : [] return 'stop' } else { - cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent]: [] + cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent] : [] } }) let needRender = false - parentsList.forEach((node) => { + parentsList.forEach(node => { if (!node.data.expand) { needRender = true node.data.expand = true @@ -1067,11 +1177,11 @@ class Render { // 根据uid找到对应的节点实例 findNodeByUid(uid) { let res = null - walk(this.root, null, (node) => { + walk(this.root, null, node => { if (node.nodeData.data.uid === uid) { res = node return true - } + } }) return res } diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index 6ae819a9..523cf4ba 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -10,11 +10,14 @@ export default class TextEdit { this.currentNode = null // 文本编辑框 this.textEditNode = null + // 隐藏的文本输入框 + this.hiddenInputEl = null // 文本编辑框是否显示 this.showTextEdit = false // 如果编辑过程中缩放画布了,那么缓存当前编辑的内容 this.cacheEditingText = '' this.bindEvent() + this.createHiddenInput() } // 事件 @@ -46,6 +49,10 @@ 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) { @@ -56,6 +63,43 @@ export default class TextEdit { this.mindMap.on('scale', this.onScale) } + // 创建一个隐藏的文本输入框 + createHiddenInput() { + if (this.hiddenInputEl) return + this.hiddenInputEl = document.createElement('input') + this.hiddenInputEl.type = 'text' + this.hiddenInputEl.style.cssText = ` + position: fixed; + left: -99999px; + top: -99999px; + ` + // 监听粘贴事件 + 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.hiddenInputEl.focus() + } + // 注册临时快捷键 registerTmpShortcut() { // 注册回车快捷键 @@ -96,7 +140,8 @@ export default class TextEdit { onScale() { if (!this.currentNode) return if (this.mindMap.richText) { - this.mindMap.richText.cacheEditingText = this.mindMap.richText.getEditText() + this.mindMap.richText.cacheEditingText = + this.mindMap.richText.getEditText() this.mindMap.richText.showTextEdit = false } else { this.cacheEditingText = this.getEditText() @@ -124,7 +169,9 @@ export default class TextEdit { let scale = this.mindMap.view.scale let lineHeight = node.style.merge('lineHeight') let fontSize = node.style.merge('fontSize') - let textLines = (this.cacheEditingText || node.nodeData.data.text).split(/\n/gim) + let textLines = (this.cacheEditingText || node.nodeData.data.text).split( + /\n/gim + ) let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true' node.style.domText(this.textEditNode, scale, isMultiLine) this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex @@ -134,9 +181,12 @@ export default class TextEdit { 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 * scale + 'px' + this.textEditNode.style.maxWidth = + this.mindMap.opt.textAutoWrapWidth * scale + 'px' if (isMultiLine && lineHeight !== 1) { - this.textEditNode.style.transform = `translateY(${-((lineHeight * fontSize - fontSize) / 2) * scale}px)` + this.textEditNode.style.transform = `translateY(${ + -((lineHeight * fontSize - fontSize) / 2) * scale + }px)` } this.showTextEdit = true // 选中文本 diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index 462b1913..99470427 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -433,4 +433,23 @@ export const getImageSize = src => { // 创建节点唯一的id export const createUid = () => { return uuidv4() -} \ No newline at end of file +} + +// 加载图片文件 +export const loadImage = imgFile => { + return new Promise((resolve, reject) => { + let fr = new FileReader() + fr.readAsDataURL(imgFile) + fr.onload = async e => { + let url = e.target.result + let size = await getImageSize(url) + resolve({ + url, + size + }) + } + fr.onerror = error => { + reject(error) + } + }) +} diff --git a/web/src/pages/Edit/components/Contextmenu.vue b/web/src/pages/Edit/components/Contextmenu.vue index b868777a..c2980860 100644 --- a/web/src/pages/Edit/components/Contextmenu.vue +++ b/web/src/pages/Edit/components/Contextmenu.vue @@ -55,7 +55,6 @@
{{ $t('contextmenu.pasteNode') }} @@ -123,7 +122,6 @@ export default { left: 0, top: 0, node: null, - copyData: null, type: '', isMousedown: false, mosuedownX: 0, @@ -180,10 +178,6 @@ export default { this.$bus.$on('expand_btn_click', this.hide) this.$bus.$on('svg_mousedown', this.onMousedown) this.$bus.$on('mouseup', this.onMouseup) - // 注册快捷键 - this.mindMap.keyCommand.addShortcut('Control+c', this.copy) - this.mindMap.keyCommand.addShortcut('Control+v', this.paste) - this.mindMap.keyCommand.addShortcut('Control+x', this.cut) }, beforeDestroy() { this.$bus.$off('node_contextmenu', this.show) @@ -192,10 +186,6 @@ export default { this.$bus.$off('expand_btn_click', this.hide) this.$bus.$on('svg_mousedown', this.onMousedown) this.$bus.$on('mouseup', this.onMouseup) - // 移除快捷键 - this.mindMap.keyCommand.removeShortcut('Control+c', this.copy) - this.mindMap.keyCommand.removeShortcut('Control+v', this.paste) - this.mindMap.keyCommand.removeShortcut('Control+x', this.cut) }, methods: { ...mapMutations(['setLocalConfig']), @@ -282,15 +272,13 @@ export default { } switch (key) { case 'COPY_NODE': - this.copyData = this.mindMap.renderer.copyNode() + this.mindMap.renderer.copy() break case 'CUT_NODE': - this.$bus.$emit('execCommand', key, copyData => { - this.copyData = copyData - }) + this.mindMap.renderer.cut() break case 'PASTE_NODE': - this.$bus.$emit('execCommand', key, this.copyData) + this.mindMap.renderer.paste() break case 'RETURN_CENTER': this.mindMap.view.reset() @@ -308,33 +296,6 @@ export default { break } this.hide() - }, - - /** - * @Author: 王林25 - * @Date: 2022-08-04 14:25:45 - * @Desc: 复制 - */ - copy() { - this.exec('COPY_NODE') - }, - - /** - * @Author: 王林25 - * @Date: 2022-08-04 14:26:43 - * @Desc: 粘贴 - */ - paste() { - this.exec('PASTE_NODE') - }, - - /** - * @Author: 王林25 - * @Date: 2022-08-04 14:27:32 - * @Desc: 剪切 - */ - cut() { - this.exec('CUT_NODE') } } } From 4ee458c509ae08f9523cb2a28bc96ffb763dbb34 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Mon, 24 Jul 2023 08:53:08 +0800 Subject: [PATCH 05/43] =?UTF-8?q?Demo=EF=BC=9A=E4=BF=AE=E5=A4=8D=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=AD=A3=E5=9C=A8=E7=BC=96=E8=BE=91=E6=97=B6=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=AF=8C=E6=96=87=E6=9C=AC=E7=BC=96=E8=BE=91=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E8=BE=93=E5=85=A5=E6=A1=86=E5=87=BA=E7=8E=B0=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/Edit/components/BaseStyle.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/pages/Edit/components/BaseStyle.vue b/web/src/pages/Edit/components/BaseStyle.vue index 3de1096a..04cb5d73 100644 --- a/web/src/pages/Edit/components/BaseStyle.vue +++ b/web/src/pages/Edit/components/BaseStyle.vue @@ -900,6 +900,7 @@ export default { // 切换是否开启节点富文本编辑 enableNodeRichTextChange(e) { + this.mindMap.renderer.textEdit.hideEditTextBox() this.setLocalConfig({ openNodeRichText: e }) From d5e4044fb22ac59da7916f56814fe0a350497792 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Mon, 24 Jul 2023 10:12:53 +0800 Subject: [PATCH 06/43] =?UTF-8?q?Demo=EF=BC=9A=E6=94=AF=E6=8C=81=E5=A4=9C?= =?UTF-8?q?=E9=97=B4=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/Edit/Index.vue | 113 +++++++++++++++++- web/src/pages/Edit/components/BaseStyle.vue | 18 ++- web/src/pages/Edit/components/Color.vue | 14 ++- web/src/pages/Edit/components/Count.vue | 15 ++- web/src/pages/Edit/components/Fullscreen.vue | 11 +- web/src/pages/Edit/components/MouseAction.vue | 11 +- .../Edit/components/NavigatorToolbar.vue | 26 +++- web/src/pages/Edit/components/Outline.vue | 7 +- web/src/pages/Edit/components/Scale.vue | 15 ++- web/src/pages/Edit/components/ShortcutKey.vue | 26 +++- web/src/pages/Edit/components/Sidebar.vue | 19 ++- .../pages/Edit/components/SidebarTrigger.vue | 18 ++- web/src/pages/Edit/components/Structure.vue | 10 +- web/src/pages/Edit/components/Style.vue | 26 +++- web/src/pages/Edit/components/Theme.vue | 10 +- web/src/pages/Edit/components/Toolbar.vue | 32 ++++- web/src/store.js | 8 +- 17 files changed, 348 insertions(+), 31 deletions(-) diff --git a/web/src/pages/Edit/Index.vue b/web/src/pages/Edit/Index.vue index 96091a77..d6e8c8c0 100644 --- a/web/src/pages/Edit/Index.vue +++ b/web/src/pages/Edit/Index.vue @@ -1,5 +1,5 @@