Compare commits

...

45 Commits
0.8.0 ... 0.9.0

Author SHA1 Message Date
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
76 changed files with 30038 additions and 663 deletions

View File

@@ -210,4 +210,20 @@ 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>
</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?6450bdeb4871438bb6e4" rel="stylesheet"><link href="dist/css/app.css?6450bdeb4871438bb6e4" 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?6450bdeb4871438bb6e4"></script><script src="dist/js/app.js?6450bdeb4871438bb6e4"></script></body></html>

View File

@@ -486,6 +486,7 @@ class MindMap {
// 销毁
destroy() {
this.emit('beforeDestroy')
// 移除插件
;[...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.0",
"description": "一个简单的web在线思维导图",
"authors": [
{

View File

@@ -160,6 +160,9 @@ export const defaultOpt = {
customHandleClipboardText: null,
// 禁止鼠标滚轮缩放你仍旧可以使用api进行缩放
disableMouseWheelZoom: false,
// 禁止双指缩放你仍旧可以使用api进行缩放
// 需要注册TouchEvent插件后生效
disableTouchZoom: false,
// 错误处理函数
errorHandler: (code, error) => {
console.error(code, error)
@@ -219,5 +222,12 @@ export const defaultOpt = {
// 可以传递一个函数返回promiseresolve代表根据换行分割reject代表忽略换行
handleIsSplitByWrapOnPasteCreateNewNode: null,
// 多少时间内只允许添加一次历史记录避免添加没有必要的中间状态单位ms
addHistoryTime: 100
addHistoryTime: 100,
// 是否禁止拖动画布
isDisableDrag: false,
// 鼠标移入概要高亮所属节点时的高亮框样式
highlightNodeBoxStyle: {
stroke: 'rgb(94, 200, 248)',
fill: 'transparent'
}
}

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

@@ -23,11 +23,14 @@ import {
getNodeDataIndex,
getNodeIndexInNodeList,
setDataToClipboard,
getDataFromClipboard
getDataFromClipboard,
htmlEscape,
parseAddGeneralizationNodeList
} 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 +85,8 @@ class Render {
this.beingPasteText = ''
this.beingPasteImgSize = 0
this.currentBeingPasteType = ''
// 节点高亮框
this.highlightBoxNode = null
// 布局
this.setLayout()
// 绑定事件
@@ -463,6 +468,7 @@ class Render {
// 全选
selectAll() {
if (this.mindMap.opt.readonly) return
walk(
this.root,
null,
@@ -885,9 +891,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 +1035,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 +1054,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 +1088,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 +1410,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 +1456,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 +1464,7 @@ class Render {
})
})
this.mindMap.render()
this.closeHighlightNode()
}
// 设置节点自定义位置
@@ -1569,6 +1606,60 @@ class Render {
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()
}
}
export default Render

View File

@@ -28,6 +28,7 @@ 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)
// 点击事件
@@ -63,15 +64,26 @@ export default class TextEdit {
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, false, true)
}
}

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
// 尺寸信息
@@ -457,12 +458,18 @@ 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)
})
// 双击事件
@@ -749,10 +756,7 @@ class Node {
item.setOpacity(val)
})
// 概要节点
if (this._generalizationNode) {
this._generalizationLine.opacity(val)
this._generalizationNode.group.opacity(val)
}
this.setGeneralizationOpacity(val)
}
// 隐藏子节点
@@ -847,7 +851,7 @@ class Node {
return this.customLeft !== undefined && this.customTop !== undefined
}
// 检查节点是否存在自定义位置的祖先节点
// 检查节点是否存在自定义位置的祖先节点,包含自身
ancestorHasCustomPosition() {
let node = this
while (node) {
@@ -859,6 +863,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 +905,7 @@ class Node {
}
// 检测当前节点是否是某个节点的祖先节点
isParent(node) {
isAncestor(node) {
if (this.uid === node.uid) {
return false
}
@@ -903,6 +919,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 +941,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 +998,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) {

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

@@ -677,7 +677,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,8 @@ import {
imgToDataUrl,
downloadFile,
readBlob,
removeHTMLEntities
removeHTMLEntities,
resizeImgSize
} from '../utils'
import { SVG } from '@svgdotjs/svg.js'
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
@@ -63,7 +64,8 @@ class Export {
transparent,
checkRotate = () => {
return false
}
},
compress
) {
return new Promise((resolve, reject) => {
const img = new Image()
@@ -76,8 +78,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) {
@@ -186,9 +199,8 @@ class Export {
* 方法1.把svg的图片都转化成data:url格式再转换
* 方法2.把svg的图片提取出来再挨个绘制到canvas里最后一起转换
*/
async png(name, transparent = false, checkRotate) {
async png(name, transparent = false, checkRotate, compress) {
let { node, str } = await this.getSvgData()
str = removeHTMLEntities(str)
// 如果开启了富文本则使用htmltocanvas转换为图片
if (this.mindMap.richText) {
// 覆盖html默认的样式
@@ -208,6 +220,7 @@ class Export {
// )
// return imgDataUrl
}
str = removeHTMLEntities(str)
// 转换成blob数据
let blob = new Blob([str], {
type: 'image/svg+xml'
@@ -215,20 +228,27 @@ class Export {
// 转换成data:url数据
let svgUrl = await readBlob(blob)
// 绘制到canvas上
let res = await this.svgToPng(svgUrl, transparent, checkRotate)
let 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)
let 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

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

@@ -422,10 +422,13 @@ class RichText {
// 中文输入结束
onCompositionEnd() {
if (!this.showTextEdit || !this.lostStyle) {
if (!this.showTextEdit) {
return
}
this.isCompositing = false
if (!this.lostStyle) {
return
}
this.setTextStyleIfNotRichText(this.node)
}
@@ -648,11 +651,13 @@ class RichText {
beforePluginRemove() {
this.transformAllNodesToNormalNode()
document.head.removeChild(this.styleEl)
this.unbindEvent()
}
// 插件被卸载前做的事情
beforePluginDestroy() {
document.head.removeChild(this.styleEl)
this.unbindEvent()
}
}

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

@@ -152,6 +152,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 +629,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 +713,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 +722,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,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -237,7 +237,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 +276,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

@@ -233,7 +233,7 @@ export default {
formula: '公式',
more: '更多',
selectFileTip: '请选择文件',
notSupportTip: '你的浏览器或网络协议不支持该功能',
notSupportTip: '你的浏览器不支持该功能或者当前页面非https协议',
tip: '提示',
editingLocalFileTipFront: '当前正在编辑你本机的【',
editingLocalFileTipEnd: '】文件',
@@ -270,7 +270,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,58 @@
# Changelog
## 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,38 @@
<template>
<div>
<h1>Changelog</h1>
<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,7 +37,7 @@ 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 | |
@@ -100,6 +100,9 @@ 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 | |
### Data structure
@@ -408,6 +411,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 +523,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 +596,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>
@@ -560,6 +560,27 @@
<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>
</tbody>
</table>
<h3>Data structure</h3>
@@ -1032,6 +1053,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 +1296,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,22 @@ 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>

View File

@@ -8,17 +8,17 @@
</blockquote>
<h2>Features</h2>
<ul>
<li><input type="checkbox" id="checkbox16" checked="true" /><label for="checkbox16">Pluggable architecture, in addition to core functions, other functions are provided as plugins, which can be used as needed to reduce packaging volume</label></li>
<li><input type="checkbox" id="checkbox17" checked="true" /><label for="checkbox17">Support logical structure chart, mind map, Organizational chart, directory organization chart, timeline (horizontal and vertical), fishbone chart and other structures</label></li>
<li><input type="checkbox" id="checkbox18" checked="true" /><label for="checkbox18">Built-in multiple themes, allowing for highly customizable styles, and supporting registration of new themes</label></li>
<li><input type="checkbox" id="checkbox19" checked="true" /><label for="checkbox19">Node content supports text (regular text, rich text), images, icons, hyperlinks, notes, labels, summaries, and math formulas</label></li>
<li><input type="checkbox" id="checkbox20" checked="true" /><label for="checkbox20">Nodes support drag and drop (drag and move, freely adjust), multiple node shapes, and fully customize node content using DDM</label></li>
<li><input type="checkbox" id="checkbox21" checked="true" /><label for="checkbox21">Support canvas dragging and scaling</label></li>
<li><input type="checkbox" id="checkbox22" checked="true" /><label for="checkbox22">Supports two multi node selection methods: mouse button drag selection and Ctrl+left button selection</label></li>
<li><input type="checkbox" id="checkbox23" checked="true" /><label for="checkbox23">Supoorts to export as </label><code>json</code><code>png</code><code>svg</code><code>pdf</code><code>markdown</code><code>xmind</code>, support import from <code>json</code><code>xmind</code><code>markdown</code></li>
<li><input type="checkbox" id="checkbox24" checked="true" /><label for="checkbox24">Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, watermarks, and scrollbar</label></li>
<li><input type="checkbox" id="checkbox25" checked="true" /><label for="checkbox25">Provide rich configurations to meet various scenarios and usage habits</label></li>
<li><input type="checkbox" id="checkbox26" checked="true" /><label for="checkbox26">Support collaborative editing</label></li>
<li><input type="checkbox" id="checkbox32" checked="true" /><label for="checkbox32">Pluggable architecture, in addition to core functions, other functions are provided as plugins, which can be used as needed to reduce packaging volume</label></li>
<li><input type="checkbox" id="checkbox33" checked="true" /><label for="checkbox33">Support logical structure chart, mind map, Organizational chart, directory organization chart, timeline (horizontal and vertical), fishbone chart and other structures</label></li>
<li><input type="checkbox" id="checkbox34" checked="true" /><label for="checkbox34">Built-in multiple themes, allowing for highly customizable styles, and supporting registration of new themes</label></li>
<li><input type="checkbox" id="checkbox35" checked="true" /><label for="checkbox35">Node content supports text (regular text, rich text), images, icons, hyperlinks, notes, labels, summaries, and math formulas</label></li>
<li><input type="checkbox" id="checkbox36" checked="true" /><label for="checkbox36">Nodes support drag and drop (drag and move, freely adjust), multiple node shapes, and fully customize node content using DDM</label></li>
<li><input type="checkbox" id="checkbox37" checked="true" /><label for="checkbox37">Support canvas dragging and scaling</label></li>
<li><input type="checkbox" id="checkbox38" checked="true" /><label for="checkbox38">Supports two multi node selection methods: mouse button drag selection and Ctrl+left button selection</label></li>
<li><input type="checkbox" id="checkbox39" checked="true" /><label for="checkbox39">Supoorts to export as </label><code>json</code><code>png</code><code>svg</code><code>pdf</code><code>markdown</code><code>xmind</code>, support import from <code>json</code><code>xmind</code><code>markdown</code></li>
<li><input type="checkbox" id="checkbox40" checked="true" /><label for="checkbox40">Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, watermarks, and scrollbar</label></li>
<li><input type="checkbox" id="checkbox41" checked="true" /><label for="checkbox41">Provide rich configurations to meet various scenarios and usage habits</label></li>
<li><input type="checkbox" id="checkbox42" checked="true" /><label for="checkbox42">Support collaborative editing</label></li>
</ul>
<h2>Repository Catalog Introduction</h2>
<p>1.<code>simple-mind-map</code></p>
@@ -28,16 +28,16 @@ frameworks such as Vue and React, or without a framework.</p>
<p>This is an online mind map built using the <code>simple-mind-map</code> library and based
on <code>Vue2.x</code> and <code>ElementUI</code>. Features include:</p>
<ul>
<li><input type="checkbox" id="checkbox27" checked="true" /><label for="checkbox27">Toolbar, which supports inserting and deleting nodes, and editing node</label>
<li><input type="checkbox" id="checkbox43" checked="true" /><label for="checkbox43">Toolbar, which supports inserting and deleting nodes, and editing node</label>
images, icons, hyperlinks, notes, tags, and summaries</li>
<li><input type="checkbox" id="checkbox28" checked="true" /><label for="checkbox28">Sidebar, with panels for basic style settings, node style settings,</label>
<li><input type="checkbox" id="checkbox44" checked="true" /><label for="checkbox44">Sidebar, with panels for basic style settings, node style settings,</label>
outline, theme selection, and structure selection</li>
<li><input type="checkbox" id="checkbox29" checked="true" /><label for="checkbox29">Import and export functionality; data is saved in the browser's local</label>
<li><input type="checkbox" id="checkbox45" checked="true" /><label for="checkbox45">Import and export functionality; data is saved in the browser's local</label>
storage by default, but it also supports creating, opening, and editing
local files on the computer directly</li>
<li><input type="checkbox" id="checkbox30" checked="true" /><label for="checkbox30">Right-click menu, which supports operations such as expanding, collapsing,</label>
<li><input type="checkbox" id="checkbox46" checked="true" /><label for="checkbox46">Right-click menu, which supports operations such as expanding, collapsing,</label>
and organizing layout</li>
<li><input type="checkbox" id="checkbox31" checked="true" /><label for="checkbox31">Bottom bar, which supports node and word count statistics, switching</label>
<li><input type="checkbox" id="checkbox47" checked="true" /><label for="checkbox47">Bottom bar, which supports node and word count statistics, switching</label>
between edit and read-only modes, zooming in and out, and switching to
full screen, support mini map</li>
</ul>
@@ -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,24 @@ 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>
</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,12 @@ 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.
## Simulate CSS background in Canvas
Import:

View File

@@ -328,6 +328,11 @@ 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>
<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,58 @@
# Changelog
## 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,38 @@
<template>
<div>
<h1>Changelog</h1>
<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,7 +37,7 @@ 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 | | 水印配置,详细配置请参考下方表格【水印配置】 |
@@ -100,6 +100,9 @@ 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' } | 鼠标移入概要高亮所属节点时的高亮框样式 |
### 数据结构
@@ -405,6 +408,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 +518,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 +584,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>
@@ -483,6 +483,24 @@
<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>
</tbody>
</table>
<h3>数据结构</h3>
@@ -948,6 +966,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 +1211,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

@@ -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,22 @@
<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>

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,24 @@
<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>
</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,12 @@ copyNodeTree({}, node)
从节点的父节点的`nodeData.children`列表中移除该节点的数据。
#### checkHasSupSubRelation()
> v0.8.1+
从给定的节点实例列表里判断是否存在上下级关系。
## 在canvas中模拟css的背景属性
引入:

View File

@@ -323,6 +323,11 @@
<p>v0.8.0+</p>
</blockquote>
<p>从节点的父节点的<code>nodeData.children</code>列表中移除该节点的数据</p>
<h4>checkHasSupSubRelation()</h4>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<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

@@ -294,8 +294,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,11 +308,15 @@ 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'
}
)
}
// isUseCustomNodeContent: true,
// 示例1组件里用到了router、store、i18n等实例化vue组件时需要用到的东西
@@ -380,7 +384,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 +403,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 +463,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

@@ -21,7 +21,8 @@ const store = new Vuex.Store({
},
activeSidebar: '', // 当前显示的侧边栏
isDark: false,// 是否是暗黑模式
isOutlineEdit: false// 是否是大纲编辑模式
isOutlineEdit: false,// 是否是大纲编辑模式
isReadonly: false// 是否只读
},
mutations: {
/**
@@ -75,7 +76,12 @@ const store = new Vuex.Store({
// 设置大纲编辑模式
setIsOutlineEdit(state, data) {
state.isOutlineEdit = data
}
},
// 设置是否只读
setIsReadonly(state, data) {
state.isReadonly = data
},
},
actions: {
/**