Compare commits

...

64 Commits
0.8.0 ... 0.9.1

Author SHA1 Message Date
wanglin2
9fe94bfc21 打包0.9.1 2023-12-05 09:34:51 +08:00
wanglin2
6be6f6ee8a Doc 2023-12-05 09:31:13 +08:00
wanglin2
83ce090402 Fix:修复在节点文本编辑中和关联线文本编辑中时销毁思维导图文本编辑框未被销毁的问题 2023-12-05 09:02:41 +08:00
wanglin2
250ff30704 Demo:支持配置创建新节点时的行为 2023-12-04 11:25:04 +08:00
wanglin2
55d7d0a846 Feat:新增创建新节点的行为配置选项 2023-12-04 11:07:26 +08:00
wanglin2
0b3d2cedbd Fix:重新加上因新功能而丢失的旧功能:新创建的节点默认全选 2023-12-04 10:18:17 +08:00
wanglin2
de025d9bc7 Fix:修复不在格式刷时点击画布和节点也会触发painter_end事件的问题 2023-12-04 09:57:29 +08:00
wanglin2
79a39f993d Fix:修复点击节点也会触发node_dragend事件的问题 2023-12-04 09:51:56 +08:00
wanglin2
0fa731c3a2 Fix:修复在节点编辑状态中通过鼠标滚轮缩放画布再推出节点编辑后快捷键生效的问题 2023-12-01 10:23:32 +08:00
wanglin2
34eece8fcf Fix:修复节点处于编辑状态时,通过鼠标滚动移动画布后编辑框和节点脱离的问题 2023-12-01 09:59:33 +08:00
wanglin2
790662ad0c Demo: 增加快捷键提示 2023-12-01 09:52:17 +08:00
wanglin2
7d83a3635f Feat:在鼠标滚轮行为为上下移动画布时,支持按住Ctrl键改为放大缩小画布 2023-12-01 09:49:02 +08:00
wanglin2
546cf27b33 Feat:鼠标滚轮行为默认改为上下移动画布;改为默认向前滚动放大画布,向后缩小 2023-12-01 09:48:14 +08:00
wanglin2
e59d419708 Feat:只读模式下搜索时给当前匹配到的节点增加高亮效果 2023-11-30 17:40:12 +08:00
wanglin2
2d50106ce7 Feat:新增创建新节点时默认不聚焦新节点的配置选项 2023-11-29 08:50:13 +08:00
wanglin2
70c32e3c74 Fix:1.按住Ctrl键时禁用节点双击事件;2.优化节点激活事件的派发,激活节点未改变时不派发事件,短时间派发多次事件时跳过中间事件 2023-11-28 17:37:44 +08:00
wanglin2
a0f56473ee Fix:修复批量执行类添加同名任务时任务未更新为最小任务的问题 2023-11-28 17:36:13 +08:00
wanglin2
51dcb1f54f Fix:修复自定义节点内容时导出图片、svg、pdf报错的问题 2023-11-27 17:15:35 +08:00
wanglin2
9e34fd6174 Doc update 2023-11-24 13:45:03 +08:00
wanglin2
3b47d11b21 打包0.9.0 2023-11-23 14:22:43 +08:00
wanglin2
6e91df9d03 Doc: update 2023-11-23 14:16:06 +08:00
wanglin2
4c8f1bd69c Feat:新增鼠标移入概要时高亮其所属的节点 2023-11-23 11:25:37 +08:00
wanglin2
1f993518fd Merge branch 'feature3' into feature 2023-11-22 16:58:57 +08:00
wanglin2
7f9a1e9309 Fix:修复部分事件在思维导图卸载后未取消监听的问题 2023-11-22 16:23:04 +08:00
wanglin2
5df4a7edb8 Feat:导入和导出xmind文件时,支持处理区间概要 2023-11-22 14:23:21 +08:00
wanglin2
ddd578d773 Doc: update 2023-11-21 17:54:12 +08:00
wanglin2
c8e63e433c Feat:支持对同一个节点的多个子节点添加概要 2023-11-21 17:33:43 +08:00
wanglin2
cadd159a46 Feat:给节点实例增加isParent方法 2023-11-21 10:41:31 +08:00
wanglin2
d32588763c Feat:将节点实例的isParent方法改名为isAncestor 2023-11-21 10:40:00 +08:00
wanglin2
6723250266 Fix:修复同时给存在上下级关系的节点添加概要时概要重叠的问题 2023-11-21 10:32:19 +08:00
街角小林
41ca8f8e46 Merge pull request #407 from doyouhaobaby/doyouhaobaby-patch-1
Xmind 导入支持概要,其中xmind8修复将概要弄成了主题,导出改为最新版本xmind结构
2023-11-21 09:40:18 +08:00
wanglin2
a6d04ffa91 Feat:导出png的方法新增压缩参数;优化大数据量节点导出pdf时体积过大的问题 2023-11-20 18:06:14 +08:00
wanglin2
631892d785 Demo:导出操作增加loading 2023-11-20 17:49:30 +08:00
wanglin2
3d7f0fcbe7 Fix:修复导出pdf时异步控制丢失的问题 2023-11-20 17:49:05 +08:00
wanglin2
c716ec7294 Fix:修复节点数量很多的情况下导出pdf报错的问题 2023-11-20 17:48:19 +08:00
wanglin2
4db6bda193 修复先给自身添加概要,再给下级添加概要会出现概要重叠的问题 2023-11-20 17:05:00 +08:00
wanglin2
bc2a5b214f Demo:修复同时选中多个节点添加图标时,所有节点图标都会统一为第一个节点的图标的问题 2023-11-20 16:46:00 +08:00
wanglin2
879de57b49 Fix:修复富文本模式下节点内容存在 时导出为图片出错的问题 2023-11-20 16:26:35 +08:00
wanglin2
0d4cbc7344 Demo:修复节点内容为html标签时大纲无法显示和编辑的问题 2023-11-20 15:46:33 +08:00
wanglin2
217f5ee95d Fix:修复只读模式下可以全选节点的问题 2023-11-20 15:15:57 +08:00
wanglin2
ad98c0f229 Demo:修复只读模式下仍旧可以替换和编辑大纲的问题 2023-11-20 15:15:38 +08:00
wanglin2
9a7f827301 Fix:修复缩放画布时图标浮层和备注浮层和节点脱离的问题 2023-11-20 14:58:42 +08:00
wanglin2
211fa183d3 Fix:修复在safari浏览器中运行时,页面空白且控制台抛出异常的问题 2023-11-20 14:07:09 +08:00
wanglin2
c0e0fb23e4 Demo:修改本地文件操作功能不可用的提示 2023-11-20 14:00:32 +08:00
wanglin2
e0d46055a7 Feat:新增禁止双指缩放画布的配置信息 2023-11-20 11:46:46 +08:00
wanglin2
a1ec72b401 Feat:新增禁止拖动画布的配置选项 2023-11-20 11:39:08 +08:00
wanglin2
ffc7bf5b7e Merge branch 'feature' of https://github.com/wanglin2/mind-map into feature 2023-11-20 11:04:52 +08:00
wanglin2
4e6e0e2221 Fix:修复历史记录数据概要节点的激活状态未被删除的问题 2023-11-20 11:04:23 +08:00
小牛仔
a1bbe543ba 修复判断 2023-11-17 17:03:35 +08:00
小牛仔
48b5ff5f1c 修复判断 2023-11-17 16:56:15 +08:00
小牛仔
13dc61a585 旧版本xmind8支持导入概要,老的注解被搞成了一个主题了 2023-11-17 16:20:02 +08:00
小牛仔
6e476489f5 feat:Xmind导入支持概要,导入支持概要并且导出为最新版本的xmind格式 2023-11-17 13:47:04 +08:00
wanglin2
e6b82db674 Doc: update 2023-11-04 15:10:47 +08:00
wanglin2
4c60c52a8f 打包demo 2023-11-03 10:33:01 +08:00
wanglin2
29ed8f7a8d Merge branch 'main' into feature 2023-11-03 10:32:07 +08:00
街角小林
4b880e13a5 Merge pull request #373 from maxchang3/docs-typo
chore: typo
2023-11-03 10:30:54 +08:00
wanglin2
b5a08a2414 Doc: update 2023-11-03 10:27:04 +08:00
wanglin2
57e9379f49 Doc: update 2023-11-03 10:01:01 +08:00
MaxChang3
134fd69ffa chore: typo 2023-10-24 12:31:59 +08:00
wanglin2
e2830ccfb1 Doc: update 2023-10-20 11:45:00 +08:00
wanglin2
b0dd90f5b3 Feat:节点增加getPureData方法获取该节点的纯数据 2023-10-19 10:47:53 +08:00
wanglin2
32fc6937d2 Fix:修复极少数情况下输入中文时文本样式丢失的问题 2023-10-18 15:34:14 +08:00
wanglin2
23a3e26800 打包0.8.0-fix.1 2023-10-18 14:34:35 +08:00
wanglin2
c7b0cbc128 Fix:修复粘贴方式创建新节点时如果粘贴的内容带有<>等html标签符号时新创建的节点内容为空的问题 2023-10-18 14:30:00 +08:00
101 changed files with 30730 additions and 846 deletions

View File

@@ -210,4 +210,24 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/天清如愿.jpg" style="width: 50px;height: 50px;" />
<span>天清如愿</span>
</span>
<span>
<img src="./web/src/assets/avatar/敬明朗.jpg" style="width: 50px;height: 50px;" />
<span>敬明朗</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>飞箭</span>
</span>
<span>
<img src="./web/src/assets/avatar/戚永峰.png" style="width: 50px;height: 50px;" />
<span>戚永峰</span>
</span>
<span>
<img src="./web/src/assets/avatar/moom.jpg" style="width: 50px;height: 50px;" />
<span>moom</span>
</span>
<span>
<img src="./web/src/assets/avatar/张扬.png" style="width: 50px;height: 50px;" />
<span>张扬</span>
</span>
</p>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
window.externalPublicPath = './dist/'
// 接管应用
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?d19203eaeb8de9913fb3" rel="stylesheet"><link href="dist/css/app.css?d19203eaeb8de9913fb3" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?7820598bfce2a2653e5c" rel="stylesheet"><link href="dist/css/app.css?7820598bfce2a2653e5c" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
@@ -66,4 +66,4 @@
// 可以通过window.$bus.$on()来监听应用的一些事件
// 实例化页面
window.initApp()
}</script><script src="dist/js/chunk-vendors.js?d19203eaeb8de9913fb3"></script><script src="dist/js/app.js?d19203eaeb8de9913fb3"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?7820598bfce2a2653e5c"></script><script src="dist/js/app.js?7820598bfce2a2653e5c"></script></body></html>

View File

@@ -486,6 +486,13 @@ class MindMap {
// 销毁
destroy() {
this.emit('beforeDestroy')
// 清除节点编辑框
this.renderer.textEdit.hideEditTextBox()
// 清除关联线文字编辑框
if (this.associativeLine) {
this.associativeLine.hideEditTextBox()
}
// 移除插件
;[...MindMap.pluginList].forEach(plugin => {
if (this[plugin.instanceName].beforePluginDestroy) {

View File

@@ -1,11 +1,11 @@
{
"name": "simple-mind-map",
"version": "0.7.2",
"version": "0.8.0-fix.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.7.2",
"version": "0.8.0-fix.1",
"license": "MIT",
"dependencies": {
"@svgdotjs/svg.js": "^3.0.16",
@@ -28,11 +28,11 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
@@ -160,9 +160,9 @@
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
"node_modules/@types/raf": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz",
"integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"optional": true
},
"node_modules/@types/unist": {
@@ -394,6 +394,12 @@
"node": ">=10.0.0"
}
},
"node_modules/canvg/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -459,9 +465,9 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/core-js": {
"version": "3.27.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz",
"integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==",
"version": "3.33.3",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz",
"integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==",
"hasInstallScript": true,
"optional": true,
"funding": {
@@ -599,9 +605,9 @@
}
},
"node_modules/dompurify": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
"integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==",
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz",
"integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==",
"optional": true
},
"node_modules/enhanced-resolve": {
@@ -2139,9 +2145,9 @@
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.4.3",
@@ -2326,9 +2332,9 @@
}
},
"node_modules/stackblur-canvas": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz",
"integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==",
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz",
"integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==",
"optional": true,
"engines": {
"node": ">=0.1.14"
@@ -2665,11 +2671,11 @@
},
"dependencies": {
"@babel/runtime": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
"requires": {
"regenerator-runtime": "^0.13.11"
"regenerator-runtime": "^0.14.0"
}
},
"@eslint/eslintrc": {
@@ -2765,9 +2771,9 @@
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
"@types/raf": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz",
"integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"optional": true
},
"@types/unist": {
@@ -2914,6 +2920,14 @@
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true
}
}
},
"chalk": {
@@ -2962,9 +2976,9 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"core-js": {
"version": "3.27.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz",
"integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==",
"version": "3.33.3",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz",
"integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==",
"optional": true
},
"core-util-is": {
@@ -3061,9 +3075,9 @@
}
},
"dompurify": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
"integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==",
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz",
"integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==",
"optional": true
},
"enhanced-resolve": {
@@ -4100,9 +4114,9 @@
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"regexp.prototype.flags": {
"version": "1.4.3",
@@ -4221,9 +4235,9 @@
}
},
"stackblur-canvas": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz",
"integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==",
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz",
"integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==",
"optional": true
},
"string_decoder": {

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.8.0",
"version": "0.9.1",
"description": "一个简单的web在线思维导图",
"authors": [
{

View File

@@ -229,6 +229,11 @@ export const CONSTANTS = {
SCROLL_BAR_DIR: {
VERTICAL: 'vertical',
HORIZONTAL: 'horizontal'
},
CREATE_NEW_NODE_BEHAVIOR: {
DEFAULT: 'default',
NOT_ACTIVE: 'notActive',
ACTIVE_ONLY: 'activeOnly'
}
}
@@ -351,3 +356,14 @@ export const cssContent = `
stroke-width: 2;
}
`
// html自闭合标签列表
export const selfCloseTagList = [
'img',
'br',
'hr',
'input',
'link',
'meta',
'area'
]

View File

@@ -56,11 +56,11 @@ export const defaultOpt = {
// 可以传一个函数,回调参数为事件对象
customHandleMousewheel: null,
// 鼠标滚动的行为如果customHandleMousewheel传了自定义函数这个属性不生效
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM, // zoom放大缩小、move上下移动
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.MOVE, // zoom放大缩小、move上下移动
// 当mousewheelAction设为move时可以通过该属性控制鼠标滚动一下视图移动的步长单位px
mousewheelMoveStep: 100,
// 当mousewheelAction设为zoom时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来
mousewheelZoomActionReverse: false,
// 当mousewheelAction设为zoom时或者按住Ctrl键时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来
mousewheelZoomActionReverse: true,
// 默认插入的二级节点的文字
defaultInsertSecondLevelNodeText: '二级节点',
// 默认插入的二级以下节点的文字
@@ -160,6 +160,9 @@ export const defaultOpt = {
customHandleClipboardText: null,
// 禁止鼠标滚轮缩放你仍旧可以使用api进行缩放
disableMouseWheelZoom: false,
// 禁止双指缩放你仍旧可以使用api进行缩放
// 需要注册TouchEvent插件后生效
disableTouchZoom: false,
// 错误处理函数
errorHandler: (code, error) => {
console.error(code, error)
@@ -207,8 +210,8 @@ export const defaultOpt = {
tagsColorMap: {},
// 节点协作样式配置
cooperateStyle: {
avatarSize: 22,// 头像大小
fontSize: 12,// 如果是文字头像,那么文字的大小
avatarSize: 22, // 头像大小
fontSize: 12 // 如果是文字头像,那么文字的大小
},
// 关联线是否始终显示在节点上层
// false即创建关联线和激活关联线时处于最顶层其他情况下处于节点下方
@@ -219,5 +222,19 @@ export const defaultOpt = {
// 可以传递一个函数返回promiseresolve代表根据换行分割reject代表忽略换行
handleIsSplitByWrapOnPasteCreateNewNode: null,
// 多少时间内只允许添加一次历史记录避免添加没有必要的中间状态单位ms
addHistoryTime: 100
addHistoryTime: 100,
// 是否禁止拖动画布
isDisableDrag: false,
// 鼠标移入概要高亮所属节点时的高亮框样式
highlightNodeBoxStyle: {
stroke: 'rgb(94, 200, 248)',
fill: 'transparent'
},
// 创建新节点时的行为
/*
DEFAULT :默认会激活新创建的节点,并且进入编辑模式。如果同时创建了多个新节点,那么只会激活而不会进入编辑模式
NOT_ACTIVE : 不激活新创建的节点
ACTIVE_ONLY : 只激活新创建的节点,不进入编辑模式
*/
createNewNodeBehavior: CONSTANTS.CREATE_NEW_NODE_BEHAVIOR.DEFAULT
}

View File

@@ -38,6 +38,7 @@ export default class KeyCommand {
// 绑定事件
bindEvent() {
this.onKeydown = this.onKeydown.bind(this)
// 只有当鼠标在画布内才响应快捷键
this.mindMap.on('svg_mouseenter', () => {
this.isInSvg = true
@@ -55,25 +56,36 @@ export default class KeyCommand {
}
this.isInSvg = false
})
window.addEventListener('keydown', e => {
if (
this.isPause ||
(this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
) {
return
}
Object.keys(this.shortcutMap).forEach(key => {
if (this.checkKey(e, key)) {
// 粘贴事件不组织因为要监听paste事件
if (!this.checkKey(e, 'Control+v')) {
e.stopPropagation()
e.preventDefault()
}
this.shortcutMap[key].forEach(fn => {
fn()
})
window.addEventListener('keydown', this.onKeydown)
this.mindMap.on('beforeDestroy', () => {
this.unBindEvent()
})
}
// 解绑事件
unBindEvent() {
window.removeEventListener('keydown', this.onKeydown)
}
// 按键事件
onKeydown(e) {
if (
this.isPause ||
(this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
) {
return
}
Object.keys(this.shortcutMap).forEach(key => {
if (this.checkKey(e, key)) {
// 粘贴事件不组织因为要监听paste事件
if (!this.checkKey(e, 'Control+v')) {
e.stopPropagation()
e.preventDefault()
}
})
this.shortcutMap[key].forEach(fn => {
fn()
})
}
})
}

View File

@@ -133,18 +133,10 @@ class Event extends EventEmitter {
e.stopPropagation()
e.preventDefault()
let dir
// 解决mac触控板双指缩放方向相反的问题
if (e.ctrlKey) {
if (e.deltaY > 0) dir = CONSTANTS.DIR.UP
if (e.deltaY < 0) dir = CONSTANTS.DIR.DOWN
if (e.deltaX > 0) dir = CONSTANTS.DIR.LEFT
if (e.deltaX < 0) dir = CONSTANTS.DIR.RIGHT
} else {
if ((e.wheelDeltaY || e.detail) > 0) dir = CONSTANTS.DIR.UP
if ((e.wheelDeltaY || e.detail) < 0) dir = CONSTANTS.DIR.DOWN
if ((e.wheelDeltaX || e.detail) > 0) dir = CONSTANTS.DIR.LEFT
if ((e.wheelDeltaX || e.detail) < 0) dir = CONSTANTS.DIR.RIGHT
}
if (e.deltaY < 0) dir = CONSTANTS.DIR.UP
if (e.deltaY > 0) dir = CONSTANTS.DIR.DOWN
if (e.deltaX < 0) dir = CONSTANTS.DIR.LEFT
if (e.deltaX > 0) dir = CONSTANTS.DIR.RIGHT
// 判断是否是触控板
let isTouchPad = false
// mac、windows

View File

@@ -23,11 +23,15 @@ import {
getNodeDataIndex,
getNodeIndexInNodeList,
setDataToClipboard,
getDataFromClipboard
getDataFromClipboard,
htmlEscape,
parseAddGeneralizationNodeList,
checkNodeListIsEqual
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
import { CONSTANTS, ERROR_TYPES } from '../../constants/constant'
import { Polygon } from '@svgdotjs/svg.js'
// 布局列表
const layouts = {
@@ -82,6 +86,11 @@ class Render {
this.beingPasteText = ''
this.beingPasteImgSize = 0
this.currentBeingPasteType = ''
// 节点高亮框
this.highlightBoxNode = null
// 上一次节点激活数据
this.lastActiveNode = null
this.lastActiveNodeList = []
// 布局
this.setLayout()
// 绑定事件
@@ -330,6 +339,21 @@ class Render {
})
}
// 派发节点激活事件
emitNodeActiveEvent(node = null, activeNodeList = [...this.activeNodeList]) {
let isChange = false
isChange = this.lastActiveNode !== node
if (!isChange) {
isChange = !checkNodeListIsEqual(this.lastActiveNodeList, activeNodeList)
}
if (!isChange) return
this.lastActiveNode = node
this.lastActiveNodeList = [...activeNodeList]
this.mindMap.batchExecution.push('emitNodeActiveEvent', () => {
this.mindMap.emit('node_active', node, activeNodeList)
})
}
// 鼠标点击画布时清空当前激活节点列表
clearActiveNodeListOnDrawClick(e, eventType) {
if (this.activeNodeList.length <= 0) return
@@ -426,7 +450,7 @@ class Render {
return
}
this.clearActiveNodeList()
this.mindMap.emit('node_active', null, [])
this.emitNodeActiveEvent(null, [])
}
// 清除当前激活的节点列表
@@ -463,6 +487,7 @@ class Render {
// 全选
selectAll() {
if (this.mindMap.opt.readonly) return
walk(
this.root,
null,
@@ -499,6 +524,36 @@ class Render {
}
}
// 获取创建新节点的行为
getNewNodeBehavior(openEdit = false, handleMultiNodes = false) {
const { createNewNodeBehavior } = this.mindMap.opt
let focusNewNode = false // 是否激活新节点
let inserting = false // 新节点是否进入编辑模式
switch (createNewNodeBehavior) {
// 默认会激活新创建的节点,并且进入编辑模式。如果同时创建了多个新节点,那么只会激活而不会进入编辑模式
case CONSTANTS.CREATE_NEW_NODE_BEHAVIOR.DEFAULT:
focusNewNode = handleMultiNodes || !openEdit
inserting = handleMultiNodes ? false : openEdit // 如果同时对多个节点插入子节点,那么无需进入编辑模式
break
// 不激活新创建的节点
case CONSTANTS.CREATE_NEW_NODE_BEHAVIOR.NOT_ACTIVE:
focusNewNode = false
inserting = false
break
// 只激活新创建的节点,不进入编辑模式
case CONSTANTS.CREATE_NEW_NODE_BEHAVIOR.ACTIVE_ONLY:
focusNewNode = true
inserting = false
break
default:
break
}
return {
focusNewNode,
inserting
}
}
// 插入同级节点
insertNode(
openEdit = true,
@@ -518,11 +573,15 @@ class Render {
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: handleMultiNodes || !openEdit // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态
isActive: focusNewNode // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态
}
// 动态指定的子节点数据也需要添加相关属性
appointChildren = addDataToAppointNodes(appointChildren, {
@@ -541,7 +600,7 @@ class Render {
// 计算插入位置
const index = getNodeDataIndex(node)
const newNodeData = {
inserting: handleMultiNodes ? false : openEdit, // 如果同时对多个节点插入子节点,那么无需进入编辑模式,
inserting,
data: {
text: text,
...params,
@@ -553,7 +612,7 @@ class Render {
parent.nodeData.children.splice(index + 1, 0, newNodeData)
})
// 如果同时对多个节点插入子节点,需要清除原来激活的节点
if (handleMultiNodes || !openEdit) {
if (focusNewNode) {
this.clearActiveNodeList()
}
this.mindMap.render()
@@ -569,11 +628,12 @@ class Render {
this.textEdit.hideEditTextBox()
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const isRichText = !!this.mindMap.richText
const { focusNewNode } = this.getNewNodeBehavior(false, true)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: true
isActive: focusNewNode
}
nodeList = addDataToAppointNodes(nodeList, params)
list.forEach(node => {
@@ -589,7 +649,9 @@ class Render {
)
parent.nodeData.children.splice(index + 1, 0, ...newNodeList)
})
this.clearActiveNodeList()
if (focusNewNode) {
this.clearActiveNodeList()
}
this.mindMap.render()
}
@@ -612,11 +674,15 @@ class Render {
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: handleMultiNodes || !openEdit // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态
isActive: focusNewNode
}
// 动态指定的子节点数据也需要添加相关属性
appointChildren = addDataToAppointNodes(appointChildren, {
@@ -633,7 +699,7 @@ class Render {
? defaultInsertSecondLevelNodeText
: defaultInsertBelowSecondLevelNodeText
const newNode = {
inserting: handleMultiNodes ? false : openEdit, // 如果同时对多个节点插入子节点,那么无需进入编辑模式
inserting,
data: {
text: text,
uid: createUid(),
@@ -649,7 +715,7 @@ class Render {
})
})
// 如果同时对多个节点插入子节点,需要清除原来激活的节点
if (handleMultiNodes || !openEdit) {
if (focusNewNode) {
this.clearActiveNodeList()
}
this.mindMap.render()
@@ -665,11 +731,12 @@ class Render {
this.textEdit.hideEditTextBox()
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const isRichText = !!this.mindMap.richText
const { focusNewNode } = this.getNewNodeBehavior(false, true)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: true
isActive: focusNewNode
}
childList = addDataToAppointNodes(childList, params)
list.forEach(node => {
@@ -686,7 +753,9 @@ class Render {
expand: true
})
})
this.clearActiveNodeList()
if (focusNewNode) {
this.clearActiveNodeList()
}
this.mindMap.render()
}
@@ -704,11 +773,15 @@ class Render {
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: handleMultiNodes || !openEdit // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态
isActive: focusNewNode
}
list.forEach(node => {
if (node.isGeneralization || node.isRoot) {
@@ -719,7 +792,7 @@ class Render {
? defaultInsertSecondLevelNodeText
: defaultInsertBelowSecondLevelNodeText
const newNode = {
inserting: handleMultiNodes ? false : openEdit, // 如果同时对多个节点插入子节点,那么无需进入编辑模式
inserting,
data: {
text: text,
uid: createUid(),
@@ -734,7 +807,7 @@ class Render {
parent.nodeData.children.splice(index, 1, newNode)
})
// 如果同时对多个节点插入子节点,需要清除原来激活的节点
if (handleMultiNodes || !openEdit) {
if (focusNewNode) {
this.clearActiveNodeList()
}
this.mindMap.render()
@@ -885,9 +958,12 @@ class Render {
Array.isArray(smmData) ? smmData : [smmData]
)
} else {
const textArr = text.split(/\r?\n|(?<!\n)\r/g).filter(item => {
return !!item
})
text = htmlEscape(text)
const textArr = text
.split(new RegExp('\r?\n|(?<!\n)\r', 'g'))
.filter(item => {
return !!item
})
// 判断是否需要根据换行自动分割节点
if (textArr.length > 1 && handleIsSplitByWrapOnPasteCreateNewNode) {
handleIsSplitByWrapOnPasteCreateNewNode()
@@ -1026,14 +1102,7 @@ class Render {
let node = list[i]
if (isAppointNodes) list.splice(i, 1)
if (node.isGeneralization) {
// 删除概要节点
this.mindMap.execCommand(
'SET_NODE_DATA',
node.generalizationBelongNode,
{
generalization: null
}
)
this.deleteNodeGeneralization(node)
this.removeNodeFromActiveList(node)
i--
} else {
@@ -1052,6 +1121,23 @@ class Render {
this.mindMap.render()
}
// 删除概要节点,即从所属节点里删除该概要
deleteNodeGeneralization(node) {
const targetNode = node.generalizationBelongNode
const index = targetNode.getGeneralizationNodeIndex(node)
let generalization = targetNode.getData('generalization')
if (Array.isArray(generalization)) {
generalization.splice(index, 1)
} else {
generalization = null
}
// 删除概要节点
this.mindMap.execCommand('SET_NODE_DATA', targetNode, {
generalization
})
this.closeHighlightNode()
}
// 仅删除当前节点
removeCurrentNode(appointNodes = []) {
appointNodes = formatDataToArray(appointNodes)
@@ -1069,13 +1155,7 @@ class Render {
let node = list[i]
if (node.isGeneralization) {
// 删除概要节点
this.mindMap.execCommand(
'SET_NODE_DATA',
node.generalizationBelongNode,
{
generalization: null
}
)
this.deleteNodeGeneralization(node)
} else {
const parent = node.parent
const index = getNodeDataIndex(node)
@@ -1397,21 +1477,44 @@ class Render {
if (this.activeNodeList.length <= 0) {
return
}
this.activeNodeList.forEach(node => {
if (node.getData('generalization') || node.isRoot) {
return
}
this.mindMap.execCommand('SET_NODE_DATA', node, {
generalization: data || {
const nodeList = this.activeNodeList.filter(node => {
return (
!node.isRoot &&
!node.isGeneralization &&
!node.checkHasSelfGeneralization()
)
})
const list = parseAddGeneralizationNodeList(nodeList)
list.forEach(item => {
const newData = {
...(data || {
text: this.mindMap.opt.defaultGeneralizationText
}),
range: item.range || null
}
let generalization = item.node.getData('generalization')
if (generalization) {
if (Array.isArray(generalization)) {
generalization.push(newData)
} else {
generalization = [generalization, newData]
}
} else {
generalization = [newData]
}
this.mindMap.execCommand('SET_NODE_DATA', item.node, {
generalization
})
// 插入子节点时自动展开子节点
node.setData({
item.node.setData({
expand: true
})
})
this.mindMap.render()
this.mindMap.render(() => {
// 修复祖先节点存在概要时位置未更新的问题
// 修复同时给存在上下级关系的节点添加概要时重叠的问题
this.mindMap.render()
})
}
// 删除节点概要
@@ -1420,7 +1523,7 @@ class Render {
return
}
this.activeNodeList.forEach(node => {
if (!node.getData('generalization')) {
if (!node.checkHasGeneralization()) {
return
}
this.mindMap.execCommand('SET_NODE_DATA', node, {
@@ -1428,6 +1531,7 @@ class Render {
})
})
this.mindMap.render()
this.closeHighlightNode()
}
// 设置节点自定义位置
@@ -1565,9 +1669,58 @@ class Render {
return res
}
// 派发节点激活改变事件
emitNodeActiveEvent() {
this.mindMap.emit('node_active', null, [...this.activeNodeList])
// 高亮节点或子节点
highlightNode(node, range) {
const { highlightNodeBoxStyle = {} } = this.mindMap.opt
if (!this.highlightBoxNode) {
this.highlightBoxNode = new Polygon()
.stroke({
color: highlightNodeBoxStyle.stroke || 'transparent'
})
.fill({
color: highlightNodeBoxStyle.fill || 'transparent'
})
}
let minx = Infinity,
miny = Infinity,
maxx = -Infinity,
maxy = -Infinity
if (range) {
const children = node.children.slice(range[0], range[1] + 1)
children.forEach(child => {
if (child.left < minx) {
minx = child.left
}
if (child.top < miny) {
miny = child.top
}
const right = child.left + child.width
const bottom = child.top + child.height
if (right > maxx) {
maxx = right
}
if (bottom > maxy) {
maxy = bottom
}
})
} else {
minx = node.left
miny = node.top
maxx = node.left + node.width
maxy = node.top + node.height
}
this.highlightBoxNode.plot([
[minx, miny],
[maxx, miny],
[maxx, maxy],
[minx, maxy]
])
this.mindMap.otherDraw.add(this.highlightBoxNode)
}
// 关闭高亮
closeHighlightNode() {
this.highlightBoxNode.remove()
}
}

View File

@@ -5,7 +5,7 @@ import {
selectAllInput,
htmlEscape
} from '../../utils'
import { ERROR_TYPES } from '../../constants/constant'
import { ERROR_TYPES, CONSTANTS } from '../../constants/constant'
// 节点文字编辑类
export default class TextEdit {
@@ -28,8 +28,11 @@ export default class TextEdit {
bindEvent() {
this.show = this.show.bind(this)
this.onScale = this.onScale.bind(this)
this.onKeydown = this.onKeydown.bind(this)
// 节点双击事件
this.mindMap.on('node_dblclick', this.show)
this.mindMap.on('node_dblclick', (node, e, isInserting) => {
this.show({ node, e, isInserting })
})
// 点击事件
this.mindMap.on('draw_click', () => {
// 隐藏文本编辑框
@@ -53,24 +56,50 @@ export default class TextEdit {
this.mindMap.on('before_node_active', () => {
this.hideEditTextBox()
})
// 鼠标滚动事件
this.mindMap.on('mousewheel', () => {
if (
this.mindMap.opt.mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.MOVE
) {
this.hideEditTextBox()
}
})
// 注册编辑快捷键
this.mindMap.keyCommand.addShortcut('F2', () => {
if (this.renderer.activeNodeList.length <= 0) {
return
}
this.show(this.renderer.activeNodeList[0])
this.show({
node: this.renderer.activeNodeList[0]
})
})
this.mindMap.on('scale', this.onScale)
// // 监听按键事件,判断是否自动进入文本编辑模式
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]
// 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式
if (node && this.checkIsAutoEnterTextEditKey(e)) {
this.show(node, e, false, true)
}
window.addEventListener('keydown', this.onKeydown)
}
this.mindMap.on('beforeDestroy', () => {
this.unBindEvent()
})
}
// 解绑事件
unBindEvent() {
window.removeEventListener('keydown', this.onKeydown)
}
// 按键事件
onKeydown(e) {
const activeNodeList = this.mindMap.renderer.activeNodeList
if (activeNodeList.length <= 0 || activeNodeList.length > 1) return
const node = activeNodeList[0]
// 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式
if (node && this.checkIsAutoEnterTextEditKey(e)) {
this.show({
node,
e,
isInserting: false,
isFromKeyDown: true
})
}
}
@@ -100,12 +129,17 @@ export default class TextEdit {
// 显示文本编辑框
// isInserting是否是刚创建的节点
// isFromKeyDown是否是在按键事件进入的编辑
async show(node, e, isInserting = false, isFromKeyDown = false) {
async show({
node,
isInserting = false,
isFromKeyDown = false,
isFromScale = false
}) {
// 使用了自定义节点内容那么不响应编辑事件
if (node.isUseCustomNodeContent()) {
return
}
let { beforeTextEdit } = this.mindMap.opt
const { beforeTextEdit } = this.mindMap.opt
if (typeof beforeTextEdit === 'function') {
let isShow = false
try {
@@ -117,14 +151,21 @@ export default class TextEdit {
if (!isShow) return
}
this.currentNode = node
let { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
const { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
this.mindMap.view.translateXY(offsetLeft, offsetTop)
let rect = node._textData.node.node.getBoundingClientRect()
const rect = node._textData.node.node.getBoundingClientRect()
const params = {
node,
rect,
isInserting,
isFromKeyDown,
isFromScale
}
if (this.mindMap.richText) {
this.mindMap.richText.showEditText(node, rect, isInserting, isFromKeyDown)
this.mindMap.richText.showEditText(params)
return
}
this.showEditTextBox(node, rect, isInserting, isFromKeyDown)
this.showEditTextBox(params)
}
// 处理画布缩放
@@ -138,15 +179,20 @@ export default class TextEdit {
this.cacheEditingText = this.getEditText()
this.showTextEdit = false
}
this.show(this.currentNode)
this.show({
node: this.currentNode,
isFromScale: true
})
}
// 显示文本编辑框
showEditTextBox(node, rect, isInserting, isFromKeyDown) {
showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
if (this.showTextEdit) return
const { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText } =
this.mindMap.opt
this.mindMap.emit('before_show_text_edit')
if (!isFromScale) {
this.mindMap.emit('before_show_text_edit')
}
this.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')

View File

@@ -1,6 +1,6 @@
import Style from './Style'
import Shape from './Shape'
import { G, ForeignObject, SVG, Rect } from '@svgdotjs/svg.js'
import { G, ForeignObject, Rect } from '@svgdotjs/svg.js'
import nodeGeneralizationMethods from './nodeGeneralization'
import nodeExpandBtnMethods from './nodeExpandBtn'
import nodeCommandWrapsMethods from './nodeCommandWraps'
@@ -8,6 +8,7 @@ import nodeCreateContentsMethods from './nodeCreateContents'
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
import nodeCooperateMethods from './nodeCooperate'
import { CONSTANTS } from '../../../constants/constant'
import { copyNodeTree } from '../../../utils/index'
// 节点类
class Node {
@@ -73,6 +74,7 @@ class Node {
this._tagData = null
this._noteData = null
this.noteEl = null
this.noteContentIsShow = false
this._expandBtn = null
this._lastExpandBtnType = null
this._showExpandBtn = false
@@ -81,8 +83,7 @@ class Node {
this._fillExpandNode = null
this._userListGroup = null
this._lines = []
this._generalizationLine = null
this._generalizationNode = null
this._generalizationList = []
this._unVisibleRectRegionNode = null
this._isMouseenter = false
// 尺寸信息
@@ -178,6 +179,7 @@ class Node {
let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt
if (isUseCustomNodeContent && customCreateNodeContent) {
this._customNodeContent = customCreateNodeContent(this)
this._customNodeContent.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
}
// 如果没有返回内容,那么还是使用内置的节点内容
if (this._customNodeContent) return
@@ -440,9 +442,7 @@ class Node {
this.mindMap.renderer[
isActive ? 'removeNodeFromActiveList' : 'addNodeToActiveList'
](this)
this.mindMap.emit('node_active', isActive ? null : this, [
...this.mindMap.renderer.activeNodeList
])
this.renderer.emitNodeActiveEvent(isActive ? null : this)
}
this.mindMap.emit('node_mousedown', this, e)
})
@@ -457,17 +457,23 @@ class Node {
this._isMouseenter = true
// 显示展开收起按钮
this.showExpandBtn()
if (this.isGeneralization) {
this.handleGeneralizationMouseenter()
}
this.mindMap.emit('node_mouseenter', this, e)
})
this.group.on('mouseleave', e => {
if (!this._isMouseenter) return
this._isMouseenter = false
this.hideExpandBtn()
if (this.isGeneralization) {
this.handleGeneralizationMouseleave()
}
this.mindMap.emit('node_mouseleave', this, e)
})
// 双击事件
this.group.on('dblclick', e => {
if (this.mindMap.opt.readonly) {
if (this.mindMap.opt.readonly || e.ctrlKey) {
return
}
e.stopPropagation()
@@ -513,7 +519,7 @@ class Node {
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
this.renderer.clearActiveNodeList()
this.renderer.addNodeToActiveList(this)
this.mindMap.emit('node_active', this, [...this.renderer.activeNodeList])
this.renderer.emitNodeActiveEvent(this)
}
// 更新节点
@@ -749,10 +755,7 @@ class Node {
item.setOpacity(val)
})
// 概要节点
if (this._generalizationNode) {
this._generalizationLine.opacity(val)
this._generalizationNode.group.opacity(val)
}
this.setGeneralizationOpacity(val)
}
// 隐藏子节点
@@ -847,7 +850,7 @@ class Node {
return this.customLeft !== undefined && this.customTop !== undefined
}
// 检查节点是否存在自定义位置的祖先节点
// 检查节点是否存在自定义位置的祖先节点,包含自身
ancestorHasCustomPosition() {
let node = this
while (node) {
@@ -859,6 +862,18 @@ class Node {
return false
}
// 检查是否存在有概要的祖先节点
ancestorHasGeneralization() {
let node = this.parent
while (node) {
if (node.checkHasGeneralization()) {
return true
}
node = node.parent
}
return false
}
// 添加子节点
addChildren(node) {
this.children.push(node)
@@ -889,7 +904,7 @@ class Node {
}
// 检测当前节点是否是某个节点的祖先节点
isParent(node) {
isAncestor(node) {
if (this.uid === node.uid) {
return false
}
@@ -903,6 +918,18 @@ class Node {
return false
}
// 检查当前节点是否是某个节点的父节点
isParent(node) {
if (this.uid === node.uid) {
return false
}
const parent = node.parent
if (parent && this.uid === parent.uid) {
return true
}
return false
}
// 检测当前节点是否是某个节点的兄弟节点
isBrother(node) {
if (!this.parent || this.uid === node.uid) {
@@ -913,6 +940,15 @@ class Node {
})
}
// 获取该节点在兄弟节点列表中的索引
getIndexInBrothers() {
return this.parent && this.parent.children
? this.parent.children.findIndex(item => {
return item.uid === this.uid
})
: -1
}
// 获取padding值
getPaddingVale() {
let { isActive } = this.getData()
@@ -961,10 +997,39 @@ class Node {
return key ? this.nodeData.data[key] : this.nodeData.data
}
// 获取该节点的纯数据,即不包含对节点实例的引用
getPureData(removeActiveState = true, removeId = false) {
return copyNodeTree({}, this, removeActiveState, removeId)
}
// 是否存在自定义样式
hasCustomStyle() {
return this.style.hasCustomStyle()
}
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置是相对于浏览器窗口左上角的位置
getRect() {
return this.group.rbox()
}
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置信息相对于画布
getRectInSvg() {
let { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
let { left, top, width, height } = this
let right = (left + width) * scaleX + translateX
let bottom = (top + height) * scaleY + translateY
left = left * scaleX + translateX
top = top * scaleY + translateY
return {
left,
right,
top,
bottom,
width: width * scaleX,
height: height * scaleY
}
}
}
export default Node

View File

@@ -307,16 +307,17 @@ function createNoteNode() {
this.noteEl.innerText = this.getData('note')
}
node.on('mouseover', () => {
let { left, top } = node.node.getBoundingClientRect()
const { left, top } = this.getNoteContentPosition()
if (!this.mindMap.opt.customNoteContentShow) {
this.noteEl.style.left = left + 'px'
this.noteEl.style.top = top + iconSize + 'px'
this.noteEl.style.top = top + 'px'
this.noteEl.style.display = 'block'
} else {
this.mindMap.opt.customNoteContentShow.show(
this.getData('note'),
left,
top + iconSize
top,
this
)
}
})
@@ -334,6 +335,19 @@ function createNoteNode() {
}
}
// 获取节点备注显示位置
function getNoteContentPosition() {
const iconSize = this.mindMap.themeConfig.iconSize
const { scaleY } = this.mindMap.view.getTransformData().transform
const iconSizeAddScale = iconSize * scaleY
let { left, top } = this._noteData.node.node.getBoundingClientRect()
top += iconSizeAddScale
return {
left,
top
}
}
// 测量自定义节点内容元素的宽高
function measureCustomNodeContentSize(content) {
if (!commonCaches.measureCustomNodeContentSizeEl) {
@@ -368,6 +382,7 @@ export default {
createHyperlinkNode,
createTagNode,
createNoteNode,
getNoteContentPosition,
measureCustomNodeContentSize,
isUseCustomNodeContent
}

View File

@@ -1,9 +1,30 @@
import Node from './Node'
import { createUid } from '../../../utils/index'
// 获取节点概要数据
function formatGetGeneralization() {
const data = this.getData('generalization')
return Array.isArray(data) ? data : data ? [data] : []
}
// 检查是否存在概要
function checkHasGeneralization() {
return !!this.getData('generalization')
return this.formatGetGeneralization().length > 0
}
// 检查是否存在自身的概要,非子节点区间
function checkHasSelfGeneralization() {
const list = this.formatGetGeneralization()
return !!list.find(item => {
return !item.range || item.range.length <= 0
})
}
// 获取概要节点所在的概要列表里的索引
function getGeneralizationNodeIndex(node) {
return this._generalizationList.findIndex(item => {
return item.generalizationNode.uid === node.uid
})
}
// 创建概要节点
@@ -11,26 +32,47 @@ function createGeneralizationNode() {
if (this.isGeneralization || !this.checkHasGeneralization()) {
return
}
if (!this._generalizationLine) {
this._generalizationLine = this.lineDraw.path()
}
if (!this._generalizationNode) {
this._generalizationNode = new Node({
data: {
data: this.getData('generalization')
},
uid: createUid(),
renderer: this.renderer,
mindMap: this.mindMap,
isGeneralization: true
})
this._generalizationNodeWidth = this._generalizationNode.width
this._generalizationNodeHeight = this._generalizationNode.height
this._generalizationNode.generalizationBelongNode = this
if (this.getData('generalization').isActive) {
this.renderer.addNodeToActiveList(this._generalizationNode)
let maxWidth = 0
let maxHeight = 0
const list = this.formatGetGeneralization()
list.forEach((item, index) => {
let cur = this._generalizationList[index]
if (!cur) {
cur = this._generalizationList[index] = {}
}
}
// 所属节点
cur.node = this
// 区间范围
cur.range = item.range
// 线和节点
if (!cur.generalizationLine) {
cur.generalizationLine = this.lineDraw.path()
}
if (!cur.generalizationNode) {
cur.generalizationNode = new Node({
data: {
data: item
},
uid: createUid(),
renderer: this.renderer,
mindMap: this.mindMap,
isGeneralization: true
})
}
// 关联所属节点
cur.generalizationNode.generalizationBelongNode = this
// 大小
if (cur.generalizationNode.width > maxWidth)
maxWidth = cur.generalizationNode.width
if (cur.generalizationNode.height > maxHeight)
maxHeight = cur.generalizationNode.height
// 如果该概要为激活状态,那么加入激活节点列表
if (item.isActive) {
this.renderer.addNodeToActiveList(cur.generalizationNode)
}
})
this._generalizationNodeWidth = maxWidth
this._generalizationNodeHeight = maxHeight
}
// 更新概要节点
@@ -43,39 +85,64 @@ function updateGeneralization() {
// 渲染概要节点
function renderGeneralization() {
if (this.isGeneralization) return
if (!this.checkHasGeneralization()) {
this.updateGeneralizationData()
const list = this.formatGetGeneralization()
if (list.length <= 0 || this.getData('expand') === false) {
this.removeGeneralization()
this._generalizationNodeWidth = 0
this._generalizationNodeHeight = 0
return
}
if (this.getData('expand') === false) {
if (list.length !== this._generalizationList.length) {
this.removeGeneralization()
return
}
this.createGeneralizationNode()
this.renderer.layout.renderGeneralization(
this,
this._generalizationLine,
this._generalizationNode
)
this.style.generalizationLine(this._generalizationLine)
this._generalizationNode.render()
this.renderer.layout.renderGeneralization(this._generalizationList)
this._generalizationList.forEach(item => {
this.style.generalizationLine(item.generalizationLine)
item.generalizationNode.render()
})
}
// 更新节点概要数据
function updateGeneralizationData() {
const childrenLength = this.children.length
const list = this.formatGetGeneralization()
const newList = []
list.forEach(item => {
if (!item.range) {
newList.push(item)
return
}
if (
item.range.length > 0 &&
item.range[0] <= childrenLength - 1 &&
item.range[1] <= childrenLength - 1
) {
newList.push(item)
}
})
if (newList.length !== list.length) {
this.setData({
generalization: newList
})
}
}
// 删除概要节点
function removeGeneralization() {
if (this.isGeneralization) return
if (this._generalizationLine) {
this._generalizationLine.remove()
this._generalizationLine = null
}
if (this._generalizationNode) {
// 删除概要节点时要同步从激活节点里删除
this.renderer.removeNodeFromActiveList(this._generalizationNode)
this._generalizationNode.remove()
this._generalizationNode = null
}
this._generalizationList.forEach(item => {
if (item.generalizationLine) {
item.generalizationLine.remove()
item.generalizationLine = null
}
if (item.generalizationNode) {
// 删除概要节点时要同步从激活节点里删除
this.renderer.removeNodeFromActiveList(item.generalizationNode)
item.generalizationNode.remove()
item.generalizationNode = null
}
})
this._generalizationList = []
// hack修复当激活一个节点时创建概要然后立即激活创建的概要节点后会重复创建概要节点并且无法删除的问题
if (this.generalizationBelongNode) {
this.nodeDraw
@@ -87,31 +154,65 @@ function removeGeneralization() {
// 隐藏概要节点
function hideGeneralization() {
if (this.isGeneralization) return
if (this._generalizationLine) {
this._generalizationLine.hide()
}
if (this._generalizationNode) {
this._generalizationNode.hide()
}
this._generalizationList.forEach(item => {
if (item.generalizationLine) item.generalizationLine.hide()
if (item.generalizationNode) item.generalizationNode.hide()
})
}
// 显示概要节点
function showGeneralization() {
if (this.isGeneralization) return
if (this._generalizationLine) {
this._generalizationLine.show()
}
if (this._generalizationNode) {
this._generalizationNode.show()
this._generalizationList.forEach(item => {
if (item.generalizationLine) item.generalizationLine.show()
if (item.generalizationNode) item.generalizationNode.show()
})
}
// 设置概要节点的透明度
function setGeneralizationOpacity(val) {
this._generalizationList.forEach(item => {
item.generalizationLine.opacity(val)
item.generalizationNode.group.opacity(val)
})
}
// 处理概要节点鼠标移入事件
function handleGeneralizationMouseenter() {
const belongNode = this.generalizationBelongNode
const list = belongNode.formatGetGeneralization()
const index = belongNode.getGeneralizationNodeIndex(this)
const generalizationData = list[index]
// 区间概要,框子节点
if (
Array.isArray(generalizationData.range) &&
generalizationData.range.length > 0
) {
this.mindMap.renderer.highlightNode(belongNode, generalizationData.range)
} else {
// 否则框自己
this.mindMap.renderer.highlightNode(belongNode)
}
}
// 处理概要节点鼠标移出事件
function handleGeneralizationMouseleave() {
this.mindMap.renderer.closeHighlightNode()
}
export default {
formatGetGeneralization,
checkHasGeneralization,
checkHasSelfGeneralization,
getGeneralizationNodeIndex,
createGeneralizationNode,
updateGeneralization,
updateGeneralizationData,
renderGeneralization,
removeGeneralization,
hideGeneralization,
showGeneralization
showGeneralization,
setGeneralizationOpacity,
handleGeneralizationMouseenter,
handleGeneralizationMouseleave
}

View File

@@ -30,12 +30,14 @@ class View {
})
// 拖动视图
this.mindMap.event.on('mousedown', () => {
if (this.mindMap.opt.isDisableDrag) return
this.sx = this.x
this.sy = this.y
})
this.mindMap.event.on('drag', (e, event) => {
if (e.ctrlKey) {
// 按住ctrl键拖动为多选
// 按住ctrl键拖动为多选
// 禁用拖拽
if (e.ctrlKey || this.mindMap.opt.isDisableDrag) {
return
}
if (this.firstDrag) {
@@ -70,7 +72,7 @@ class View {
return customHandleMousewheel(e)
}
// 鼠标滚轮事件控制缩放
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM) {
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM || e.ctrlKey) {
if (disableMouseWheelZoom) return
const { x: clientX, y: clientY } = this.mindMap.toPos(
e.clientX,

View File

@@ -346,6 +346,50 @@ class Base {
}
}
// 获取指定索引区间的子节点的边界范围
getChildrenBoundaries(node, dir, startIndex = 0, endIndex) {
let { generalizationLineMargin, generalizationNodeMargin } =
this.mindMap.themeConfig
const children = node.children.slice(startIndex, endIndex + 1)
let left = Infinity
let right = -Infinity
let top = Infinity
let bottom = -Infinity
children.forEach(item => {
const cur = this.getNodeBoundaries(item, dir)
left = cur.left < left ? cur.left : left
right = cur.right > right ? cur.right : right
top = cur.top < top ? cur.top : top
bottom = cur.bottom > bottom ? cur.bottom : bottom
})
return {
left,
right,
top,
bottom,
generalizationLineMargin,
generalizationNodeMargin
}
}
// 获取节点概要的渲染边界
getNodeGeneralizationRenderBoundaries(item, dir) {
let res = null
// 区间
if (item.range) {
res = this.getChildrenBoundaries(
item.node,
dir,
item.range[0],
item.range[1]
)
} else {
// 整体概要
res = this.getNodeBoundaries(item.node, dir)
}
return res
}
// 获取节点实际存在几个子节点
getNodeActChildrenLength(node) {
return node.nodeData.children && node.nodeData.children.length

View File

@@ -72,11 +72,7 @@ class CatalogOrganization extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.getData('expand') &&
node.children &&
node.children.length
) {
if (node.getData('expand') && node.children && node.children.length) {
let marginX = this.getMarginX(layerIndex + 1)
let marginY = this.getMarginY(layerIndex + 1)
if (isRoot) {
@@ -339,24 +335,27 @@ class CatalogOrganization extends Base {
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left = right + generalizationNodeMargin
gNode.top = top + (bottom - top - gNode.height) / 2
renderGeneralization(list) {
list.forEach(item => {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2
})
}
// 渲染展开收起按钮的隐藏占位元素

View File

@@ -357,24 +357,27 @@ class Fishbone extends Base {
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left = right + generalizationNodeMargin
gNode.top = top + (bottom - top - gNode.height) / 2
renderGeneralization(list) {
list.forEach(item => {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2
})
}
// 渲染展开收起按钮的隐藏占位元素

View File

@@ -77,11 +77,7 @@ class LogicalStructure extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.getData('expand') &&
node.children &&
node.children.length
) {
if (node.getData('expand') && node.children && node.children.length) {
let marginY = this.getMarginY(layerIndex + 1)
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
let top = node.top + node.height / 2 - node.childrenAreaHeight / 2
@@ -269,24 +265,27 @@ class LogicalStructure extends Base {
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left = right + generalizationNodeMargin
gNode.top = top + (bottom - top - gNode.height) / 2
renderGeneralization(list) {
list.forEach(item => {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2
})
}
// 渲染展开收起按钮的隐藏占位元素

View File

@@ -358,32 +358,34 @@ class MindMap extends Base {
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
let {
top,
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h', isLeft)
let x = isLeft
? left - generalizationLineMargin
: right + generalizationLineMargin
let x1 = x
let y1 = top
let x2 = x
let y2 = bottom
let cx = x1 + (isLeft ? -20 : 20)
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left =
x +
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
(isLeft ? gNode.width : 0)
gNode.top = top + (bottom - top - gNode.height) / 2
renderGeneralization(list) {
list.forEach(item => {
let isLeft = item.node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
let {
top,
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
let x = isLeft
? left - generalizationLineMargin
: right + generalizationLineMargin
let x1 = x
let y1 = top
let x2 = x
let y2 = bottom
let cx = x1 + (isLeft ? -20 : 20)
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationNode.left =
x +
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
(isLeft ? item.generalizationNode.width : 0)
item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2
})
}
// 渲染展开收起按钮的隐藏占位元素

View File

@@ -243,24 +243,27 @@ class OrganizationStructure extends Base {
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'v')
let x1 = left
let y1 = bottom + generalizationLineMargin
let x2 = right
let y2 = bottom + generalizationLineMargin
let cx = x1 + (x2 - x1) / 2
let cy = y1 + 20
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.top = bottom + generalizationNodeMargin
gNode.left = left + (right - left - gNode.width) / 2
renderGeneralization(list) {
list.forEach(item => {
let {
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeGeneralizationRenderBoundaries(item, 'v')
let x1 = left
let y1 = bottom + generalizationLineMargin
let x2 = right
let y2 = bottom + generalizationLineMargin
let cx = x1 + (x2 - x1) / 2
let cy = y1 + 20
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationNode.top = bottom + generalizationNodeMargin
item.generalizationNode.left = left + (right - left - item.generalizationNode.width) / 2
})
}
// 渲染展开收起按钮的隐藏占位元素

View File

@@ -80,11 +80,7 @@ class Timeline extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex, index) => {
if (
node.getData('expand') &&
node.children &&
node.children.length
) {
if (node.getData('expand') && node.children && node.children.length) {
let marginX = this.getMarginX(layerIndex + 1)
let marginY = this.getMarginY(layerIndex + 1)
if (isRoot) {
@@ -315,24 +311,26 @@ class Timeline extends Base {
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left = right + generalizationNodeMargin
gNode.top = top + (bottom - top - gNode.height) / 2
renderGeneralization(list) {
list.forEach(item => {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2
})
}
// 渲染展开收起按钮的隐藏占位元素

View File

@@ -97,11 +97,7 @@ class VerticalTimeline extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex, index) => {
if (
node.getData('expand') &&
node.children &&
node.children.length
) {
if (node.getData('expand') && node.children && node.children.length) {
let marginY = this.getMarginY(layerIndex + 1)
// 定位二级节点的top
if (isRoot) {
@@ -386,32 +382,35 @@ class VerticalTimeline extends Base {
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
let {
top,
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h', isLeft)
let x = isLeft
? left - generalizationLineMargin
: right + generalizationLineMargin
let x1 = x
let y1 = top
let x2 = x
let y2 = bottom
let cx = x1 + (isLeft ? -20 : 20)
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left =
x +
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
(isLeft ? gNode.width : 0)
gNode.top = top + (bottom - top - gNode.height) / 2
renderGeneralization(list) {
list.forEach(item => {
let isLeft = item.node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
let {
top,
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
let x = isLeft
? left - generalizationLineMargin
: right + generalizationLineMargin
let x1 = x
let y1 = top
let x2 = x
let y2 = bottom
let cx = x1 + (isLeft ? -20 : 20)
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationNode.left =
x +
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
(isLeft ? item.generalizationNode.width : 0)
item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2
})
}
// 渲染展开收起按钮的隐藏占位元素

View File

@@ -1,12 +1,18 @@
import JSZip from 'jszip'
import xmlConvert from 'xml-js'
import { getTextFromHtml, isUndef } from '../utils/index'
import {
getTextFromHtml,
imgToDataUrl,
parseDataUrl,
getImageSize,
isUndef
} from '../utils/index'
getSummaryText,
getSummaryText2,
getRoot,
getItemByName,
getElementsByType,
addSummaryData,
handleNodeImageFromXmind,
handleNodeImageToXmind,
getXmindContentXmlData,
parseNodeGeneralizationToXmind
} from '../utils/xmind'
// 解析.xmind文件
const parseXmindFile = file => {
@@ -43,18 +49,18 @@ const parseXmindFile = file => {
// 转换xmind数据
const transformXmind = async (content, files) => {
let data = JSON.parse(content)[0]
let nodeTree = data.rootTopic
let newTree = {}
let waitLoadImageList = []
let walk = async (node, newNode) => {
const data = JSON.parse(content)[0]
const nodeTree = data.rootTopic
const newTree = {}
const waitLoadImageList = []
const walk = async (node, newNode) => {
newNode.data = {
// 节点内容
text: isUndef(node.title) ? '' : node.title
}
// 节点备注
if (node.notes) {
let notesData = node.notes.realHTML || node.notes.plain
const notesData = node.notes.realHTML || node.notes.plain
newNode.data.note = notesData ? notesData.content || '' : ''
}
// 超链接
@@ -66,41 +72,26 @@ const transformXmind = async (content, files) => {
newNode.data.tag = node.labels
}
// 图片
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 imageType = /\.([^.]+)$/.exec(node.image.src)[1]
let imageBase64 =
`data:image/${imageType};base64,` +
(await files['resources/' + node.image.src.split('/')[1]].async(
'base64'
))
newNode.data.image = imageBase64
// 如果图片尺寸不存在
if (!node.image.width && !node.image.height) {
let imageSize = await getImageSize(imageBase64)
newNode.data.imageSize = {
width: imageSize.width,
height: imageSize.height
}
} else {
newNode.data.imageSize = {
width: node.image.width,
height: node.image.height
}
}
resolve()
} catch (error) {
console.log(error)
resolve()
}
handleNodeImageFromXmind(node, newNode, waitLoadImageList, files)
// 概要
const selfSummary = []
const childrenSummary = []
if (newNode._summary) {
selfSummary.push(newNode._summary)
}
if (Array.isArray(node.summaries) && node.summaries.length > 0) {
node.summaries.forEach(item => {
addSummaryData(
selfSummary,
childrenSummary,
() => {
return getSummaryText(node, item.topicId)
},
item.range
)
})
}
newNode.data.generalization = selfSummary
// 子节点
newNode.children = []
if (
@@ -108,9 +99,12 @@ const transformXmind = async (content, files) => {
node.children.attached &&
node.children.attached.length > 0
) {
node.children.attached.forEach(item => {
let newChild = {}
node.children.attached.forEach((item, index) => {
const newChild = {}
newNode.children.push(newChild)
if (childrenSummary[index]) {
newChild._summary = childrenSummary[index]
}
walk(item, newChild)
})
}
@@ -122,39 +116,21 @@ const transformXmind = async (content, files) => {
// 转换旧版xmind数据xmind8
const transformOldXmind = content => {
let data = JSON.parse(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]
return
}
}
arr.forEach(item => {
getRoot(item.elements)
})
}
getRoot(elements)
let newTree = {}
let getItemByName = (arr, name) => {
return arr.find(item => {
return item.name === name
})
}
let walk = (node, newNode) => {
let nodeElements = node.elements
const data = JSON.parse(content)
const elements = data.elements
const root = getRoot(elements)
const newTree = {}
const walk = (node, newNode) => {
const nodeElements = node.elements
let nodeTitle = getItemByName(nodeElements, 'title')
nodeTitle = nodeTitle && nodeTitle.elements && nodeTitle.elements[0].text
// 节点内容
newNode.data = {
// 节点内容
text: isUndef(nodeTitle) ? '' : nodeTitle
}
// 节点备注
try {
// 节点备注
let notesElement = getItemByName(nodeElements, 'notes')
const notesElement = getItemByName(nodeElements, 'notes')
if (notesElement) {
newNode.data.note =
notesElement.elements[0].elements[0].elements[0].text
@@ -162,8 +138,8 @@ const transformOldXmind = content => {
} catch (error) {
console.log(error)
}
// 超链接
try {
// 超链接
if (
node.attributes &&
node.attributes['xlink:href'] &&
@@ -174,9 +150,9 @@ const transformOldXmind = content => {
} catch (error) {
console.log(error)
}
// 标签
try {
// 标签
let labelsElement = getItemByName(nodeElements, 'labels')
const labelsElement = getItemByName(nodeElements, 'labels')
if (labelsElement) {
newNode.data.tag = labelsElement.elements.map(item => {
return item.elements[0].text
@@ -185,22 +161,50 @@ const transformOldXmind = content => {
} catch (error) {
console.log(error)
}
const childrenItem = getItemByName(nodeElements, 'children')
// 概要
const selfSummary = []
const childrenSummary = []
try {
if (newNode._summary) {
selfSummary.push(newNode._summary)
}
const summariesItem = getItemByName(nodeElements, 'summaries')
if (
summariesItem &&
Array.isArray(summariesItem.elements) &&
summariesItem.elements.length > 0
) {
summariesItem.elements.forEach(item => {
addSummaryData(
selfSummary,
childrenSummary,
() => {
return getSummaryText2(childrenItem, item.attributes['topic-id'])
},
item.attributes.range
)
})
}
} catch (error) {
console.log(error)
}
newNode.data.generalization = selfSummary
// 子节点
newNode.children = []
let _children = getItemByName(nodeElements, 'children')
if (_children && _children.elements && _children.elements.length > 0) {
_children.elements.forEach(item => {
if (item.name === 'topics') {
;(item.elements || []).forEach(item2 => {
let newChild = {}
newNode.children.push(newChild)
walk(item2, newChild)
})
} else {
let newChild = {}
newNode.children.push(newChild)
walk(item, newChild)
if (
childrenItem &&
childrenItem.elements &&
childrenItem.elements.length > 0
) {
const children = getElementsByType(childrenItem.elements, 'attached')
children.forEach((item, index) => {
const newChild = {}
newNode.children.push(newChild)
if (childrenSummary[index]) {
newChild._summary = childrenSummary[index]
}
walk(item, newChild)
})
}
}
@@ -209,6 +213,7 @@ const transformOldXmind = content => {
}
// 数据转换为xmind文件
// 直接转换为最新版本的xmind文件 2023.09.11172
const transformToXmind = async (data, name) => {
const id = 'simpleMindMap_' + Date.now()
const imageList = []
@@ -244,38 +249,7 @@ const transformToXmind = async (data, name) => {
newData.labels = node.data.tag || []
}
// 图片
if (node.data.image) {
// 处理异步逻辑
let resolve = null
let promise = new Promise(_resolve => {
resolve = _resolve
})
waitLoadImageList.push(promise)
try {
let imgName = ''
let imgData = node.data.image
// base64之外的其他图片要先转换成data:url
if (!/^data:/.test(node.data.image)) {
imgData = await imgToDataUrl(node.data.image)
}
// 从data:url中解析出图片类型和ase64
let dataUrlRes = parseDataUrl(imgData)
imgName = 'image_' + imageList.length + '.' + dataUrlRes.type
imageList.push({
name: imgName,
data: dataUrlRes.base64
})
newData.image = {
src: 'xap:resources/' + imgName,
width: node.data.imageSize.width,
height: node.data.imageSize.height
}
resolve()
} catch (error) {
console.log(error)
resolve()
}
}
handleNodeImageToXmind(node, newNode, waitLoadImageList, imageList)
// 样式
// 暂时不考虑样式
if (isRoot) {
@@ -293,6 +267,20 @@ const transformToXmind = async (data, name) => {
newNode[key] = newData[key]
})
}
// 概要
const { summary, summaries } = parseNodeGeneralizationToXmind(node)
if (isRoot) {
if (summaries.length > 0) {
newNode.rootTopic.children.summary = summary
newNode.rootTopic.summaries = summaries
}
} else {
if (summaries.length > 0) {
newNode.children.summary = summary
newNode.summaries = summaries
}
}
// 子节点
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
let newChild = {}
@@ -309,10 +297,15 @@ const transformToXmind = async (data, name) => {
zip.file('content.json', JSON.stringify(contentData))
zip.file(
'metadata.json',
`{"modifier":"","dataStructureVersion":"1","layoutEngineVersion":"2","activeSheetId":"${id}"}`
`{"modifier":"","dataStructureVersion":"2","creator":{"name":"mind-map"},"layoutEngineVersion":"3","activeSheetId":"${id}"}`
)
zip.file('content.xml', getXmindContentXmlData())
const manifestData = {
'file-entries': { 'content.json': {}, 'metadata.json': {} }
'file-entries': {
'content.json': {},
'metadata.json': {},
'Thumbnails/thumbnail.png': {}
}
}
// 图片
if (imageList.length > 0) {

View File

@@ -1,4 +1,9 @@
import { bfsWalk, throttle, getTopAncestorsFomNodeList, getNodeIndexInNodeList } from '../utils'
import {
bfsWalk,
throttle,
getTopAncestorsFomNodeList,
getNodeIndexInNodeList
} from '../utils'
import Base from '../layouts/Base'
// 节点拖动插件
@@ -108,9 +113,7 @@ class Drag extends Base {
node.endDrag()
})
this.removeCloneNode()
let overlapNodeUid = this.overlapNode
? this.overlapNode.getData('uid')
: ''
let overlapNodeUid = this.overlapNode ? this.overlapNode.getData('uid') : ''
let prevNodeUid = this.prevNode ? this.prevNode.getData('uid') : ''
let nextNodeUid = this.nextNode ? this.nextNode.getData('uid') : ''
// 存在重叠子节点,则移动作为其子节点
@@ -162,12 +165,14 @@ class Drag extends Base {
)
this.mindMap.render()
}
if (this.isDragging) {
this.mindMap.emit('node_dragend', {
overlapNodeUid,
prevNodeUid,
nextNodeUid
})
}
this.reset()
this.mindMap.emit('node_dragend', {
overlapNodeUid,
prevNodeUid,
nextNodeUid
})
}
// 拖动中
@@ -677,7 +682,7 @@ class Drag extends Base {
// 检查某个节点是否在被拖拽节点内
checkIsInBeingDragNodeList(node) {
return !!this.beingDragNodeList.find(item => {
return item.uid === node.uid || item.isParent(node)
return item.uid === node.uid || item.isAncestor(node)
})
}
}

View File

@@ -2,7 +2,9 @@ import {
imgToDataUrl,
downloadFile,
readBlob,
removeHTMLEntities
removeHTMLEntities,
resizeImgSize,
handleSelfCloseTags
} from '../utils'
import { SVG } from '@svgdotjs/svg.js'
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
@@ -19,7 +21,7 @@ class Export {
// 导出
async export(type, isDownload = true, name = '思维导图', ...args) {
if (this[type]) {
let result = await this[type](name, ...args)
const result = await this[type](name, ...args)
if (isDownload && type !== 'pdf') {
downloadFile(result, name + '.' + type)
}
@@ -29,6 +31,20 @@ class Export {
}
}
// 创建图片url转换任务
createTransformImgTaskList(svg, tagName, propName, getUrlFn) {
const imageList = svg.find(tagName)
return imageList.map(async item => {
const imgUlr = getUrlFn(item)
// 已经是data:URL形式不用转换
if (/^data:/.test(imgUlr) || imgUlr === 'none') {
return
}
const imgData = await imgToDataUrl(imgUlr)
item.attr(propName, imgData)
})
}
// 获取svg数据
async getSvgData() {
let { exportPaddingX, exportPaddingY } = this.mindMap.opt
@@ -36,19 +52,34 @@ class Export {
paddingX: exportPaddingX,
paddingY: exportPaddingY
})
// 把图片的url转换成data:url类型否则导出会丢失图片
let imageList = svg.find('image')
let task = imageList.map(async item => {
let imgUlr = item.attr('href') || item.attr('xlink:href')
// 已经是data:URL形式不用转换
if (/^data:/.test(imgUlr) || imgUlr === 'none') {
return
// svg的image标签把图片的url转换成data:url类型否则导出会丢失图片
const task1 = this.createTransformImgTaskList(
svg,
'image',
'href',
item => {
return item.attr('href') || item.attr('xlink:href')
}
let imgData = await imgToDataUrl(imgUlr)
item.attr('href', imgData)
)
// html的img标签
const task2 = this.createTransformImgTaskList(svg, 'img', 'src', item => {
return item.attr('src')
})
await Promise.all(task)
if (imageList.length > 0) {
const taskList = [...task1, ...task2]
await Promise.all(taskList)
// 开启了节点富文本编辑,需要增加一些样式
let isAddResetCss
if (this.mindMap.richText) {
const foreignObjectList = svg.find('foreignObject')
if (foreignObjectList.length > 0) {
foreignObjectList[0].add(
SVG(`<style>${this.mindMap.opt.resetCss}</style>`)
)
isAddResetCss = true
}
}
// svg节点内容有变需要重新获取html字符串
if (taskList.length > 0 || isAddResetCss) {
svgHTML = svg.svg()
}
return {
@@ -63,7 +94,8 @@ class Export {
transparent,
checkRotate = () => {
return false
}
},
compress
) {
return new Promise((resolve, reject) => {
const img = new Image()
@@ -76,8 +108,19 @@ class Export {
window.devicePixelRatio,
this.mindMap.opt.minExportImgCanvasScale
)
const imgWidth = img.width
const imgHeight = img.height
let imgWidth = img.width
let imgHeight = img.height
// 压缩图片
if (compress) {
const compressedSize = resizeImgSize(
imgWidth,
imgHeight,
compress.width,
compress.height
)
imgWidth = compressedSize[0]
imgHeight = compressedSize[1]
}
// 如果宽比高长那么旋转90度
const needRotate = checkRotate(imgWidth, imgHeight)
if (needRotate) {
@@ -118,7 +161,7 @@ class Export {
// 在canvas上绘制思维导图背景
drawBackgroundToCanvas(ctx, width, height) {
return new Promise((resolve, reject) => {
let {
const {
backgroundColor = '#fff',
backgroundImage,
backgroundRepeat = 'no-repeat',
@@ -162,7 +205,7 @@ class Export {
// 在svg上绘制思维导图背景
drawBackgroundToSvg(svg) {
return new Promise(async resolve => {
let {
const {
backgroundColor = '#fff',
backgroundImage,
backgroundRepeat = 'repeat'
@@ -171,7 +214,7 @@ class Export {
svg.css('background-color', backgroundColor)
// 背景图片
if (backgroundImage && backgroundImage !== 'none') {
let imgDataUrl = await imgToDataUrl(backgroundImage)
const imgDataUrl = await imgToDataUrl(backgroundImage)
svg.css('background-image', `url(${imgDataUrl})`)
svg.css('background-repeat', backgroundRepeat)
resolve()
@@ -186,49 +229,31 @@ class Export {
* 方法1.把svg的图片都转化成data:url格式再转换
* 方法2.把svg的图片提取出来再挨个绘制到canvas里最后一起转换
*/
async png(name, transparent = false, checkRotate) {
let { node, str } = await this.getSvgData()
str = removeHTMLEntities(str)
// 如果开启了富文本则使用htmltocanvas转换为图片
if (this.mindMap.richText) {
// 覆盖html默认的样式
let foreignObjectList = node.find('foreignObject')
if (foreignObjectList.length > 0) {
foreignObjectList[0].add(
SVG(`<style>${this.mindMap.opt.resetCss}</style>`)
)
}
str = node.svg()
// 使用其他库html2canvas、dom-to-image-more等来完成导出
// let res = await this.mindMap.richText.handleExportPng(node.node)
// let imgDataUrl = await this.svgToPng(
// res,
// transparent,
// checkRotate
// )
// return imgDataUrl
}
// 转换成blob数据
let blob = new Blob([str], {
type: 'image/svg+xml'
})
// 转换成data:url数据
let svgUrl = await readBlob(blob)
async png(name, transparent = false, checkRotate, compress) {
const { str } = await this.getSvgData()
const svgUrl = await this.fixSvgStrAndToBlob(str)
// 绘制到canvas上
let res = await this.svgToPng(svgUrl, transparent, checkRotate)
const res = await this.svgToPng(svgUrl, transparent, checkRotate, compress)
return res
}
// 导出为pdf
async pdf(name, useMultiPageExport) {
async pdf(name, useMultiPageExport, maxImageWidth) {
if (!this.mindMap.doExportPDF) {
throw new Error('请注册ExportPDF插件')
}
let img = await this.png('', false, (width, height) => {
if (width <= a4Size.width && height && a4Size.height) return false
return width / height > 1
})
this.mindMap.doExportPDF.pdf(name, img, useMultiPageExport)
const img = await this.png(
'',
false,
(width, height) => {
if (width <= a4Size.width && height && a4Size.height) return false
return width / height > 1
},
{
width: maxImageWidth || a4Size.width * 2
}
)
await this.mindMap.doExportPDF.pdf(name, img, useMultiPageExport)
}
// 导出为xmind
@@ -243,51 +268,50 @@ class Export {
}
// 导出为svg
// plusCssText附加的css样式如果svg中存在dom节点想要设置一些针对节点的样式可以通过这个参数传入
async svg(name) {
let { node } = await this.getSvgData()
// 开启了节点富文本编辑
if (this.mindMap.richText) {
let foreignObjectList = node.find('foreignObject')
if (foreignObjectList.length > 0) {
foreignObjectList[0].add(
SVG(`<style>${this.mindMap.opt.resetCss}</style>`)
)
}
}
const { node } = await this.getSvgData()
node.first().before(SVG(`<title>${name}</title>`))
await this.drawBackgroundToSvg(node)
let str = node.svg()
const str = node.svg()
const res = await this.fixSvgStrAndToBlob(str)
return res
}
// 修复svg字符串并且转换为blob数据
async fixSvgStrAndToBlob(str) {
// 移除字符串中的html实体
str = removeHTMLEntities(str)
// 给html自闭合标签添加闭合状态
str = handleSelfCloseTags(str)
// 转换成blob数据
let blob = new Blob([str], {
const blob = new Blob([str], {
type: 'image/svg+xml'
})
let res = await readBlob(blob)
const res = await readBlob(blob)
return res
}
// 导出为json
async json(name, withConfig = true) {
let data = this.mindMap.getData(withConfig)
let str = JSON.stringify(data)
let blob = new Blob([str])
let res = await readBlob(blob)
const data = this.mindMap.getData(withConfig)
const str = JSON.stringify(data)
const blob = new Blob([str])
const res = await readBlob(blob)
return res
}
// 专有文件其实就是json文件
async smm(name, withConfig) {
let res = await this.json(name, withConfig)
const res = await this.json(name, withConfig)
return res
}
// markdown文件
async md() {
let data = this.mindMap.getData()
let content = transformToMarkdown(data)
let blob = new Blob([content])
let res = await readBlob(blob)
const data = this.mindMap.getData()
const content = transformToMarkdown(data)
const blob = new Blob([content])
const res = await readBlob(blob)
return res
}
}

View File

@@ -1,4 +1,4 @@
import JsPDF from 'jspdf'
import JsPDF from '../utils/jspdf'
import { a4Size } from '../constants/constant'
// 导出PDF插件需要通过Export插件使用
@@ -9,92 +9,104 @@ class ExportPDF {
}
// 导出为pdf
pdf(name, img, useMultiPageExport = false) {
async pdf(name, img, useMultiPageExport = false) {
if (useMultiPageExport) {
this.multiPageExport(name, img)
await this.multiPageExport(name, img)
} else {
this.onePageExport(name, img)
await this.onePageExport(name, img)
}
}
// 单页导出
onePageExport(name, img) {
let pdf = new JsPDF('', 'pt', 'a4')
let a4Ratio = a4Size.width / a4Size.height
let image = new Image()
image.onload = () => {
let imageWidth = image.width
let imageHeight = image.height
let imageRatio = imageWidth / imageHeight
let w, h
if (imageWidth <= a4Size.width && imageHeight <= a4Size.height) {
// 使用图片原始宽高
w = imageWidth
h = imageHeight
} else if (a4Ratio > imageRatio) {
// 以a4Height为高度缩放图片宽度
w = imageRatio * a4Size.height
h = a4Size.height
} else {
// 以a4Width为宽度缩放图片高度
w = a4Size.width
h = a4Size.width / imageRatio
return new Promise((resolve, reject) => {
let pdf = new JsPDF('', 'pt', 'a4')
let a4Ratio = a4Size.width / a4Size.height
let image = new Image()
image.onload = () => {
let imageWidth = image.width
let imageHeight = image.height
let imageRatio = imageWidth / imageHeight
let w, h
if (imageWidth <= a4Size.width && imageHeight <= a4Size.height) {
// 使用图片原始宽高
w = imageWidth
h = imageHeight
} else if (a4Ratio > imageRatio) {
// 以a4Height为高度缩放图片宽度
w = imageRatio * a4Size.height
h = a4Size.height
} else {
// 以a4Width为宽度缩放图片高度
w = a4Size.width
h = a4Size.width / imageRatio
}
pdf.addImage(
img,
'PNG',
(a4Size.width - w) / 2,
(a4Size.height - h) / 2,
w,
h
)
pdf.save(name)
resolve()
}
pdf.addImage(
img,
'PNG',
(a4Size.width - w) / 2,
(a4Size.height - h) / 2,
w,
h
)
pdf.save(name)
}
image.src = img
image.onerror = e => {
reject(e)
}
image.src = img
})
}
// 多页导出
multiPageExport(name, img) {
let image = new Image()
image.onload = () => {
let imageWidth = image.width
let imageHeight = image.height
// 一页pdf显示高度
let pageHeight = (imageWidth / a4Size.width) * a4Size.height
// 未生成pdf的高度
let leftHeight = imageHeight
// 偏移
let position = 0
// a4纸的尺寸[595.28,841.89]图片在pdf中图片的宽高
let imgWidth = a4Size.width
let imgHeight = (a4Size.width / imageWidth) * imageHeight
let pdf = new JsPDF('', 'pt', 'a4')
// 有两个高度需要区分一个是图片的实际高度和生成pdf的页面高度(841.89)
// 当内容未超过pdf一页显示的范围无需分页
if (leftHeight < pageHeight) {
pdf.addImage(
img,
'PNG',
(a4Size.width - imgWidth) / 2,
(a4Size.height - imgHeight) / 2,
imgWidth,
imgHeight
)
} else {
// 分页
while (leftHeight > 0) {
pdf.addImage(img, 'PNG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight
position -= a4Size.height
// 避免添加空白页
if (leftHeight > 0) {
pdf.addPage()
return new Promise((resolve, reject) => {
let image = new Image()
image.onload = () => {
let imageWidth = image.width
let imageHeight = image.height
// 一页pdf显示高度
let pageHeight = (imageWidth / a4Size.width) * a4Size.height
// 未生成pdf的高度
let leftHeight = imageHeight
// 偏移
let position = 0
// a4纸的尺寸[595.28,841.89]图片在pdf中图片的宽高
let imgWidth = a4Size.width
let imgHeight = (a4Size.width / imageWidth) * imageHeight
let pdf = new JsPDF('', 'pt', 'a4')
// 有两个高度需要区分一个是图片的实际高度和生成pdf的页面高度(841.89)
// 当内容未超过pdf一页显示的范围无需分页
if (leftHeight < pageHeight) {
pdf.addImage(
img,
'PNG',
(a4Size.width - imgWidth) / 2,
(a4Size.height - imgHeight) / 2,
imgWidth,
imgHeight
)
} else {
// 分页
while (leftHeight > 0) {
pdf.addImage(img, 'PNG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight
position -= a4Size.height
// 避免添加空白页
if (leftHeight > 0) {
pdf.addPage()
}
}
}
pdf.save(name)
resolve()
}
pdf.save(name)
}
image.src = img
image.onerror = (e) => {
reject(e)
}
image.src = img
})
}
}

View File

@@ -38,6 +38,7 @@ class Painter {
}
onEndPainter() {
if (!this.isInPainter) return
this.endPainter()
this.mindMap.emit('painter_end')
}

View File

@@ -153,7 +153,7 @@ class RichText {
}
// 显示文本编辑控件
showEditText(node, rect, isInserting, isFromKeyDown) {
showEditText({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
if (this.showTextEdit) {
return
}
@@ -167,7 +167,9 @@ class RichText {
this.node = node
this.isInserting = isInserting
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
this.mindMap.emit('before_show_text_edit')
if (!isFromScale) {
this.mindMap.emit('before_show_text_edit')
}
this.mindMap.renderer.textEdit.registerTmpShortcut()
// 原始宽高
let g = node._textData.node
@@ -422,10 +424,13 @@ class RichText {
// 中文输入结束
onCompositionEnd() {
if (!this.showTextEdit || !this.lostStyle) {
if (!this.showTextEdit) {
return
}
this.isCompositing = false
if (!this.lostStyle) {
return
}
this.setTextStyleIfNotRichText(this.node)
}
@@ -568,7 +573,7 @@ class RichText {
setNotActiveNodeStyle(node, style) {
const config = this.normalStyleToRichTextStyle(style)
if (Object.keys(config).length > 0) {
this.showEditText(node)
this.showEditText({ node })
this.formatAllText(config)
this.hideEditText([node])
}
@@ -648,11 +653,13 @@ class RichText {
beforePluginRemove() {
this.transformAllNodesToNormalNode()
document.head.removeChild(this.styleEl)
this.unbindEvent()
}
// 插件被卸载前做的事情
beforePluginDestroy() {
document.head.removeChild(this.styleEl)
this.unbindEvent()
}
}

View File

@@ -65,6 +65,9 @@ class Search {
this.currentIndex = -1
this.notResetSearchText = false
this.isSearching = false
if (this.mindMap.opt.readonly) {
this.mindMap.renderer.closeHighlightNode()
}
this.emitEvent()
}
@@ -96,6 +99,10 @@ class Search {
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
this.notResetSearchText = false
callback()
// 只读模式下节点无法激活,所以通过高亮的方式
if (this.mindMap.opt.readonly) {
this.mindMap.renderer.highlightNode(currentNode)
}
})
}

View File

@@ -133,9 +133,7 @@ class Select {
}
}
if (isNumChange || isNodeChange) {
this.mindMap.emit('node_active', null, [
...this.mindMap.renderer.activeNodeList
])
this.mindMap.renderer.emitNodeActiveEvent()
}
}

View File

@@ -48,6 +48,7 @@ class TouchEvent {
let touch = e.touches[0]
this.dispatchMouseEvent('mousemove', touch.target, touch)
} else if (len === 2) {
if (this.mindMap.opt.disableTouchZoom) return
let touch1 = e.touches[0]
let touch2 = e.touches[1]
let ox = touch1.clientX - touch2.clientX

View File

@@ -12,6 +12,7 @@ class BatchExecution {
// 添加任务
push(name, fn) {
if (this.has[name]) {
this.replaceTask(name, fn)
return
}
this.has[name] = true
@@ -22,6 +23,19 @@ class BatchExecution {
this.nextTick()
}
// 替换任务
replaceTask(name, fn) {
const index = this.queue.findIndex(item => {
return item.name === name
})
if (index !== -1) {
this.queue[index] = {
name,
fn
}
}
}
// 执行队列
flush() {
let fns = this.queue.slice(0)

View File

@@ -1,5 +1,8 @@
import { v4 as uuidv4 } from 'uuid'
import { nodeDataNoStylePropList } from '../constants/constant'
import {
nodeDataNoStylePropList,
selfCloseTagList
} from '../constants/constant'
import MersenneTwister from './mersenneTwister'
// 深度优先遍历树
export const walk = (
@@ -152,6 +155,9 @@ export const copyRenderTree = (tree, root, removeActiveState = false) => {
tree.data = simpleDeepClone(root.data)
if (removeActiveState) {
tree.data.isActive = false
if (tree.data.generalization) {
tree.data.generalization.isActive = false
}
}
tree.children = []
if (root.children && root.children.length > 0) {
@@ -626,7 +632,7 @@ export const textToNodeRichTextWithWrap = html => {
}
return list
.map(item => {
return `<p><span>${item}</span></p>`
return `<p><span>${htmlEscape(item)}</span></p>`
})
.join('')
}
@@ -710,7 +716,7 @@ export const getTopAncestorsFomNodeList = list => {
list.forEach(node => {
if (
!list.find(item => {
return item.uid !== node.uid && item.isParent(node)
return item.uid !== node.uid && item.isAncestor(node)
})
) {
res.push(node)
@@ -719,6 +725,71 @@ export const getTopAncestorsFomNodeList = list => {
return res
}
// 从给定的节点实例列表里判断是否存在上下级关系
export const checkHasSupSubRelation = list => {
for (let i = 0; i < list.length; i++) {
const cur = list[i]
if (
list.find(item => {
return item.uid !== cur.uid && cur.isAncestor(item)
})
) {
return true
}
}
return false
}
// 解析要添加概要的节点实例列表
export const parseAddGeneralizationNodeList = list => {
const cache = {}
const uidToParent = {}
list.forEach(node => {
const parent = node.parent
if (parent) {
const pUid = parent.uid
uidToParent[pUid] = parent
const index = node.getIndexInBrothers()
const data = {
node,
index
}
if (cache[pUid]) {
if (
!cache[pUid].find(item => {
return item.index === data.index
})
) {
cache[pUid].push(data)
}
} else {
cache[pUid] = [data]
}
}
})
const res = []
Object.keys(cache).forEach(uid => {
if (cache[uid].length > 1) {
const rangeList = cache[uid]
.map(item => {
return item.index
})
.sort((a, b) => {
return a - b
})
res.push({
node: uidToParent[uid],
range: [rangeList[0], rangeList[rangeList.length - 1]]
})
} else {
res.push({
node: cache[uid][0].node
})
}
})
return res
}
// 判断两个矩形是否重叠
export const checkTwoRectIsOverlap = (
minx1,
@@ -921,3 +992,29 @@ export const removeFromParentNodeData = node => {
if (index === -1) return
node.parent.nodeData.children.splice(index, 1)
}
// 给html自闭合标签添加闭合状态
export const handleSelfCloseTags = str => {
selfCloseTagList.forEach(tagName => {
str = str.replaceAll(
new RegExp(`<${tagName}([^>]*)>`, 'g'),
`<${tagName} $1 />`
)
})
return str
}
// 检查两个节点列表是否包含的节点是一样的
export const checkNodeListIsEqual = (list1, list2) => {
if (list1.length !== list2.length) return false
for (let i = 0; i < list1.length; i++) {
if (
!list2.find(item => {
return item.uid === list1[i].uid
})
) {
return false
}
}
return true
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -74,6 +74,7 @@ declare class MindMap {
richTextEditFakeInPlace: boolean;
customHandleClipboardText: any;
disableMouseWheelZoom: boolean;
disableTouchZoom: boolean;
errorHandler: (code: any, error: any) => void;
resetCss: string;
enableDblclickBackToRootNode: boolean;
@@ -103,6 +104,12 @@ declare class MindMap {
defaultGeneralizationText: string;
handleIsSplitByWrapOnPasteCreateNewNode: any;
addHistoryTime: number;
isDisableDrag: boolean;
highlightNodeBoxStyle: {
stroke: string;
fill: string;
};
createNewNodeBehavior: string;
});
opt: any;
el: any;

View File

@@ -80,6 +80,11 @@ export namespace CONSTANTS {
const VERTICAL: string;
const HORIZONTAL: string;
}
namespace CREATE_NEW_NODE_BEHAVIOR {
const DEFAULT: string;
const NOT_ACTIVE: string;
const ACTIVE_ONLY: string;
}
}
export const initRootNodePositionMap: {
[x: string]: number;
@@ -107,3 +112,4 @@ export namespace a4Size {
const height: number;
}
export const cssContent: "\n /* 鼠标hover和激活时渲染的矩形 */\n .smm-hover-node{\n display: none;\n opacity: 0.6;\n stroke-width: 1;\n }\n\n .smm-node:not(.smm-node-dragging):hover .smm-hover-node{\n display: block;\n }\n\n .smm-node.active .smm-hover-node{\n display: block;\n opacity: 1;\n stroke-width: 2;\n }\n";
export const selfCloseTagList: string[];

View File

@@ -70,6 +70,7 @@ export namespace defaultOpt {
const richTextEditFakeInPlace: boolean;
const customHandleClipboardText: any;
const disableMouseWheelZoom: boolean;
const disableTouchZoom: boolean;
function errorHandler(code: any, error: any): void;
const resetCss: string;
const enableDblclickBackToRootNode: boolean;
@@ -101,4 +102,11 @@ export namespace defaultOpt {
const defaultGeneralizationText: string;
const handleIsSplitByWrapOnPasteCreateNewNode: any;
const addHistoryTime: number;
const isDisableDrag: boolean;
namespace highlightNodeBoxStyle {
export const stroke: string;
const fill_2: string;
export { fill_2 as fill };
}
const createNewNodeBehavior: string;
}

View File

@@ -11,6 +11,8 @@ export default class KeyCommand {
save(): void;
restore(): void;
bindEvent(): void;
onKeydown(e: any): void;
unBindEvent(): void;
checkKey(e: any, key: any): boolean;
getOriginEventCodeArr(e: any): any[];
hasCombinationKey(e: any): any;

View File

@@ -20,6 +20,9 @@ declare class Render {
beingPasteText: string;
beingPasteImgSize: number;
currentBeingPasteType: string;
highlightBoxNode: any;
lastActiveNode: any;
lastActiveNodeList: any[];
setLayout(): void;
layout: MindMap | CatalogOrganization | OrganizationStructure | Timeline | VerticalTimeline;
setData(data: any): void;
@@ -66,6 +69,7 @@ declare class Render {
goTargetNode(node: any, callback?: () => void): void;
registerShortcutKeys(): void;
toggleActiveExpand(): void;
emitNodeActiveEvent(node?: any, activeNodeList?: any[]): void;
clearActiveNodeListOnDrawClick(e: any, eventType: any): void;
startTextEdit(): void;
endTextEdit(): void;
@@ -75,12 +79,17 @@ declare class Render {
removeNodeFromActiveList(node: any): void;
findActiveNodeIndex(node: any): any;
backForward(type: any, step: any): void;
getNewNodeBehavior(openEdit?: boolean, handleMultiNodes?: boolean): {
focusNewNode: boolean;
inserting: boolean;
};
copy(): void;
cut(): void;
paste(): void;
onPaste(): Promise<void>;
insertTo(node: any, exist: any, dir?: string): void;
checkNodeLayerChange(node: any, toNode: any): void;
deleteNodeGeneralization(node: any): void;
getNextActiveNode(): any;
copyNode(): any;
toggleNodeExpand(node: any): void;
@@ -89,7 +98,8 @@ declare class Render {
setRootNodeCenter(): void;
expandToNodeUid(uid: any, callback?: () => void): void;
findNodeByUid(uid: any): any;
emitNodeActiveEvent(): void;
highlightNode(node: any, range: any): void;
closeHighlightNode(): void;
}
import TextEdit from "./TextEdit";
import MindMap from "../../layouts/MindMap";

View File

@@ -7,11 +7,24 @@ export default class TextEdit {
showTextEdit: boolean;
cacheEditingText: string;
bindEvent(): void;
show(node: any, e: any, isInserting?: boolean, isFromKeyDown?: boolean): Promise<void>;
show({ node, isInserting, isFromKeyDown, isFromScale }: {
node: any;
isInserting?: boolean;
isFromKeyDown?: boolean;
isFromScale?: boolean;
}): Promise<void>;
onScale(): void;
onKeydown(e: any): void;
unBindEvent(): void;
checkIsAutoEnterTextEditKey(e: any): boolean;
registerTmpShortcut(): void;
showEditTextBox(node: any, rect: any, isInserting: any, isFromKeyDown: any): void;
showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }: {
node: any;
rect: any;
isInserting: any;
isFromKeyDown: any;
isFromScale: any;
}): void;
getEditText(): any;
hideEditTextBox(): any;
}

View File

@@ -39,6 +39,7 @@ declare class Node {
_tagData: any;
_noteData: any;
noteEl: any;
noteContentIsShow: boolean;
_expandBtn: any;
_lastExpandBtnType: any;
_showExpandBtn: boolean;
@@ -47,8 +48,7 @@ declare class Node {
_fillExpandNode: any;
_userListGroup: any;
_lines: any[];
_generalizationLine: any;
_generalizationNode: any;
_generalizationList: any[];
_unVisibleRectRegionNode: any;
_isMouseenter: boolean;
_rectInfo: {
@@ -102,11 +102,14 @@ declare class Node {
getShape(): any;
hasCustomPosition(): boolean;
ancestorHasCustomPosition(): boolean;
ancestorHasGeneralization(): boolean;
addChildren(node: any): void;
styleLine(line: any, node: any): void;
removeLine(): void;
isAncestor(node: any): boolean;
isParent(node: any): boolean;
isBrother(node: any): any;
getIndexInBrothers(): any;
getPaddingVale(): {
paddingX: any;
paddingY: any;
@@ -117,7 +120,17 @@ declare class Node {
getSelfInhertStyle(prop: any): any;
getBorderWidth(): any;
getData(key: any): any;
getPureData(removeActiveState?: boolean, removeId?: boolean): any;
hasCustomStyle(): boolean;
getRect(): any;
getRectInSvg(): {
left: any;
right: any;
top: any;
bottom: any;
width: number;
height: number;
};
}
import Style from "./Style";
import Shape from "./Shape";

View File

@@ -7,6 +7,7 @@ declare namespace _default {
export { createHyperlinkNode };
export { createTagNode };
export { createNoteNode };
export { getNoteContentPosition };
export { measureCustomNodeContentSize };
export { isUseCustomNodeContent };
}
@@ -38,6 +39,10 @@ declare function createNoteNode(): {
declare class createNoteNode {
noteEl: HTMLDivElement;
}
declare function getNoteContentPosition(): {
left: any;
top: any;
};
declare function measureCustomNodeContentSize(content: any): {
width: any;
height: any;

View File

@@ -1,32 +1,38 @@
declare namespace _default {
export { formatGetGeneralization };
export { checkHasGeneralization };
export { checkHasSelfGeneralization };
export { getGeneralizationNodeIndex };
export { createGeneralizationNode };
export { updateGeneralization };
export { updateGeneralizationData };
export { renderGeneralization };
export { removeGeneralization };
export { hideGeneralization };
export { showGeneralization };
export { setGeneralizationOpacity };
export { handleGeneralizationMouseenter };
export { handleGeneralizationMouseleave };
}
export default _default;
declare function formatGetGeneralization(): any[];
declare function checkHasGeneralization(): boolean;
declare function checkHasSelfGeneralization(): boolean;
declare function getGeneralizationNodeIndex(node: any): any;
declare function createGeneralizationNode(): void;
declare class createGeneralizationNode {
_generalizationLine: any;
_generalizationNode: Node;
_generalizationNodeWidth: any;
_generalizationNodeHeight: any;
}
declare function updateGeneralization(): void;
declare function renderGeneralization(): void;
declare class renderGeneralization {
_generalizationNodeWidth: number;
_generalizationNodeHeight: number;
}
declare function updateGeneralization(): void;
declare function updateGeneralizationData(): void;
declare function renderGeneralization(): void;
declare function removeGeneralization(): void;
declare class removeGeneralization {
_generalizationLine: any;
_generalizationNode: any;
_generalizationList: any[];
}
declare function hideGeneralization(): void;
declare function showGeneralization(): void;
import Node from "./Node";
declare function setGeneralizationOpacity(val: any): void;
declare function handleGeneralizationMouseenter(): void;
declare function handleGeneralizationMouseleave(): void;

View File

@@ -39,6 +39,22 @@ declare class Base {
generalizationLineMargin: any;
generalizationNodeMargin: any;
};
getChildrenBoundaries(node: any, dir: any, startIndex: number, endIndex: any): {
left: number;
right: number;
top: number;
bottom: number;
generalizationLineMargin: any;
generalizationNodeMargin: any;
};
getNodeGeneralizationRenderBoundaries(item: any, dir: any): {
left: any;
right: any;
top: any;
bottom: any;
generalizationLineMargin: any;
generalizationNodeMargin: any;
};
getNodeActChildrenLength(node: any): any;
}
import Lru from "../utils/Lru";

View File

@@ -4,5 +4,6 @@ declare class BatchExecution {
queue: any[];
nextTick: any;
push(name: any, fn: any): void;
replaceTask(name: any, fn: any): void;
flush(): void;
}

View File

@@ -58,6 +58,8 @@ export function getObjectChangedProps(oldObject: any, newObject: any): {};
export function checkIsNodeStyleDataKey(key: any): boolean;
export function mergerIconList(list: any): any;
export function getTopAncestorsFomNodeList(list: any): any[];
export function checkHasSupSubRelation(list: any): boolean;
export function parseAddGeneralizationNodeList(list: any): any[];
export function checkTwoRectIsOverlap(minx1: any, maxx1: any, miny1: any, maxy1: any, minx2: any, maxx2: any, miny2: any, maxy2: any): boolean;
export function focusInput(el: any): void;
export function selectAllInput(el: any): void;
@@ -75,3 +77,5 @@ export function getDataFromClipboard(): Promise<{
img: any;
}>;
export function removeFromParentNodeData(node: any): void;
export function handleSelfCloseTags(str: any): any;
export function checkNodeListIsEqual(list1: any, list2: any): boolean;

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -314,6 +314,11 @@ export const shortcutKeyList = [
name: 'Zoom out',
value: 'Ctrl + -'
},
{
icon: 'iconfangda',
name: 'Zoom in/Zoom out',
value: 'Ctrl + Mouse wheel'
},
{
icon: 'icondingwei',
name: 'Back root node',

View File

@@ -381,6 +381,11 @@ export const shortcutKeyList = [
name: '缩小',
value: 'Ctrl + -'
},
{
icon: 'iconfangda',
name: '放大/缩小',
value: 'Ctrl + 鼠标滚动'
},
{
icon: 'icondingwei',
name: '回到根节点',

View File

@@ -47,6 +47,10 @@ export default {
mousewheelZoomActionReverse: 'Mouse Wheel Zoom',
mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back',
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back',
createNewNodeBehavior: 'Behavior of creating new node',
default: 'Active new node and editing',
notActive: 'Not active new node',
activeOnly: 'Only active new node but not editing',
rootStyle: 'Root Node',
associativeLineText: 'Associative line text',
fontFamily: 'Font family',
@@ -237,7 +241,7 @@ export default {
more: 'More',
selectFileTip: 'Please select a file',
notSupportTip:
'Your browser or network protocol does not support this feature',
'Your browser does not support this feature, or the current page is not using the HTTPS protocol',
tip: 'Tip',
editingLocalFileTipFront: 'Currently editing your local【',
editingLocalFileTipEnd: '】file',
@@ -276,7 +280,7 @@ export default {
},
formulaSidebar: {
title: 'Formula',
placeholder: 'Please enter LaText syntax',
placeholder: 'Please enter LaTeX syntax',
confirm: 'Confirm',
common: 'Common formulas',
tip: 'Inserting formulas is not supported in non rich text mode'

View File

@@ -47,6 +47,10 @@ export default {
mousewheelZoomActionReverse: '鼠标滚轮缩放',
mousewheelZoomActionReverse1: '向前缩小向后放大',
mousewheelZoomActionReverse2: '向前放大向后缩小',
createNewNodeBehavior: '创建新节点的行为',
default: '激活新节点及进入编辑',
notActive: '不激活新节点',
activeOnly: '只激活新节点,不进入编辑',
rootStyle: '根节点',
associativeLineText: '关联线文字',
fontFamily: '字体',
@@ -233,7 +237,7 @@ export default {
formula: '公式',
more: '更多',
selectFileTip: '请选择文件',
notSupportTip: '你的浏览器或网络协议不支持该功能',
notSupportTip: '你的浏览器不支持该功能或者当前页面非https协议',
tip: '提示',
editingLocalFileTipFront: '当前正在编辑你本机的【',
editingLocalFileTipEnd: '】文件',
@@ -270,7 +274,7 @@ export default {
},
formulaSidebar: {
title: '公式',
placeholder: '请输入 LaText 语法',
placeholder: '请输入 LaTeX 语法',
confirm: '完成',
common: '常用公式',
tip: '非富文本模式下不支持插入公式'

View File

@@ -11,7 +11,7 @@ let langList = [
}
]
let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog']
let CourseList = new Array(24).fill(0).map((_, index) => {
let CourseList = new Array(25).fill(0).map((_, index) => {
return 'course' + (index + 1)
})
let APIList = [

View File

@@ -1,5 +1,90 @@
# Changelog
## 0.9.1
Fix:
> 1.Fix the issue of exporting images, SVGs, and PDFs with errors when customizing node content.
>
> 2.Optimize the distribution of node activation events, do not distribute events when the activation node has not changed, and skip intermediate events when distributing multiple events in a short period of time.
>
> 3.Fix the issue where the edit box and node detach when scrolling the canvas with the mouse while the node is in editing mode.
>
> 4.Fix the issue of shortcut keys becoming invalid when zooming the canvas with the mouse wheel and then exiting node editing while in node editing mode.
>
> 5.Fix the issue where clicking on a node can also trigger node_dragend event.
>
> 6.Fix that clicking on the canvas and nodes while not in the format brush will also trigger the painter_end event.
>
> 7.Fixed the issue where the mind map text editing box was not destroyed during node text editing and associated line text editing.
New:
> 1.When holding down the Ctrl key, disable the node double-click event.
>
> 2.Support configuring the behavior when creating new nodes: focusing and entering editing, not focusing, only focusing.
>
> 3.When searching in read-only mode, add a highlight effect to the currently matched node.
>
> 4.The default behavior of the mouse scroll wheel is to move the canvas up and down; The default is to scroll forward to enlarge the canvas and zoom back.
>
> 5.When the mouse scroll wheel behavior is to move the canvas up and down, it supports holding down the Ctrl key to zoom in and out of the canvas.
Demo支持配置创建新节点时的行为。
## 0.9.0
New:
1.Support adding summaries to some child nodes of the same node.
2.Moving the mouse into the summary will highlight its node.
3.Importing and exporting xmind files supports processing profiles.
## 0.8.1
Fix
> 1.Fix the issue where the activation status of the summary node in the history data has not been deleted, which can cause data to be triggered when clicking on the summary node_ Change event.
>
> 2.Fix the issue of blank pages and exceptions thrown by the console when running in Safari browser.
>
> 3.Fixed the issue of icon floating layer and note floating layer detached from nodes when scaling the canvas.
>
> 4.Fixed the issue of selecting all nodes in read-only mode.
>
> 5.Fix the presence of node content has &nbsp; in rich text mode; Error exporting as image.
>
> 6.Fixed the issue of overlapping profiles when adding profiles to oneself first and then to subordinates; Fix the issue of overlapping profiles when adding profiles to nodes with hierarchical relationships at the same time.
>
> 7.Fix the issue of exporting PDF errors when there are many nodes.
New
> 1.Add a configuration option that prohibits dragging the canvas.
>
> 2.Add a configuration option to prohibit double finger scaling of the canvas.
>
> 3.Add compression parameters to the method of exporting PNG; Optimize the problem of excessive volume when exporting PDF from nodes with large amounts of data.
>
> 4.Rename the isParent method of the node instance to isAncestor and add the isParent method at the same time.
Demo:
> 1.Fixed the issue of being able to search for replacement and edit outlines in read-only mode.
>
> 2.Fix the issue where the outline cannot be displayed and edited when the node content is an HTML tag.
>
> 3.Fix the issue where when multiple nodes are selected and icons are added at the same time, all node icons will be unified as the icon of the first node.
>
> 4.Adding loading to the export operation.
## 0.8.0-fix.1
Fix: Fixed the issue of creating a new node using direct paste if the pasted content contains HTML label symbols such as <> and the newly created node content is empty.
## 0.8.0
Breaking change: Greatly optimize some of the code and slightly improve performance, mainly by using the 'render' class to remove useless logic, adjust unreasonable implementations, and extract duplicate code; Modify function names, functions, etc.

View File

@@ -1,6 +1,58 @@
<template>
<div>
<h1>Changelog</h1>
<h2>0.9.1</h2>
<p>Fix:</p>
<blockquote>
<p>1.Fix the issue of exporting images, SVGs, and PDFs with errors when customizing node content.</p>
<p>2.Optimize the distribution of node activation events, do not distribute events when the activation node has not changed, and skip intermediate events when distributing multiple events in a short period of time.</p>
<p>3.Fix the issue where the edit box and node detach when scrolling the canvas with the mouse while the node is in editing mode.</p>
<p>4.Fix the issue of shortcut keys becoming invalid when zooming the canvas with the mouse wheel and then exiting node editing while in node editing mode.</p>
<p>5.Fix the issue where clicking on a node can also trigger node_dragend event.</p>
<p>6.Fix that clicking on the canvas and nodes while not in the format brush will also trigger the painter_end event.</p>
<p>7.Fixed the issue where the mind map text editing box was not destroyed during node text editing and associated line text editing.</p>
</blockquote>
<p>New:</p>
<blockquote>
<p>1.When holding down the Ctrl key, disable the node double-click event.</p>
<p>2.Support configuring the behavior when creating new nodes: focusing and entering editing, not focusing, only focusing.</p>
<p>3.When searching in read-only mode, add a highlight effect to the currently matched node.</p>
<p>4.The default behavior of the mouse scroll wheel is to move the canvas up and down; The default is to scroll forward to enlarge the canvas and zoom back.</p>
<p>5.When the mouse scroll wheel behavior is to move the canvas up and down, it supports holding down the Ctrl key to zoom in and out of the canvas.</p>
</blockquote>
<p>Demo支持配置创建新节点时的行为</p>
<h2>0.9.0</h2>
<p>New:</p>
<p>1.Support adding summaries to some child nodes of the same node.</p>
<p>2.Moving the mouse into the summary will highlight its node.</p>
<p>3.Importing and exporting xmind files supports processing profiles.</p>
<h2>0.8.1</h2>
<p>Fix</p>
<blockquote>
<p>1.Fix the issue where the activation status of the summary node in the history data has not been deleted, which can cause data to be triggered when clicking on the summary node_ Change event.</p>
<p>2.Fix the issue of blank pages and exceptions thrown by the console when running in Safari browser.</p>
<p>3.Fixed the issue of icon floating layer and note floating layer detached from nodes when scaling the canvas.</p>
<p>4.Fixed the issue of selecting all nodes in read-only mode.</p>
<p>5.Fix the presence of node content has   in rich text mode; Error exporting as image.</p>
<p>6.Fixed the issue of overlapping profiles when adding profiles to oneself first and then to subordinates; Fix the issue of overlapping profiles when adding profiles to nodes with hierarchical relationships at the same time.</p>
<p>7.Fix the issue of exporting PDF errors when there are many nodes.</p>
</blockquote>
<p>New</p>
<blockquote>
<p>1.Add a configuration option that prohibits dragging the canvas.</p>
<p>2.Add a configuration option to prohibit double finger scaling of the canvas.</p>
<p>3.Add compression parameters to the method of exporting PNG; Optimize the problem of excessive volume when exporting PDF from nodes with large amounts of data.</p>
<p>4.Rename the isParent method of the node instance to isAncestor and add the isParent method at the same time.</p>
</blockquote>
<p>Demo:</p>
<blockquote>
<p>1.Fixed the issue of being able to search for replacement and edit outlines in read-only mode.</p>
<p>2.Fix the issue where the outline cannot be displayed and edited when the node content is an HTML tag.</p>
<p>3.Fix the issue where when multiple nodes are selected and icons are added at the same time, all node icons will be unified as the icon of the first node.</p>
<p>4.Adding loading to the export operation.</p>
</blockquote>
<h2>0.8.0-fix.1</h2>
<p>Fix: Fixed the issue of creating a new node using direct paste if the pasted content contains HTML label symbols such as &lt;&gt; and the newly created node content is empty.</p>
<h2>0.8.0</h2>
<p>Breaking change: Greatly optimize some of the code and slightly improve performance, mainly by using the 'render' class to remove useless logic, adjust unreasonable implementations, and extract duplicate code; Modify function names, functions, etc.</p>
<p>Fix:</p>

View File

@@ -37,15 +37,15 @@ const mindMap = new MindMap({
| textContentMargin | Number | 2 | The spacing between various text information in the node, such as the spacing between the icon and text | |
| selectTranslateStep | Number | 3 | The canvas offset when mouse moves to the edge during multi-select node | |
| selectTranslateLimit | Number | 20 | The distance from the edge when the canvas begins to offset during multi-select node | |
| customNoteContentShowv0.1.6+ | Object | null | Custom node note content display, object type, structure: {show: (noteContent, left, top) => {// your display node note logic }, hide: () => {// your hide node note logic }} | |
| customNoteContentShowv0.1.6+ | Object | null | Custom node note content display, object type, structure: {show: (noteContent, left, top, node) => {// your display node note logic. node is a new parameter added in v0.8.1+ version, representing node instances }, hide: () => {// your hide node note logic }} | |
| readonlyv0.1.7+ | Boolean | false | Whether it is read-only mode | |
| enableFreeDragv0.2.4+ | Boolean | false | Enable node free(Free drag means that nodes can be dragged to any position on the canvas. Please note that it is not a function of dragging nodes to become siblings of other nodes. The connection of free drag may have certain problems, so it is best not to use this feature) drag | |
| watermarkConfigv0.2.4+ | Object | | Watermark config, Please refer to the table 【Watermark config】 below for detailed configuration | |
| textAutoWrapWidthv0.3.4+ | Number | 500 | Each line of text in the node will wrap automatically when it reaches the width | |
| customHandleMousewheelv0.4.3+ | Function | null | User-defined mouse wheel event processing can pass a function, and the callback parameter is the event object | |
| mousewheelActionv0.4.3+ | String | zoom | The behavior of the mouse wheel, `zoom`(Zoom in and out)、`move`(Move up and down). If `customHandleMousewheel` passes a custom function, this property will not take effect | |
| mousewheelActionv0.4.3+ | String | zoomv0.9.1+ default is move | The behavior of the mouse wheel, `zoom`(Zoom in and out)、`move`(Move up and down). If `customHandleMousewheel` passes a custom function, this property will not take effect | |
| mousewheelMoveStepv0.4.3+ | Number | 100 | When the `mousewheelAction` is set to `move`, you can use this attribute to control the step length of the view movement when the mouse scrolls. The unit is `px` | |
| mousewheelZoomActionReversev0.6.5+ | Boolean | false | When `mousewheelAction` is set to `zoom`, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed | |
| mousewheelZoomActionReversev0.6.5+ | Boolean | falsev0.9.1+ default is true | When `mousewheelAction` is set to `zoom`, Or when holding down the Ctrl key, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed | |
| defaultInsertSecondLevelNodeTextv0.4.7+ | String | 二级节点 | Text of the default inserted secondary node | |
| defaultInsertBelowSecondLevelNodeTextv0.4.7+ | String | 分支主题 | Text for nodes below the second level inserted by default | |
| expandBtnStylev0.5.0+ | Object | { color: '#808080', fill: '#fff', fontSize: 13, strokeColor: '#333333' } | Expand the color of the stow button, (The fontSize and strokeColor fields were added in version 0.7.0+to set the text style for displaying the number of nodes when folded) | |
@@ -100,6 +100,10 @@ const mindMap = new MindMap({
| defaultGeneralizationTextv0.8.0+ | String | 概要 | Insert default text for summary | |
| handleIsSplitByWrapOnPasteCreateNewNodev0.8.0+ | Function / null | null | When creating a new node by pasting text, control whether to automatically split the nodes based on line breaks. If there is a line break, multiple nodes will be created based on the line break. Otherwise, only one node will be created, and a function can be passed to return promise. resolve represents splitting based on line breaks, and reject represents ignoring line breaks | |
| addHistoryTimev0.8.0+ | Number | 100 | Only one historical record can be added within the specified time to avoid adding unnecessary intermediate states. Unit: ms | |
| isDisableDragv0.8.1+ | Boolean | false | Is disable dragging the canvas | |
| disableTouchZoomv0.8.1+ | Boolean | false | Prohibit double finger scaling, you can still use the API for scaling, which takes effect on the TouchEvent plugin | |
| highlightNodeBoxStylev0.9.0+ | Object | { stroke: 'rgb(94, 200, 248)', fill: 'transparent' } | Highlight box style when the mouse moves into the summary to highlight the node it belongs to | |
| createNewNodeBehaviorv0.9.1+ | String | default | Behavior when creating a new node. defaultBy default, newly created nodes will be activated and enter editing mode. If multiple new nodes are created simultaneously, they will only be activated and will not enter editing mode、notActiveDo not activate newly created nodes、activeOnlyOnly activate newly created nodes and do not enter editing mode | |
### Data structure
@@ -408,6 +412,7 @@ Listen to an event. Event list:
| view_theme_changev0.6.12+ | Triggered after calling the setTheme method to set the theme | themetheme name |
| set_datav0.7.3+ | Triggered when the setData method is called to dynamically set mind map data | dataNew Mind Map Data |
| resizev0.8.0+ | Triggered after the container size changes, actually when the 'resize' method of the mind map instance is called | |
| beforeDestroyv0.9.0+ | Triggered before destroying the mind map, i.e. triggered by calling the destroy method | |
### emit(event, ...args)
@@ -519,7 +524,7 @@ redo. All commands are as follows:
| GO_TARGET_NODEv0.6.7+ | Navigate to a node, and if the node is collapsed, it will automatically expand to that node | nodeNode instance or node uid to locate、callbackv0.6.9+, Callback function after positioning completion |
| INSERT_MULTI_NODEv0.7.2+ | Insert multiple sibling nodes into the specified node at the same time, with the operating node being the currently active node or the specified node | appointNodesOptional, specify nodes, specify multiple nodes to pass an array, nodeListData list of newly inserted nodes, array type |
| INSERT_MULTI_CHILD_NODEv0.7.2+ | Insert multiple child nodes into the specified node simultaneously, with the operation node being the currently active node or the specified node | appointNodesOptional, specify nodes, specify multiple nodes to pass an array, childListData list of newly inserted nodes, array type |
| INSERT_FORMULAv0.7.2+ | Insert mathematical formulas into nodes, operate on the currently active node or specified node | formulaMathematical formula to insert, LaText syntax, appointNodesOptional, specify the node to insert the formula into. Multiple nodes can be passed as arrays, otherwise it defaults to the currently active node |
| INSERT_FORMULAv0.7.2+ | Insert mathematical formulas into nodes, operate on the currently active node or specified node | formulaMathematical formula to insert, LaTeX syntax, appointNodesOptional, specify the node to insert the formula into. Multiple nodes can be passed as arrays, otherwise it defaults to the currently active node |
| INSERT_PARENT_NODEv0.8.0+ | Insert a parent node into the specified node, with the operation node being the currently active node or the specified node | openEditActivate the newly inserted node and enter editing mode, default to 'true'`)、 appointNodesOptional, specify the node to insert into the parent node, and specify that multiple nodes can pass an array、 appointDataOptional, specify the data for the newly created node, such as {text: 'xxx', ...}, Detailed structure can be referenced [exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) |
| REMOVE_CURRENT_NODEv0.8.0+ | Delete only the current node, operate on the currently active node or specified node | appointNodesOptional, specify the nodes to be deleted, and multiple nodes can be passed as an array |
@@ -592,4 +597,4 @@ Register plugin, Use `MindMap.usePlugin` to register plugin only before instanti
> v0.4.0+
Remove registered plugin, Plugins registered through the `usePlugin` or `addPlugin` methods can be removed.
Remove registered plugin, Plugins registered through the `usePlugin` or `addPlugin` methods can be removed.

View File

@@ -123,7 +123,7 @@
<td>customNoteContentShowv0.1.6+</td>
<td>Object</td>
<td>null</td>
<td>Custom node note content display, object type, structure: {show: (noteContent, left, top) =&gt; {// your display node note logic }, hide: () =&gt; {// your hide node note logic }}</td>
<td>Custom node note content display, object type, structure: {show: (noteContent, left, top, node) =&gt; {// your display node note logic. node is a new parameter added in v0.8.1+ version, representing node instances }, hide: () =&gt; {// your hide node note logic }}</td>
<td></td>
</tr>
<tr>
@@ -164,7 +164,7 @@
<tr>
<td>mousewheelActionv0.4.3+</td>
<td>String</td>
<td>zoom</td>
<td>zoomv0.9.1+ default is move</td>
<td>The behavior of the mouse wheel, <code>zoom</code>(Zoom in and out)<code>move</code>(Move up and down). If <code>customHandleMousewheel</code> passes a custom function, this property will not take effect</td>
<td></td>
</tr>
@@ -178,8 +178,8 @@
<tr>
<td>mousewheelZoomActionReversev0.6.5+</td>
<td>Boolean</td>
<td>false</td>
<td>When <code>mousewheelAction</code> is set to <code>zoom</code>, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed</td>
<td>falsev0.9.1+ default is true</td>
<td>When <code>mousewheelAction</code> is set to <code>zoom</code>, Or when holding down the Ctrl key, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed</td>
<td></td>
</tr>
<tr>
@@ -560,6 +560,34 @@
<td>Only one historical record can be added within the specified time to avoid adding unnecessary intermediate states. Unit: ms</td>
<td></td>
</tr>
<tr>
<td>isDisableDragv0.8.1+</td>
<td>Boolean</td>
<td>false</td>
<td>Is disable dragging the canvas</td>
<td></td>
</tr>
<tr>
<td>disableTouchZoomv0.8.1+</td>
<td>Boolean</td>
<td>false</td>
<td>Prohibit double finger scaling, you can still use the API for scaling, which takes effect on the TouchEvent plugin</td>
<td></td>
</tr>
<tr>
<td>highlightNodeBoxStylev0.9.0+</td>
<td>Object</td>
<td>{ stroke: 'rgb(94, 200, 248)', fill: 'transparent' }</td>
<td>Highlight box style when the mouse moves into the summary to highlight the node it belongs to</td>
<td></td>
</tr>
<tr>
<td>createNewNodeBehaviorv0.9.1+</td>
<td>String</td>
<td>default</td>
<td>Behavior when creating a new node. defaultBy default, newly created nodes will be activated and enter editing mode. If multiple new nodes are created simultaneously, they will only be activated and will not enter editing mode、notActiveDo not activate newly created nodes、activeOnlyOnly activate newly created nodes and do not enter editing mode</td>
<td></td>
</tr>
</tbody>
</table>
<h3>Data structure</h3>
@@ -1032,6 +1060,11 @@ poor performance and should be used sparingly.</p>
<td>Triggered after the container size changes, actually when the 'resize' method of the mind map instance is called</td>
<td></td>
</tr>
<tr>
<td>beforeDestroyv0.9.0+</td>
<td>Triggered before destroying the mind map, i.e. triggered by calling the destroy method</td>
<td></td>
</tr>
</tbody>
</table>
<h3>emit(event, ...args)</h3>
@@ -1270,7 +1303,7 @@ redo. All commands are as follows:</p>
<tr>
<td>INSERT_FORMULAv0.7.2+</td>
<td>Insert mathematical formulas into nodes, operate on the currently active node or specified node</td>
<td>formulaMathematical formula to insert, LaText syntax, appointNodesOptional, specify the node to insert the formula into. Multiple nodes can be passed as arrays, otherwise it defaults to the currently active node</td>
<td>formulaMathematical formula to insert, LaTeX syntax, appointNodesOptional, specify the node to insert the formula into. Multiple nodes can be passed as arrays, otherwise it defaults to the currently active node</td>
</tr>
<tr>
<td>INSERT_PARENT_NODEv0.8.0+</td>

View File

@@ -38,7 +38,7 @@ a.download = 'xxx'
a.click()
```
### png(name, transparent = false, checkRotate)
### png(name, transparent = false, checkRotate, compress)
> Versions below v0.7.0 are: png(name, transparent = false, rotateWhenWidthLongerThenHeight)
@@ -48,7 +48,9 @@ a.click()
- `rotateWhenWidthLongerThenHeight`: v0.6.15+, V0.7.0+abandoned, Boolean, false, Automatically rotate 90 degrees when the image has a width to height ratio
- `checkRotate`: v0.7.0+, Function, You can pass a function that takes two parameters, the width and height of the image, and returns true or false. True represents that the image needs to be rotated by 90 degrees.
- `checkRotate`: v0.7.0+, Function, You can pass a function that takes two parameters, the width and height of the image, and returns true or false. True represents that the image needs to be rotated by 90 degrees
- `compress`v0.8.1+null | { width, height }, The parameter for compressing images. In some cases, the length and width of the exported image may be very large. If you want to reduce it, you can use this parameter to control it. Only one width or height can be provided, and it will be scaled proportionally
Exports as `png`.
@@ -72,7 +74,7 @@ svg(
Exports as `svg`.
### pdf(name, useMultiPageExport)
### pdf(name, useMultiPageExport, maxImageWidth)
> v0.2.1+
@@ -80,6 +82,8 @@ Exports as `svg`.
- `useMultiPageExport`: v0.6.15+, Boolean, false, Whether to export multiple pages, default to single page
- `maxImageWidth`v0.8.1+null | NumberThe default is twice the width of A4 paper, which is a parameter for compressing images. In some cases, the length and width of the image may be very large, resulting in a very large PDF volume. Therefore, if you want to reduce the volume, you can use this parameter to control the maximum width of the image
Export as `pdf`. Unlike other export methods, this method does not return data and directly triggers the download.
> After v0.6.0, an additional ExportPDF plugin needs to be registered

View File

@@ -27,7 +27,7 @@ a.href = <span class="hljs-string">&#x27;xxx.png&#x27;</span><span class="hljs-c
a.download = <span class="hljs-string">&#x27;xxx&#x27;</span>
a.click()
</code></pre>
<h3>png(name, transparent = false, checkRotate)</h3>
<h3>png(name, transparent = false, checkRotate, compress)</h3>
<blockquote>
<p>Versions below v0.7.0 are: png(name, transparent = false, rotateWhenWidthLongerThenHeight)</p>
</blockquote>
@@ -42,7 +42,10 @@ a.click()
<p><code>rotateWhenWidthLongerThenHeight</code>: v0.6.15+, V0.7.0+abandoned, Boolean, false, Automatically rotate 90 degrees when the image has a width to height ratio</p>
</li>
<li>
<p><code>checkRotate</code>: v0.7.0+, Function, You can pass a function that takes two parameters, the width and height of the image, and returns true or false. True represents that the image needs to be rotated by 90 degrees.</p>
<p><code>checkRotate</code>: v0.7.0+, Function, You can pass a function that takes two parameters, the width and height of the image, and returns true or false. True represents that the image needs to be rotated by 90 degrees</p>
</li>
<li>
<p><code>compress</code>v0.8.1+null | { width, height }, The parameter for compressing images. In some cases, the length and width of the exported image may be very large. If you want to reduce it, you can use this parameter to control it. Only one width or height can be provided, and it will be scaled proportionally</p>
</li>
</ul>
<p>Exports as <code>png</code>.</p>
@@ -66,7 +69,7 @@ a.click()
)
</code></pre>
<p>Exports as <code>svg</code>.</p>
<h3>pdf(name, useMultiPageExport)</h3>
<h3>pdf(name, useMultiPageExport, maxImageWidth)</h3>
<blockquote>
<p>v0.2.1+</p>
</blockquote>
@@ -77,6 +80,9 @@ a.click()
<li>
<p><code>useMultiPageExport</code>: v0.6.15+, Boolean, false, Whether to export multiple pages, default to single page</p>
</li>
<li>
<p><code>maxImageWidth</code>v0.8.1+null | NumberThe default is twice the width of A4 paper, which is a parameter for compressing images. In some cases, the length and width of the image may be very large, resulting in a very large PDF volume. Therefore, if you want to reduce the volume, you can use this parameter to control the maximum width of the image</p>
</li>
</ul>
<p>Export as <code>pdf</code>. Unlike other export methods, this method does not return data and directly triggers the download.</p>
<blockquote>

View File

@@ -67,6 +67,8 @@ The folder containing the packaged resources for the `web` folder.
[How does the dom-to-image library convert HTML into images](https://juejin.cn/post/7287913415803764747)
[Two days to achieve collaborative editing of mind maps? It's really possible to use Yjs](https://juejin.cn/post/7295669711533998117)
## Special Note
This project can be used for learning and reference. Please deeply experience whether it can meet your needs when using it for actual projects.
@@ -233,4 +235,26 @@ Open source is not easy. If this project is helpful to you, you can invite the a
<img src="../../../../assets/avatar/天清如愿.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>天清如愿</p>
</div>
</div>
<div style="display: flex;">
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/敬明朗.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>敬明朗</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>飞箭</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/戚永峰.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>戚永峰</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/moom.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>moom</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/张扬.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>张扬</p>
</div>
</div>

View File

@@ -52,6 +52,7 @@ full screen, support mini map</li>
<p><a href="https://juejin.cn/post/7233012756314701884">My first Electron application</a></p>
<p><a href="https://juejin.cn/post/7276712861514170409">Explore how to export HTML and SVG as images</a></p>
<p><a href="https://juejin.cn/post/7287913415803764747">How does the dom-to-image library convert HTML into images</a></p>
<p><a href="https://juejin.cn/post/7295669711533998117">Two days to achieve collaborative editing of mind maps? It's really possible to use Yjs</a></p>
<h2>Special Note</h2>
<p>This project can be used for learning and reference. Please deeply experience whether it can meet your needs when using it for actual projects.</p>
<p>This project may not have fully tested every function point, so there may be bugs. In addition, when the number of nodes is very large, there may be some performance issues. Because everyone can accept different levels of congestion, you can test the maximum number of nodes yourself. Generally speaking, within 500 nodes, it is more smooth, while over 1000 nodes have more noticeable lag.</p>
@@ -190,6 +191,28 @@ full screen, support mini map</li>
<img src="../../../../assets/avatar/天清如愿.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>天清如愿</p>
</div>
</div>
<div style="display: flex;">
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/敬明朗.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>敬明朗</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>飞箭</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/戚永峰.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>戚永峰</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/moom.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>moom</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/张扬.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>张扬</p>
</div>
</div>
</div>
</template>

View File

@@ -56,6 +56,50 @@ Whether the node is currently being dragged
## Methods
### setGeneralizationOpacity(val)
> v0.9.0+
- `val`Number, 0-1Opacity
Set the transparency of summary nodes and curves.
### formatGetGeneralization()
> v0.9.0+
Obtain node summary data.
### getIndexInBrothers()
> v0.9.0+
Gets the index of the node in the sibling node list.
### getRectInSvg()
> v0.9.0+
Obtain the size and position information of the node. The width and height are the actual width and height after applying the scaling effect, and the position information is relative to the canvas.
### getRect()
> v0.8.1+
Obtain the size and position information of the node. The width and height are the actual width and height after applying the scaling effect, and the position is relative to the upper left corner of the browser window.
### ancestorHasGeneralization()
> v0.8.1+
Check if there are ancestor nodes with a summary.
### getNoteContentPosition()
> v0.8.1+
Obtain the display position of node comments. When a node has comments and is displaying a state, dragging or zooming will cause the comment floating layer to detach from the node. This method can be used to obtain a new position and update the comment floating layer.
### updateNodeByActive(active)
> v0.8.0+
@@ -207,9 +251,15 @@ Show node and its sub-nodes
### isParent(node)
> v0.1.5+
> v0.1.5+Detect whether the current node is an ancestor node of a certain node
Check if the current node is an ancestor of a certain node
> v0.8.1+Detect whether the current node is the parent node of a certain node
### isAncestor(node)
> v0.8.1+
Detect whether the current node is an ancestor node of a certain node
### isBrother(node)
@@ -223,6 +273,12 @@ Check if the current node is a sibling of a certain node
Check if there is a summary
### checkHasSelfGeneralization()
> v0.9.0+
Check if there is a summary of oneself, not a sub node interval summary
### hideGeneralization()
> v0.2.0+

View File

@@ -31,6 +31,44 @@
</blockquote>
<p>Whether the node is currently being dragged</p>
<h2>Methods</h2>
<h3>setGeneralizationOpacity(val)</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<ul>
<li><code>val</code>Number, 0-1Opacity</li>
</ul>
<p>Set the transparency of summary nodes and curves.</p>
<h3>formatGetGeneralization()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>Obtain node summary data.</p>
<h3>getIndexInBrothers()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>Gets the index of the node in the sibling node list.</p>
<h3>getRectInSvg()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>Obtain the size and position information of the node. The width and height are the actual width and height after applying the scaling effect, and the position information is relative to the canvas.</p>
<h3>getRect()</h3>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>Obtain the size and position information of the node. The width and height are the actual width and height after applying the scaling effect, and the position is relative to the upper left corner of the browser window.</p>
<h3>ancestorHasGeneralization()</h3>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>Check if there are ancestor nodes with a summary.</p>
<h3>getNoteContentPosition()</h3>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>Obtain the display position of node comments. When a node has comments and is displaying a state, dragging or zooming will cause the comment floating layer to detach from the node. This method can be used to obtain a new position and update the comment floating layer.</p>
<h3>updateNodeByActive(active)</h3>
<blockquote>
<p>v0.8.0+</p>
@@ -143,9 +181,16 @@ nodeData, <code>SET_NODE_DATA</code> command's shortcut method</p>
<p>Show node and its sub-nodes</p>
<h3>isParent(node)</h3>
<blockquote>
<p>v0.1.5+</p>
<p>v0.1.5+Detect whether the current node is an ancestor node of a certain node</p>
</blockquote>
<p>Check if the current node is an ancestor of a certain node</p>
<blockquote>
<p>v0.8.1+Detect whether the current node is the parent node of a certain node</p>
</blockquote>
<h3>isAncestor(node)</h3>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>Detect whether the current node is an ancestor node of a certain node</p>
<h3>isBrother(node)</h3>
<blockquote>
<p>v0.1.5+</p>
@@ -156,6 +201,11 @@ nodeData, <code>SET_NODE_DATA</code> command's shortcut method</p>
<p>v0.2.0+</p>
</blockquote>
<p>Check if there is a summary</p>
<h3>checkHasSelfGeneralization()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>Check if there is a summary of oneself, not a sub node interval summary</p>
<h3>hideGeneralization()</h3>
<blockquote>
<p>v0.2.0+</p>

View File

@@ -15,6 +15,24 @@ Gets the root node of the node tree.
## Methods
### highlightNode(node, range)
> v0.9.0+
- `node`Target node instance to highlight
- `range`Optional, Array, a range array, [0, 1]
Highlight a node or child node. If the 'range' parameter is not passed, the specified 'node' node is directly highlighted. If the 'range' passes a range of child nodes to be highlighted, the child nodes of that range will be highlighted.
The highlight effect is wrapped by a rectangular box, and the stroke and fill style of the rectangle can be configured through the 'highlightNodeBoxStyle' instantiation option.
### closeHighlightNode()
> v0.9.0+
隐藏节点高亮框。
### setRootNodeCenter()
> v0.8.0+

View File

@@ -9,6 +9,25 @@ accessed through <code>mindMap.renderer</code>.</p>
<h3>root</h3>
<p>Gets the root node of the node tree.</p>
<h2>Methods</h2>
<h3>highlightNode(node, range)</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<ul>
<li>
<p><code>node</code>Target node instance to highlight</p>
</li>
<li>
<p><code>range</code>Optional, Array, a range array, [0, 1]</p>
</li>
</ul>
<p>Highlight a node or child node. If the 'range' parameter is not passed, the specified 'node' node is directly highlighted. If the 'range' passes a range of child nodes to be highlighted, the child nodes of that range will be highlighted.</p>
<p>The highlight effect is wrapped by a rectangular box, and the stroke and fill style of the rectangle can be configured through the 'highlightNodeBoxStyle' instantiation option.</p>
<h3>closeHighlightNode()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>隐藏节点高亮框</p>
<h3>setRootNodeCenter()</h3>
<blockquote>
<p>v0.8.0+</p>

View File

@@ -402,6 +402,28 @@ Reading text and images from the user's clipboard returns:
Remove the data of a node from its parent node's `nodeData.children` list.
#### checkHasSupSubRelation()
> v0.8.1+
Determine whether there is a hierarchical relationship from the given node instance list.
#### handleSelfCloseTags(str)
> v0.9.1+
- `str`: html string
Add a closed state to HTML self closing tags, `<div><img src="xxx"></div>` -> `<div><img src="xxx" /></div>`
#### checkNodeListIsEqual(list1, list2)
> v0.9.1+
- `list1/list2`: Node instance list
Check if the two node instance lists contain the same nodes.
## Simulate CSS background in Canvas
Import:

View File

@@ -328,6 +328,27 @@ and copying the <code>data</code> of the data object, example:</p>
<p>v0.8.0+</p>
</blockquote>
<p>Remove the data of a node from its parent node's <code>nodeData.children</code> list.</p>
<h4>checkHasSupSubRelation()</h4>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>Determine whether there is a hierarchical relationship from the given node instance list.</p>
<h4>handleSelfCloseTags(str)</h4>
<blockquote>
<p>v0.9.1+</p>
</blockquote>
<ul>
<li><code>str</code>: html string</li>
</ul>
<p>Add a closed state to HTML self closing tags, <code>&lt;div&gt;&lt;img src=&quot;xxx&quot;&gt;&lt;/div&gt;</code> -&gt; <code>&lt;div&gt;&lt;img src=&quot;xxx&quot; /&gt;&lt;/div&gt;</code>。</p>
<h4>checkNodeListIsEqual(list1, list2)</h4>
<blockquote>
<p>v0.9.1+</p>
</blockquote>
<ul>
<li><code>list1/list2</code>: Node instance list</li>
</ul>
<p>Check if the two node instance lists contain the same nodes.</p>
<h2>Simulate CSS background in Canvas</h2>
<p>Import:</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

View File

@@ -31,6 +31,7 @@ export default [
{ path: 'course22', title: '如何实现搜索、替换' },
{ path: 'course23', title: '如何渲染滚动条' },
{ path: 'course24', title: '如何开发一个插件' },
{ path: 'course25', title: '关于概要' },
{ path: 'doExport', title: 'Export 插件' },
{ path: 'drag', title: 'Drag插件' },
{ path: 'introduction', title: '简介' },

View File

@@ -1,5 +1,90 @@
# Changelog
## 0.9.1
修复:
> 1.修复自定义节点内容时导出图片、svg、pdf报错的问题。
>
> 2.优化节点激活事件的派发,激活节点未改变时不派发事件,短时间派发多次事件时跳过中间事件。
>
> 3.修复节点处于编辑状态时,通过鼠标滚动移动画布后编辑框和节点脱离的问题。
>
> 4.修复在节点编辑状态中通过鼠标滚轮缩放画布再退出节点编辑后快捷键失效的问题。
>
> 5.修复点击节点也会触发node_dragend事件的问题。
>
> 6.修复不在格式刷时点击画布和节点也会触发painter_end事件的问题。
>
> 7.修复在节点文本编辑中和关联线文本编辑中时销毁思维导图文本编辑框未被销毁的问题。
新增:
> 1.按住Ctrl键时禁用节点双击事件。
>
> 2.支持配置创建新节点时的行为:聚焦且进入编辑、不聚焦、只聚焦。
>
> 3.只读模式下搜索时给当前匹配到的节点增加高亮效果。
>
> 4.鼠标滚轮行为默认改为上下移动画布;默认改为向前滚动放大画布,向后缩小。
>
> 5.在鼠标滚轮行为为上下移动画布时支持按住Ctrl键进行放大缩小画布。
Demo支持配置创建新节点时的行为。
## 0.9.0
新增:
1.支持对同一个节点的部分子节点添加概要。
2.鼠标移入概要会高亮其所属节点。
3.导入和导出xmind文件支持处理概要。
## 0.8.1
修复:
> 1.修复历史记录数据中概要节点的激活状态未被删除的问题会导致点击概要节点时触发data_change事件。
>
> 2.修复在safari浏览器中运行时页面空白且控制台抛出异常的问题。
>
> 3.修复缩放画布时图标浮层和备注浮层和节点脱离的问题。
>
> 4.修复只读模式下可以全选节点的问题。
>
> 5.修复富文本模式下节点内容存在&nbsp;时导出为图片出错的问题。
>
> 6.修复先给自身添加概要,再给下级添加概要会出现概要重叠的问题;修复同时给存在上下级关系的节点添加概要时概要重叠的问题。
>
> 7.修复节点数量很多的情况下导出pdf报错的问题。
新增:
> 1.新增禁止拖动画布的配置选项。
>
> 2.新增禁止双指缩放画布的配置选项。
>
> 3.导出png的方法新增压缩参数优化大数据量节点导出pdf时体积过大的问题。
>
> 4.将节点实例的isParent方法改名为isAncestor同时新增isParent方法。
Demo
> 1.修复只读模式下仍旧可以搜索替换和编辑大纲的问题。
>
> 2.修复节点内容为html标签时大纲无法显示和编辑的问题。
>
> 3.修复同时选中多个节点添加图标时,所有节点图标都会统一为第一个节点的图标的问题。
>
> 4.导出操作增加loading。
## 0.8.0-fix.1
修复:修复直接粘贴的方式创建新节点时如果粘贴的内容带有<>等html标签符号时新创建的节点内容为空的问题。
## 0.8.0
破坏性更新:大幅优化部分代码,小幅提升性能,主要是`render`类,删除无用逻辑、调整不合理的实现、提取重复代码;修改函数名称、函数功能等。

View File

@@ -1,6 +1,58 @@
<template>
<div>
<h1>Changelog</h1>
<h2>0.9.1</h2>
<p>修复</p>
<blockquote>
<p>1.修复自定义节点内容时导出图片svgpdf报错的问题</p>
<p>2.优化节点激活事件的派发激活节点未改变时不派发事件短时间派发多次事件时跳过中间事件</p>
<p>3.修复节点处于编辑状态时通过鼠标滚动移动画布后编辑框和节点脱离的问题</p>
<p>4.修复在节点编辑状态中通过鼠标滚轮缩放画布再退出节点编辑后快捷键失效的问题</p>
<p>5.修复点击节点也会触发node_dragend事件的问题</p>
<p>6.修复不在格式刷时点击画布和节点也会触发painter_end事件的问题</p>
<p>7.修复在节点文本编辑中和关联线文本编辑中时销毁思维导图文本编辑框未被销毁的问题</p>
</blockquote>
<p>新增</p>
<blockquote>
<p>1.按住Ctrl键时禁用节点双击事件</p>
<p>2.支持配置创建新节点时的行为聚焦且进入编辑不聚焦只聚焦</p>
<p>3.只读模式下搜索时给当前匹配到的节点增加高亮效果</p>
<p>4.鼠标滚轮行为默认改为上下移动画布默认改为向前滚动放大画布向后缩小</p>
<p>5.在鼠标滚轮行为为上下移动画布时支持按住Ctrl键进行放大缩小画布</p>
</blockquote>
<p>Demo支持配置创建新节点时的行为</p>
<h2>0.9.0</h2>
<p>新增</p>
<p>1.支持对同一个节点的部分子节点添加概要</p>
<p>2.鼠标移入概要会高亮其所属节点</p>
<p>3.导入和导出xmind文件支持处理概要</p>
<h2>0.8.1</h2>
<p>修复</p>
<blockquote>
<p>1.修复历史记录数据中概要节点的激活状态未被删除的问题会导致点击概要节点时触发data_change事件</p>
<p>2.修复在safari浏览器中运行时页面空白且控制台抛出异常的问题</p>
<p>3.修复缩放画布时图标浮层和备注浮层和节点脱离的问题</p>
<p>4.修复只读模式下可以全选节点的问题</p>
<p>5.修复富文本模式下节点内容存在 时导出为图片出错的问题</p>
<p>6.修复先给自身添加概要再给下级添加概要会出现概要重叠的问题修复同时给存在上下级关系的节点添加概要时概要重叠的问题</p>
<p>7.修复节点数量很多的情况下导出pdf报错的问题</p>
</blockquote>
<p>新增</p>
<blockquote>
<p>1.新增禁止拖动画布的配置选项</p>
<p>2.新增禁止双指缩放画布的配置选项</p>
<p>3.导出png的方法新增压缩参数优化大数据量节点导出pdf时体积过大的问题</p>
<p>4.将节点实例的isParent方法改名为isAncestor同时新增isParent方法</p>
</blockquote>
<p>Demo</p>
<blockquote>
<p>1.修复只读模式下仍旧可以搜索替换和编辑大纲的问题</p>
<p>2.修复节点内容为html标签时大纲无法显示和编辑的问题</p>
<p>3.修复同时选中多个节点添加图标时所有节点图标都会统一为第一个节点的图标的问题</p>
<p>4.导出操作增加loading</p>
</blockquote>
<h2>0.8.0-fix.1</h2>
<p>修复修复直接粘贴的方式创建新节点时如果粘贴的内容带有&lt;&gt;等html标签符号时新创建的节点内容为空的问题</p>
<h2>0.8.0</h2>
<p>破坏性更新大幅优化部分代码小幅提升性能主要是<code>render</code>删除无用逻辑调整不合理的实现提取重复代码修改函数名称函数功能等</p>
<p>修复</p>

View File

@@ -37,15 +37,15 @@ const mindMap = new MindMap({
| textContentMargin | Number | 2 | 节点里各种文字信息的间距,如图标和文字的间距 |
| selectTranslateStep | Number | 3 | 多选节点时鼠标移动到边缘时的画布移动偏移量 |
| selectTranslateLimit | Number | 20 | 多选节点时鼠标移动距边缘多少距离时开始偏移 |
| customNoteContentShowv0.1.6+ | Object | null | 自定义节点备注内容显示Object类型结构为{show: (noteContent, left, top) => {// 你的显示节点备注逻辑 }, hide: () => {// 你的隐藏节点备注逻辑 }} |
| customNoteContentShowv0.1.6+ | Object | null | 自定义节点备注内容显示Object类型结构为{show: (noteContent, left, top, node) => {// 你的显示节点备注逻辑。node为v0.8.1+版本新增的回参,代表节点实例 }, hide: () => {// 你的隐藏节点备注逻辑 }} |
| readonlyv0.1.7+ | Boolean | false | 是否是只读模式 |
| enableFreeDragv0.2.4+ | Boolean | false | 是否开启节点自由拖拽(自由拖拽即可以把节点拖拽到画布的任意位置,注意不是拖拽节点成为其他节点的子节点兄弟节点的功能,自由拖拽的连线会存在一定问题,所以该特性最好不要使用) |
| watermarkConfigv0.2.4+ | Object | | 水印配置,详细配置请参考下方表格【水印配置】 |
| textAutoWrapWidthv0.3.4+ | Number | 500 | 节点内每行文本达到该宽度后自动换行 |
| customHandleMousewheelv0.4.3+ | Function | null | 自定义鼠标滚轮事件处理,可以传一个函数,回调参数为事件对象 |
| mousewheelActionv0.4.3+ | String | zoom | 鼠标滚轮的行为,`zoom`(放大缩小)、`move`(上下移动)。如果`customHandleMousewheel`传了自定义函数,这个属性不生效 |
| mousewheelActionv0.4.3+ | String | zoomv0.9.1+默认改为move | 鼠标滚轮的行为,`zoom`(放大缩小)、`move`(上下移动)。如果`customHandleMousewheel`传了自定义函数,这个属性不生效 |
| mousewheelMoveStepv0.4.3+ | Number | 100 | 当`mousewheelAction`设为`move`时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位`px` |
| mousewheelZoomActionReversev0.6.5+ | Boolean | false | 当mousewheelAction设为zoom时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来 |
| mousewheelZoomActionReversev0.6.5+ | Boolean | falsev0.9.1+默认改为true | 当mousewheelAction设为zoom时或者按住Ctrl键时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来 |
| defaultInsertSecondLevelNodeTextv0.4.7+ | String | 二级节点 | 默认插入的二级节点的文字 |
| defaultInsertBelowSecondLevelNodeTextv0.4.7+ | String | 分支主题 | 默认插入的二级以下节点的文字 |
| expandBtnStylev0.5.0+ | Object | { color: '#808080', fill: '#fff', fontSize: 13, strokeColor: '#333333' } | 展开收起按钮的颜色fontSize及strokeColor字段为0.7.0+版本新增的,用于设置收起时显示节点数量的文字样式) |
@@ -100,6 +100,10 @@ const mindMap = new MindMap({
| defaultGeneralizationTextv0.8.0+ | String | 概要 | 插入概要的默认文本 |
| handleIsSplitByWrapOnPasteCreateNewNodev0.8.0+ | Function / null | null | 粘贴文本的方式创建新节点时控制是否按换行自动分割节点即如果存在换行那么会根据换行创建多个节点否则只会创建一个节点可以传递一个函数返回promiseresolve代表根据换行分割reject代表忽略换行 |
| addHistoryTimev0.8.0+ | Number | 100 | 指定时间内只允许添加一次历史记录避免添加没有必要的中间状态单位ms |
| isDisableDragv0.8.1+ | Boolean | false | 是否禁止拖动画布 |
| disableTouchZoomv0.8.1+ | Boolean | false | 禁止双指缩放你仍旧可以使用api进行缩放对TouchEvent插件生效 |
| highlightNodeBoxStylev0.9.0+ | Object | { stroke: 'rgb(94, 200, 248)', fill: 'transparent' } | 鼠标移入概要高亮所属节点时的高亮框样式 |
| createNewNodeBehaviorv0.9.1+ | String | default | 创建新节点时的行为。default默认会激活新创建的节点并且进入编辑模式。如果同时创建了多个新节点那么只会激活而不会进入编辑模式、notActive不激活新创建的节点、activeOnly只激活新创建的节点不进入编辑模式 |
### 数据结构
@@ -405,6 +409,7 @@ mindMap.setTheme('主题名称')
| view_theme_changev0.6.12+ | 调用了setTheme方法设置主题后触发 | theme设置的新主题名称 |
| set_datav0.7.3+ | 调用了setData方法动态设置思维导图数据时触发 | data新的思维导图数据 |
| resizev0.8.0+ | 容器尺寸改变后触发,实际上是当思维导图实例的`resize`方法被调用后触发 | |
| beforeDestroyv0.9.0+ | 思维导图销毁前触发即调用了destroy方法触发 | |
### emit(event, ...args)
@@ -514,7 +519,7 @@ mindMap.updateConfig({
| GO_TARGET_NODEv0.6.7+ | 定位到某个节点,如果该节点被收起,那么会自动展开到该节点 | node要定位到的节点实例或节点uid、callbackv0.6.9+,定位完成后的回调函数) |
| INSERT_MULTI_NODEv0.7.2+ | 给指定的节点同时插入多个同级节点,操作节点为当前激活的节点或指定节点 | appointNodes可选指定节点指定多个节点可以传一个数组, nodeList新插入节点的数据列表数组类型 |
| INSERT_MULTI_CHILD_NODEv0.7.2+ | 给指定的节点同时插入多个子节点,操作节点为当前激活的节点或指定节点 | appointNodes可选指定节点指定多个节点可以传一个数组, childList新插入节点的数据列表数组类型 |
| INSERT_FORMULAv0.7.2+ | 给节点插入数学公式,操作节点为当前激活的节点或指定节点 | formula要插入的数学公式LaText语法), appointNodes可选指定要插入公式的节点多个节点可以传数组否则默认为当前激活的节点 |
| INSERT_FORMULAv0.7.2+ | 给节点插入数学公式,操作节点为当前激活的节点或指定节点 | formula要插入的数学公式LaTeX 语法), appointNodes可选指定要插入公式的节点多个节点可以传数组否则默认为当前激活的节点 |
| INSERT_PARENT_NODEv0.8.0+ | 给指定的节点插入父节点,操作节点为当前激活的节点或指定节点 | openEdit是否激活新插入的节点并进入编辑模式默认为`true`)、 appointNodes可选指定要插入父节点的节点指定多个节点可以传一个数组、 appointData可选指定新创建节点的数据比如{text: 'xxx', ...},详细结构可以参考[exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) |
| REMOVE_CURRENT_NODEv0.8.0+ | 仅删除当前节点,操作节点为当前激活的节点或指定节点 | appointNodes可选指定要删除的节点指定多个节点可以传一个数组 |
@@ -580,4 +585,4 @@ mindMap.updateConfig({
> v0.4.0+
移除注册的插件,无论是通过`usePlugin`还是`addPlugin`方法注册的插件都可以移除。
移除注册的插件,无论是通过`usePlugin`还是`addPlugin`方法注册的插件都可以移除。

View File

@@ -109,7 +109,7 @@
<td>customNoteContentShowv0.1.6+</td>
<td>Object</td>
<td>null</td>
<td>自定义节点备注内容显示Object类型结构为{show: (noteContent, left, top) =&gt; {// 你的显示节点备注逻辑 }, hide: () =&gt; {// 你的隐藏节点备注逻辑 }}</td>
<td>自定义节点备注内容显示Object类型结构为{show: (noteContent, left, top, node) =&gt; {// 你的显示节点备注逻辑。node为v0.8.1+版本新增的回参,代表节点实例 }, hide: () =&gt; {// 你的隐藏节点备注逻辑 }}</td>
</tr>
<tr>
<td>readonlyv0.1.7+</td>
@@ -144,7 +144,7 @@
<tr>
<td>mousewheelActionv0.4.3+</td>
<td>String</td>
<td>zoom</td>
<td>zoomv0.9.1+默认改为move</td>
<td>鼠标滚轮的行为<code>zoom</code>放大缩小<code>move</code>上下移动如果<code>customHandleMousewheel</code>传了自定义函数这个属性不生效</td>
</tr>
<tr>
@@ -156,8 +156,8 @@
<tr>
<td>mousewheelZoomActionReversev0.6.5+</td>
<td>Boolean</td>
<td>false</td>
<td>当mousewheelAction设为zoom时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来</td>
<td>falsev0.9.1+默认改为true</td>
<td>当mousewheelAction设为zoom时或者按住Ctrl键时默认向前滚动是缩小向后滚动是放大如果该属性设为true那么会反过来</td>
</tr>
<tr>
<td>defaultInsertSecondLevelNodeTextv0.4.7+</td>
@@ -483,6 +483,30 @@
<td>100</td>
<td>指定时间内只允许添加一次历史记录避免添加没有必要的中间状态单位ms</td>
</tr>
<tr>
<td>isDisableDragv0.8.1+</td>
<td>Boolean</td>
<td>false</td>
<td>是否禁止拖动画布</td>
</tr>
<tr>
<td>disableTouchZoomv0.8.1+</td>
<td>Boolean</td>
<td>false</td>
<td>禁止双指缩放你仍旧可以使用api进行缩放对TouchEvent插件生效</td>
</tr>
<tr>
<td>highlightNodeBoxStylev0.9.0+</td>
<td>Object</td>
<td>{ stroke: 'rgb(94, 200, 248)', fill: 'transparent' }</td>
<td>鼠标移入概要高亮所属节点时的高亮框样式</td>
</tr>
<tr>
<td>createNewNodeBehaviorv0.9.1+</td>
<td>String</td>
<td>default</td>
<td>创建新节点时的行为default默认会激活新创建的节点并且进入编辑模式如果同时创建了多个新节点那么只会激活而不会进入编辑模式notActive不激活新创建的节点activeOnly只激活新创建的节点不进入编辑模式</td>
</tr>
</tbody>
</table>
<h3>数据结构</h3>
@@ -948,6 +972,11 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
<td>容器尺寸改变后触发实际上是当思维导图实例的<code>resize</code>方法被调用后触发</td>
<td></td>
</tr>
<tr>
<td>beforeDestroyv0.9.0+</td>
<td>思维导图销毁前触发即调用了destroy方法触发</td>
<td></td>
</tr>
</tbody>
</table>
<h3>emit(event, ...args)</h3>
@@ -1188,7 +1217,7 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
<tr>
<td>INSERT_FORMULAv0.7.2+</td>
<td>给节点插入数学公式操作节点为当前激活的节点或指定节点</td>
<td>formula要插入的数学公式LaText语法, appointNodes可选指定要插入公式的节点多个节点可以传数组否则默认为当前激活的节点</td>
<td>formula要插入的数学公式LaTeX 语法, appointNodes可选指定要插入公式的节点多个节点可以传数组否则默认为当前激活的节点</td>
</tr>
<tr>
<td>INSERT_PARENT_NODEv0.8.0+</td>

View File

@@ -82,7 +82,7 @@ mindMap.scrollbar.setScrollBarWrapSize(width, height)
然后你需要监听`scrollbar_change`方法来获取滚动条大小和位置数据:
```js
mindMap.('scrollbar_change', this.updateScrollbar)
mindMap.on('scrollbar_change', this.updateScrollbar)
// 根据事件返回的滚动条数据更新滚动条元素:
{

View File

@@ -70,7 +70,7 @@ mindMap.scrollbar.setScrollBarWrapSize(width, height)
</code></pre>
<p>如果容器大小发生了改变需要再次调用该方法传递改变后的大小</p>
<p>然后你需要监听<code>scrollbar_change</code>方法来获取滚动条大小和位置数据</p>
<pre class="hljs"><code>mindMap.(<span class="hljs-string">&#x27;scrollbar_change&#x27;</span>, <span class="hljs-built_in">this</span>.updateScrollbar)
<pre class="hljs"><code>mindMap.on(<span class="hljs-string">&#x27;scrollbar_change&#x27;</span>, <span class="hljs-built_in">this</span>.updateScrollbar)
<span class="hljs-comment">// 根据事件返回的滚动条数据更新滚动条元素:</span>
{

View File

@@ -0,0 +1,17 @@
# 关于概要
`simple-mind-map`概要的功能非常的不完善,具体能力和限制如下:
1.支持选中单个节点添加单个概要。
2.支持同时选中多个节点添加概要,其中具有同一个父节点的子节点的概要会被合并成一个,称为区间概要,其他的仍旧作为单个概要。
3.区间概要不会随着区间内的子节点变化而变化,只会在子节点数量无法满足区间时自动删除该区间概要。
4.概要节点后面无法再继续添加概要。
5.概要节点间、概要节点和普通节点间位置可能会产生冲突。
6.鼠标移入概要时会高亮其所属的节点。
概要功能大概率不会再完善,冲突问题也没有能力解决,所以对概要功能要求高的项目请谨慎使用`simple-mind-map`

View File

@@ -0,0 +1,24 @@
<template>
<div>
<h1>关于概要</h1>
<p><code>simple-mind-map</code>概要的功能非常的不完善具体能力和限制如下</p>
<p>1.支持选中单个节点添加单个概要</p>
<p>2.支持同时选中多个节点添加概要其中具有同一个父节点的子节点的概要会被合并成一个称为区间概要其他的仍旧作为单个概要</p>
<p>3.区间概要不会随着区间内的子节点变化而变化只会在子节点数量无法满足区间时自动删除该区间概要</p>
<p>4.概要节点后面无法再继续添加概要</p>
<p>5.概要节点间概要节点和普通节点间位置可能会产生冲突</p>
<p>6.鼠标移入概要时会高亮其所属的节点</p>
<p>概要功能大概率不会再完善冲突问题也没有能力解决所以对概要功能要求高的项目请谨慎使用<code>simple-mind-map</code></p>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -38,7 +38,7 @@ a.download = 'xxx'
a.click()
```
### png(name, transparent = false, checkRotate)
### png(name, transparent = false, checkRotate, compress)
> v0.7.0以下版本为: png(name, transparent = false, rotateWhenWidthLongerThenHeight)
@@ -48,7 +48,9 @@ a.click()
- `rotateWhenWidthLongerThenHeight`: v0.6.15+v0.7.0+已废弃Boolean, false, 是否在图片宽比高长时自动旋转90度
- `checkRotate`v0.7.0+Function可以传递一个函数接收图片的宽度和高度两个参数返回true或falsetrue代表图片需要旋转90度
- `checkRotate`v0.7.0+Function可以传递一个函数接收图片的宽度和高度两个参数返回true或falsetrue代表图片需要旋转90度
- `compress`v0.8.1+null | { width, height }, 压缩图片的参数,某些情况下导出的图片长宽可能非常大,如果希望减小,那么可以通过该参数来控制,宽或高只提供一个即可,会按比例缩放
导出为`png`
@@ -72,7 +74,7 @@ svg(
导出为`svg`
### pdf(name, useMultiPageExport)
### pdf(name, useMultiPageExport, maxImageWidth)
> v0.2.1+
@@ -80,6 +82,8 @@ svg(
- `useMultiPageExport`: v0.6.15+Boolean, false, 是否多页导出,默认为单页
- `maxImageWidth`v0.8.1+null | Number默认为a4纸的宽度的2倍, 压缩图片的参数某些情况下图片的长宽可能非常大导致pdf体积也非常大所以如果希望减小体积那么可以通过该参数来控制图片的最大宽度
导出为`pdf`,和其他导出方法不一样,这个方法不会返回数据,会直接触发下载。
> v0.6.0版本以后需要额外注册一个ExportPDF插件

View File

@@ -27,7 +27,7 @@ a.href = <span class="hljs-string">&#x27;xxx.png&#x27;</span><span class="hljs-c
a.download = <span class="hljs-string">&#x27;xxx&#x27;</span>
a.click()
</code></pre>
<h3>png(name, transparent = false, checkRotate)</h3>
<h3>png(name, transparent = false, checkRotate, compress)</h3>
<blockquote>
<p>v0.7.0以下版本为 png(name, transparent = false, rotateWhenWidthLongerThenHeight)</p>
</blockquote>
@@ -42,7 +42,10 @@ a.click()
<p><code>rotateWhenWidthLongerThenHeight</code>: v0.6.15+v0.7.0+已废弃Boolean, false, 是否在图片宽比高长时自动旋转90度</p>
</li>
<li>
<p><code>checkRotate</code>v0.7.0+Function可以传递一个函数接收图片的宽度和高度两个参数返回true或falsetrue代表图片需要旋转90度</p>
<p><code>checkRotate</code>v0.7.0+Function可以传递一个函数接收图片的宽度和高度两个参数返回true或falsetrue代表图片需要旋转90度</p>
</li>
<li>
<p><code>compress</code>v0.8.1+null | { width, height }, 压缩图片的参数某些情况下导出的图片长宽可能非常大如果希望减小那么可以通过该参数来控制宽或高只提供一个即可会按比例缩放</p>
</li>
</ul>
<p>导出为<code>png</code></p>
@@ -66,7 +69,7 @@ a.click()
)
</code></pre>
<p>导出为<code>svg</code></p>
<h3>pdf(name, useMultiPageExport)</h3>
<h3>pdf(name, useMultiPageExport, maxImageWidth)</h3>
<blockquote>
<p>v0.2.1+</p>
</blockquote>
@@ -77,6 +80,9 @@ a.click()
<li>
<p><code>useMultiPageExport</code>: v0.6.15+Boolean, false, 是否多页导出默认为单页</p>
</li>
<li>
<p><code>maxImageWidth</code>v0.8.1+null | Number默认为a4纸的宽度的2倍, 压缩图片的参数某些情况下图片的长宽可能非常大导致pdf体积也非常大所以如果希望减小体积那么可以通过该参数来控制图片的最大宽度</p>
</li>
</ul>
<p>导出为<code>pdf</code>和其他导出方法不一样这个方法不会返回数据会直接触发下载</p>
<blockquote>

View File

@@ -58,6 +58,8 @@
[dom-to-image库是如何将html转换成图片的](https://juejin.cn/post/7287913415803764747)
[两天实现思维导图的协同编辑用Yjs真的可以](https://juejin.cn/post/7295669711533998117)
## 特别说明
本项目可用于学习和参考,用于实际项目时请先深度体验一下是否能满足您的需求。
@@ -226,4 +228,26 @@
<img src="../../../../assets/avatar/天清如愿.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>天清如愿</p>
</div>
</div>
<div style="display: flex;">
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/敬明朗.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>敬明朗</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>飞箭</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/戚永峰.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>戚永峰</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/moom.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>moom</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/张扬.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>张扬</p>
</div>
</div>

View File

@@ -43,6 +43,7 @@
<p><a href="https://juejin.cn/post/7233012756314701884">我的第一个Electron应用</a></p>
<p><a href="https://juejin.cn/post/7276712861514170409">探索如何将html和svg导出为图片</a></p>
<p><a href="https://juejin.cn/post/7287913415803764747">dom-to-image库是如何将html转换成图片的</a></p>
<p><a href="https://juejin.cn/post/7295669711533998117">两天实现思维导图的协同编辑用Yjs真的可以</a></p>
<h2>特别说明</h2>
<p>本项目可用于学习和参考用于实际项目时请先深度体验一下是否能满足您的需求</p>
<p>本项目可能没有完整测试到每一个功能点所以可能存在bug另外当节点数量非常多的时候性能也存在一些问题因为每个人能接受的卡顿程度不一样所以你可以自行测试节点数量上限一般来说500个节点以内比较流畅1000个节点以上卡顿比较明显</p>
@@ -184,6 +185,28 @@
<img src="../../../../assets/avatar/天清如愿.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>天清如愿</p>
</div>
</div>
<div style="display: flex;">
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/敬明朗.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>敬明朗</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>飞箭</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/戚永峰.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>戚永峰</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/moom.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>moom</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/张扬.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>张扬</p>
</div>
</div>
</div>
</template>

View File

@@ -56,6 +56,50 @@
## 方法
### setGeneralizationOpacity(val)
> v0.9.0+
- `val`Number, 0-1透明度
设置概要节点及曲线的透明度。
### formatGetGeneralization()
> v0.9.0+
获取节点概要数据。
### getIndexInBrothers()
> v0.9.0+
获取该节点在兄弟节点列表中的索引。
### getRectInSvg()
> v0.9.0+
获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置信息相对于画布。
### getRect()
> v0.8.1+
获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置是相对于浏览器窗口左上角的位置。
### ancestorHasGeneralization()
> v0.8.1+
检查是否存在有概要的祖先节点。
### getNoteContentPosition()
> v0.8.1+
获取节点备注显示位置。当节点存在备注且正在显示状态时,如果拖动或缩放会导致备注浮层和节点脱离,那么可以通过该方法获取新位置更新备注浮层。
### updateNodeByActive(active)
> v0.8.0+
@@ -204,7 +248,13 @@
### isParent(node)
> v0.1.5+
> v0.1.5+:检测当前节点是否是某个节点的祖先节点
> v0.8.1+:检测当前节点是否是某个节点的父节点
### isAncestor(node)
> v0.8.1+
检测当前节点是否是某个节点的祖先节点
@@ -220,6 +270,12 @@
检查是否存在概要
### checkHasSelfGeneralization()
> v0.9.0+
检查是否存在自身的概要,非子节点区间概要
### hideGeneralization()
> v0.2.0+

View File

@@ -31,6 +31,44 @@
</blockquote>
<p>节点是否正在拖拽中</p>
<h2>方法</h2>
<h3>setGeneralizationOpacity(val)</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<ul>
<li><code>val</code>Number, 0-1透明度</li>
</ul>
<p>设置概要节点及曲线的透明度</p>
<h3>formatGetGeneralization()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>获取节点概要数据</p>
<h3>getIndexInBrothers()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>获取该节点在兄弟节点列表中的索引</p>
<h3>getRectInSvg()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>获取节点的尺寸和位置信息宽高是应用了缩放效果后的实际宽高位置信息相对于画布</p>
<h3>getRect()</h3>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>获取节点的尺寸和位置信息宽高是应用了缩放效果后的实际宽高位置是相对于浏览器窗口左上角的位置</p>
<h3>ancestorHasGeneralization()</h3>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>检查是否存在有概要的祖先节点</p>
<h3>getNoteContentPosition()</h3>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>获取节点备注显示位置当节点存在备注且正在显示状态时如果拖动或缩放会导致备注浮层和节点脱离那么可以通过该方法获取新位置更新备注浮层</p>
<h3>updateNodeByActive(active)</h3>
<blockquote>
<p>v0.8.0+</p>
@@ -136,7 +174,14 @@
<p>显示节点及其下级节点</p>
<h3>isParent(node)</h3>
<blockquote>
<p>v0.1.5+</p>
<p>v0.1.5+检测当前节点是否是某个节点的祖先节点</p>
</blockquote>
<blockquote>
<p>v0.8.1+检测当前节点是否是某个节点的父节点</p>
</blockquote>
<h3>isAncestor(node)</h3>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>检测当前节点是否是某个节点的祖先节点</p>
<h3>isBrother(node)</h3>
@@ -149,6 +194,11 @@
<p>v0.2.0+</p>
</blockquote>
<p>检查是否存在概要</p>
<h3>checkHasSelfGeneralization()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>检查是否存在自身的概要非子节点区间概要</p>
<h3>hideGeneralization()</h3>
<blockquote>
<p>v0.2.0+</p>

View File

@@ -14,6 +14,24 @@
## 方法
### highlightNode(node, range)
> v0.9.0+
- `node`:要高亮的目标节点实例
- `range`可选Array一个范围数组[0, 1]
高亮节点或子节点。如果`range`参数没有传递,那么直接高亮指定的`node`节点,如果`range`传递了一个要高亮的子节点的范围,那么会高亮该范围的子节点。
高亮效果为通过一个矩形框来包裹,矩形的描边和填充样式可以通过`highlightNodeBoxStyle`实例化选项进行配置。
### closeHighlightNode()
> v0.9.0+
隐藏节点高亮框。
### setRootNodeCenter()
> v0.8.0+

View File

@@ -8,6 +8,25 @@
<h3>root</h3>
<p>获取节点树的根节点</p>
<h2>方法</h2>
<h3>highlightNode(node, range)</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<ul>
<li>
<p><code>node</code>要高亮的目标节点实例</p>
</li>
<li>
<p><code>range</code>可选Array一个范围数组[0, 1]</p>
</li>
</ul>
<p>高亮节点或子节点如果<code>range</code>参数没有传递那么直接高亮指定的<code>node</code>节点如果<code>range</code>传递了一个要高亮的子节点的范围那么会高亮该范围的子节点</p>
<p>高亮效果为通过一个矩形框来包裹矩形的描边和填充样式可以通过<code>highlightNodeBoxStyle</code>实例化选项进行配置</p>
<h3>closeHighlightNode()</h3>
<blockquote>
<p>v0.9.0+</p>
</blockquote>
<p>隐藏节点高亮框</p>
<h3>setRootNodeCenter()</h3>
<blockquote>
<p>v0.8.0+</p>

View File

@@ -397,6 +397,28 @@ copyNodeTree({}, node)
从节点的父节点的`nodeData.children`列表中移除该节点的数据。
#### checkHasSupSubRelation()
> v0.8.1+
从给定的节点实例列表里判断是否存在上下级关系。
#### handleSelfCloseTags(str)
> v0.9.1+
- `str`html字符串
给html自闭合标签添加闭合状态`<div><img src="xxx"></div>` -> `<div><img src="xxx" /></div>`
#### checkNodeListIsEqual(list1, list2)
> v0.9.1+
- `list1/list2`:节点实例列表
检查两个节点实例列表包含的节点是否是一样的。
## 在canvas中模拟css的背景属性
引入:

View File

@@ -323,6 +323,27 @@
<p>v0.8.0+</p>
</blockquote>
<p>从节点的父节点的<code>nodeData.children</code>列表中移除该节点的数据</p>
<h4>checkHasSupSubRelation()</h4>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>从给定的节点实例列表里判断是否存在上下级关系</p>
<h4>handleSelfCloseTags(str)</h4>
<blockquote>
<p>v0.9.1+</p>
</blockquote>
<ul>
<li><code>str</code>html字符串</li>
</ul>
<p>给html自闭合标签添加闭合状态<code>&lt;div&gt;&lt;img src=&quot;xxx&quot;&gt;&lt;/div&gt;</code> -&gt; <code>&lt;div&gt;&lt;img src=&quot;xxx&quot; /&gt;&lt;/div&gt;</code></p>
<h4>checkNodeListIsEqual(list1, list2)</h4>
<blockquote>
<p>v0.9.1+</p>
</blockquote>
<ul>
<li><code>list1/list2</code>节点实例列表</li>
</ul>
<p>检查两个节点实例列表包含的节点是否是一样的</p>
<h2>在canvas中模拟css的背景属性</h2>
<p>引入</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

View File

@@ -753,6 +753,36 @@
</el-select>
</div>
</div>
<!-- 配置创建新节点时的行为 -->
<div class="row">
<div class="rowItem">
<span class="name">{{ $t('baseStyle.createNewNodeBehavior') }}</span>
<el-select
size="mini"
style="width: 120px"
v-model="config.createNewNodeBehavior"
placeholder=""
@change="
value => {
updateOtherConfig('createNewNodeBehavior', value)
}
"
>
<el-option
:label="$t('baseStyle.default')"
value="default"
></el-option>
<el-option
:label="$t('baseStyle.notActive')"
value="notActive"
></el-option>
<el-option
:label="$t('baseStyle.activeOnly')"
value="activeOnly"
></el-option>
</el-select>
</div>
</div>
<!-- 是否显示滚动条 -->
<div class="row">
<div class="rowItem">
@@ -843,7 +873,8 @@ export default {
config: {
enableFreeDrag: false,
mousewheelAction: 'zoom',
mousewheelZoomActionReverse: false
mousewheelZoomActionReverse: false,
createNewNodeBehavior: 'default'
},
watermarkConfig: {
show: false,
@@ -968,7 +999,8 @@ export default {
;[
'enableFreeDrag',
'mousewheelAction',
'mousewheelZoomActionReverse'
'mousewheelZoomActionReverse',
'createNewNodeBehavior'
].forEach(key => {
this.config[key] = this.mindMap.getConfig(key)
})
@@ -979,9 +1011,7 @@ export default {
this.enableNodeRichText = this.localConfig.openNodeRichText
this.mousewheelAction = this.localConfig.mousewheelAction
this.mousewheelZoomActionReverse = this.localConfig.mousewheelZoomActionReverse
;[
'isShowScrollbar'
].forEach(key => {
;['isShowScrollbar'].forEach(key => {
this.localConfigs[key] = this.localConfig[key]
})
},

View File

@@ -188,6 +188,7 @@ export default {
this.$bus.$off('node_tree_render_end', this.handleHideLoading)
this.$bus.$off('showLoading', this.handleShowLoading)
window.removeEventListener('resize', this.handleResize)
this.mindMap.destroy()
},
methods: {
handleStartTextEdit() {
@@ -294,8 +295,8 @@ export default {
nodeTextEditZIndex: 1000,
nodeNoteTooltipZIndex: 1000,
customNoteContentShow: {
show: (content, left, top) => {
this.$bus.$emit('showNoteContent', content, left, top)
show: (content, left, top, node) => {
this.$bus.$emit('showNoteContent', content, left, top, node)
},
hide: () => {
// this.$bus.$emit('hideNoteContent')
@@ -308,12 +309,26 @@ export default {
enableAutoEnterTextEditWhenKeydown: true,
customHandleClipboardText: handleClipboardText,
handleIsSplitByWrapOnPasteCreateNewNode: () => {
return this.$confirm(this.$t('edit.splitByWrap'), this.$t('edit.tip'), {
confirmButtonText: this.$t('edit.yes'),
cancelButtonText: this.$t('edit.no'),
type: 'warning'
})
}
return this.$confirm(
this.$t('edit.splitByWrap'),
this.$t('edit.tip'),
{
confirmButtonText: this.$t('edit.yes'),
cancelButtonText: this.$t('edit.no'),
type: 'warning'
}
)
},
errorHandler: (code, err) => {
console.error(err)
switch (code) {
case 'export_error':
this.$message.error('导出失败')
break
default:
break
}
},
// isUseCustomNodeContent: true,
// 示例1组件里用到了router、store、i18n等实例化vue组件时需要用到的东西
// customCreateNodeContent: (node) => {
@@ -340,7 +355,7 @@ export default {
// return comp.$el
// },
// 示例3普通元素
// customCreateNodeContent: (node) => {
// customCreateNodeContent: node => {
// let el = document.createElement('div')
// el.style.cssText = `
// width: 203px;
@@ -353,9 +368,12 @@ export default {
// justify-content: center;
// align-items: center;
// `
// el.innerHTML = node.nodeData.data.text
// el.innerHTML = `
// ${node.nodeData.data.text}
// <img crossOrigin="anonymous" src="/img/cactus.jpg" />
// `
// return el
// },
// }
})
if (this.openNodeRichText) this.addRichTextPlugin()
this.mindMap.keyCommand.addShortcut('Control+s', () => {
@@ -380,7 +398,8 @@ export default {
'generalization_node_contextmenu',
'painter_start',
'painter_end',
'scrollbar_change'
'scrollbar_change',
'scale'
].forEach(event => {
this.mindMap.on(event, (...args) => {
this.$bus.$emit(event, ...args)
@@ -398,6 +417,16 @@ export default {
}
// 协同测试
this.cooperateTest()
// 销毁
// setTimeout(() => {
// console.log('销毁')
// this.mindMap.destroy()
// }, 10000)
// 测试
// setTimeout(() => {
// console.log(this.mindMap.renderer.root.getRect())
// console.log(this.mindMap.renderer.root.getRectInSvg())
// }, 5000);
},
// url中是否存在要打开的文件
@@ -448,9 +477,12 @@ export default {
*/
async export(...args) {
try {
this.mindMap.export(...args)
showLoading()
await this.mindMap.export(...args)
hideLoading()
} catch (error) {
console.log(error)
hideLoading()
}
},

View File

@@ -119,21 +119,20 @@ export default {
version: pkg.version,
langList,
lang: '',
isReadonly: false,
openMiniMap: false
}
},
computed: {
...mapState(['isDark'])
...mapState(['isDark', 'isReadonly'])
},
created() {
this.lang = getLang()
},
methods: {
...mapMutations(['setIsDark']),
...mapMutations(['setIsDark', 'setIsReadonly']),
readonlyChange() {
this.isReadonly = !this.isReadonly
this.setIsReadonly(!this.isReadonly)
this.mindMap.setMode(this.isReadonly ? 'readonly' : 'edit')
},

View File

@@ -101,9 +101,14 @@ export default {
handleNodeActive(...args) {
this.activeNodes = [...args[1]]
if (this.activeNodes.length > 0) {
let firstNode = this.activeNodes[0]
this.nodeImage = firstNode.getData('image') || ''
this.iconList = firstNode.getData('icon') || [] // 回显图标
if (this.activeNodes.length === 1) {
let firstNode = this.activeNodes[0]
this.nodeImage = firstNode.getData('image') || ''
this.iconList = firstNode.getData('icon') || [] // 回显图标
} else {
this.nodeImage = []
this.iconList = []
}
} else {
this.iconList = []
this.nodeImage = ''
@@ -121,27 +126,31 @@ export default {
// 设置icon
setIcon(type, name) {
let key = type + '_' + name
let index = this.iconList.findIndex(item => {
return item === key
})
// 删除icon
if (index !== -1) {
this.iconList.splice(index, 1)
} else {
let typeIndex = this.iconList.findIndex(item => {
return item.split('_')[0] === type
})
// 替换icon
if (typeIndex !== -1) {
this.iconList.splice(typeIndex, 1, key)
} else {
// 增加icon
this.iconList.push(key)
}
}
this.activeNodes.forEach(node => {
node.setIcon([...this.iconList])
const iconList = [...(node.getData('icon') || [])]
let key = type + '_' + name
let index = iconList.findIndex(item => {
return item === key
})
// 删除icon
if (index !== -1) {
iconList.splice(index, 1)
} else {
let typeIndex = iconList.findIndex(item => {
return item.split('_')[0] === type
})
// 替换icon
if (typeIndex !== -1) {
iconList.splice(typeIndex, 1, key)
} else {
// 增加icon
iconList.push(key)
}
}
node.setIcon(iconList)
if (this.activeNodes.length === 1) {
this.iconList = iconList
}
})
},

View File

@@ -51,6 +51,7 @@ export default {
this.mindMap.on('svg_mousedown', this.close)
this.mindMap.on('node_dblclick', this.close)
this.mindMap.on('node_active', this.onNodeActive)
this.mindMap.on('scale', this.onScale)
this.$bus.$on('close_node_icon_toolbar', this.close)
},
mounted() {
@@ -62,6 +63,7 @@ export default {
this.mindMap.off('svg_mousedown', this.close)
this.mindMap.off('node_dblclick', this.close)
this.mindMap.off('node_active', this.onNodeActive)
this.mindMap.off('scale', this.onScale)
this.$bus.$off('close_node_icon_toolbar', this.close)
},
methods: {
@@ -75,9 +77,7 @@ export default {
this.iconList = [...allIconList.find((item) => {
return item.type === this.iconType
}).list]
let rect = node.group.rbox()
this.style.left = rect.x + 'px'
this.style.top = rect.y + rect.height + 'px'
this.updatePos()
this.showNodeIconToolbar = true
if (this.activeSidebar === 'nodeIconSidebar') {
this.setActiveSidebar('')
@@ -95,6 +95,17 @@ export default {
this.style.top = 0
},
updatePos() {
if (!this.node) return
const rect = this.node.getRect()
this.style.left = rect.x + 'px'
this.style.top = rect.y + rect.height + 'px'
},
onScale() {
this.updatePos()
},
onNodeActive(node) {
if (node === this.node) {
return

View File

@@ -27,7 +27,8 @@ export default {
editor: null,
show: false,
left: 0,
top: 0
top: 0,
node: null
}
},
created() {
@@ -35,6 +36,8 @@ export default {
this.$bus.$on('hideNoteContent', this.hideNoteContent)
document.body.addEventListener('click', this.hideNoteContent)
this.$bus.$on('node_active', this.hideNoteContent)
this.$bus.$on('scale', this.onScale)
this.$bus.$on('svg_mousedown', this.hideNoteContent)
},
mounted() {
this.initEditor()
@@ -44,34 +47,37 @@ export default {
this.$bus.$off('hideNoteContent', this.hideNoteContent)
document.body.removeEventListener('click', this.hideNoteContent)
this.$bus.$off('node_active', this.hideNoteContent)
this.$bus.$off('scale', this.onScale)
this.$bus.$off('svg_mousedown', this.hideNoteContent)
},
methods: {
/**
* @Author: 王林25
* @Date: 2022-11-14 19:56:08
* @Desc: 显示备注浮层
*/
onShowNoteContent(content, left, top) {
// 显示备注浮层
onShowNoteContent(content, left, top, node) {
this.node = node
this.editor.setMarkdown(content)
this.left = left
this.top = top
this.updateNoteContentPosition(left, top)
this.show = true
},
/**
* @Author: 王林25
* @Date: 2022-11-14 19:56:20
* @Desc: 隐藏备注浮层
*/
// 更新位置
updateNoteContentPosition(left, top) {
this.left = left
this.top = top
},
// 画布缩放事件
onScale() {
if (!this.node || !this.show) return
const { left, top } = this.node.getNoteContentPosition()
this.updateNoteContentPosition(left, top)
},
// 隐藏备注浮层
hideNoteContent() {
this.show = false
},
/**
* @Author: 王林25
* @Date: 2022-05-09 11:37:05
* @Desc: 初始化编辑器
*/
// 初始化编辑器
initEditor() {
if (!this.editor) {
this.editor = new Viewer({

View File

@@ -24,7 +24,7 @@
>
<span
class="nodeEdit"
contenteditable="true"
:contenteditable="!isReadonly"
:key="getKey()"
@keydown.stop="onNodeInputKeydown($event, node)"
@keyup.stop
@@ -42,7 +42,8 @@ import {
nodeRichTextToTextWithWrap,
textToNodeRichTextWithWrap,
getTextFromHtml,
createUid
createUid,
htmlEscape
} from 'simple-mind-map/src/utils'
// 大纲树
@@ -69,7 +70,7 @@ export default {
}
},
computed: {
...mapState(['isDark'])
...mapState(['isDark', 'isReadonly'])
},
created() {
window.addEventListener('keydown', this.onKeyDown)
@@ -129,10 +130,11 @@ export default {
let data = this.mindMap.getData()
data.root = true // 标记根节点
let walk = root => {
const text = (root.data.richText
let text = (root.data.richText
? nodeRichTextToTextWithWrap(root.data.text)
: root.data.text
).replaceAll(/\n/g, '<br>')
text = htmlEscape(text)
root.textCache = text // 保存一份修改前的数据,用于对比是否修改了
root.label = text
root.uid = root.data.uid

View File

@@ -32,7 +32,7 @@
>
<span
class="nodeEdit"
contenteditable="true"
:contenteditable="!isReadonly"
:key="getKey()"
@blur="onBlur($event, node)"
@keydown.stop="onNodeInputKeydown($event, node)"
@@ -54,7 +54,8 @@ import {
textToNodeRichTextWithWrap,
getTextFromHtml,
createUid,
simpleDeepClone
simpleDeepClone,
htmlEscape
} from 'simple-mind-map/src/utils'
import { storeData } from '@/api'
@@ -76,7 +77,7 @@ export default {
}
},
computed: {
...mapState(['isOutlineEdit', 'isDark'])
...mapState(['isOutlineEdit', 'isDark', 'isReadonly'])
},
watch: {
isOutlineEdit(val) {
@@ -102,10 +103,11 @@ export default {
let data = this.mindMap.getData()
data.root = true // 标记根节点
let walk = root => {
const text = (root.data.richText
let text = (root.data.richText
? nodeRichTextToTextWithWrap(root.data.text)
: root.data.text
).replaceAll(/\n/g, '<br>')
text = htmlEscape(text)
root.textCache = text // 保存一份修改前的数据,用于对比是否修改了
root.label = text
root.uid = root.data.uid

View File

@@ -44,10 +44,10 @@
}}</el-button>
</el-input>
<div class="btnList" v-if="showReplaceInput">
<el-button size="small" @click="replace">{{
<el-button size="small" :disabled="isReadonly" @click="replace">{{
$t('search.replace')
}}</el-button>
<el-button size="small" @click="replaceAll">{{
<el-button size="small" :disabled="isReadonly" @click="replaceAll">{{
$t('search.replaceAll')
}}</el-button>
</div>
@@ -78,7 +78,7 @@ export default {
}
},
computed: {
...mapState(['isDark'])
...mapState(['isDark', 'isReadonly'])
},
watch: {
searchText() {

View File

@@ -42,11 +42,11 @@ export default {
dataList: [
{
icon: 'iconstar',
value: 'Github star数量1000+'
value: 'Github star数量2000+'
},
{
icon: 'iconfork',
value: 'Github fork数量200+'
value: 'Github fork数量250+'
},
{
icon: 'iconxiazai',
@@ -54,7 +54,7 @@ export default {
},
{
icon: 'iconteamwork',
value: '代码贡献者12+'
value: '代码贡献者14+'
}
],
functionList: [

Some files were not shown because too many files have changed in this diff Show More