Compare commits

...

128 Commits

Author SHA1 Message Date
wanglin2
9fe94bfc21 打包0.9.1 2023-12-05 09:34:51 +08:00
wanglin2
6be6f6ee8a Doc 2023-12-05 09:31:13 +08:00
wanglin2
83ce090402 Fix:修复在节点文本编辑中和关联线文本编辑中时销毁思维导图文本编辑框未被销毁的问题 2023-12-05 09:02:41 +08:00
wanglin2
250ff30704 Demo:支持配置创建新节点时的行为 2023-12-04 11:25:04 +08:00
wanglin2
55d7d0a846 Feat:新增创建新节点的行为配置选项 2023-12-04 11:07:26 +08:00
wanglin2
0b3d2cedbd Fix:重新加上因新功能而丢失的旧功能:新创建的节点默认全选 2023-12-04 10:18:17 +08:00
wanglin2
de025d9bc7 Fix:修复不在格式刷时点击画布和节点也会触发painter_end事件的问题 2023-12-04 09:57:29 +08:00
wanglin2
79a39f993d Fix:修复点击节点也会触发node_dragend事件的问题 2023-12-04 09:51:56 +08:00
wanglin2
0fa731c3a2 Fix:修复在节点编辑状态中通过鼠标滚轮缩放画布再推出节点编辑后快捷键生效的问题 2023-12-01 10:23:32 +08:00
wanglin2
34eece8fcf Fix:修复节点处于编辑状态时,通过鼠标滚动移动画布后编辑框和节点脱离的问题 2023-12-01 09:59:33 +08:00
wanglin2
790662ad0c Demo: 增加快捷键提示 2023-12-01 09:52:17 +08:00
wanglin2
7d83a3635f Feat:在鼠标滚轮行为为上下移动画布时,支持按住Ctrl键改为放大缩小画布 2023-12-01 09:49:02 +08:00
wanglin2
546cf27b33 Feat:鼠标滚轮行为默认改为上下移动画布;改为默认向前滚动放大画布,向后缩小 2023-12-01 09:48:14 +08:00
wanglin2
e59d419708 Feat:只读模式下搜索时给当前匹配到的节点增加高亮效果 2023-11-30 17:40:12 +08:00
wanglin2
2d50106ce7 Feat:新增创建新节点时默认不聚焦新节点的配置选项 2023-11-29 08:50:13 +08:00
wanglin2
70c32e3c74 Fix:1.按住Ctrl键时禁用节点双击事件;2.优化节点激活事件的派发,激活节点未改变时不派发事件,短时间派发多次事件时跳过中间事件 2023-11-28 17:37:44 +08:00
wanglin2
a0f56473ee Fix:修复批量执行类添加同名任务时任务未更新为最小任务的问题 2023-11-28 17:36:13 +08:00
wanglin2
51dcb1f54f Fix:修复自定义节点内容时导出图片、svg、pdf报错的问题 2023-11-27 17:15:35 +08:00
wanglin2
9e34fd6174 Doc update 2023-11-24 13:45:03 +08:00
wanglin2
3b47d11b21 打包0.9.0 2023-11-23 14:22:43 +08:00
wanglin2
6e91df9d03 Doc: update 2023-11-23 14:16:06 +08:00
wanglin2
4c8f1bd69c Feat:新增鼠标移入概要时高亮其所属的节点 2023-11-23 11:25:37 +08:00
wanglin2
1f993518fd Merge branch 'feature3' into feature 2023-11-22 16:58:57 +08:00
wanglin2
7f9a1e9309 Fix:修复部分事件在思维导图卸载后未取消监听的问题 2023-11-22 16:23:04 +08:00
wanglin2
5df4a7edb8 Feat:导入和导出xmind文件时,支持处理区间概要 2023-11-22 14:23:21 +08:00
wanglin2
ddd578d773 Doc: update 2023-11-21 17:54:12 +08:00
wanglin2
c8e63e433c Feat:支持对同一个节点的多个子节点添加概要 2023-11-21 17:33:43 +08:00
wanglin2
cadd159a46 Feat:给节点实例增加isParent方法 2023-11-21 10:41:31 +08:00
wanglin2
d32588763c Feat:将节点实例的isParent方法改名为isAncestor 2023-11-21 10:40:00 +08:00
wanglin2
6723250266 Fix:修复同时给存在上下级关系的节点添加概要时概要重叠的问题 2023-11-21 10:32:19 +08:00
街角小林
41ca8f8e46 Merge pull request #407 from doyouhaobaby/doyouhaobaby-patch-1
Xmind 导入支持概要,其中xmind8修复将概要弄成了主题,导出改为最新版本xmind结构
2023-11-21 09:40:18 +08:00
wanglin2
a6d04ffa91 Feat:导出png的方法新增压缩参数;优化大数据量节点导出pdf时体积过大的问题 2023-11-20 18:06:14 +08:00
wanglin2
631892d785 Demo:导出操作增加loading 2023-11-20 17:49:30 +08:00
wanglin2
3d7f0fcbe7 Fix:修复导出pdf时异步控制丢失的问题 2023-11-20 17:49:05 +08:00
wanglin2
c716ec7294 Fix:修复节点数量很多的情况下导出pdf报错的问题 2023-11-20 17:48:19 +08:00
wanglin2
4db6bda193 修复先给自身添加概要,再给下级添加概要会出现概要重叠的问题 2023-11-20 17:05:00 +08:00
wanglin2
bc2a5b214f Demo:修复同时选中多个节点添加图标时,所有节点图标都会统一为第一个节点的图标的问题 2023-11-20 16:46:00 +08:00
wanglin2
879de57b49 Fix:修复富文本模式下节点内容存在 时导出为图片出错的问题 2023-11-20 16:26:35 +08:00
wanglin2
0d4cbc7344 Demo:修复节点内容为html标签时大纲无法显示和编辑的问题 2023-11-20 15:46:33 +08:00
wanglin2
217f5ee95d Fix:修复只读模式下可以全选节点的问题 2023-11-20 15:15:57 +08:00
wanglin2
ad98c0f229 Demo:修复只读模式下仍旧可以替换和编辑大纲的问题 2023-11-20 15:15:38 +08:00
wanglin2
9a7f827301 Fix:修复缩放画布时图标浮层和备注浮层和节点脱离的问题 2023-11-20 14:58:42 +08:00
wanglin2
211fa183d3 Fix:修复在safari浏览器中运行时,页面空白且控制台抛出异常的问题 2023-11-20 14:07:09 +08:00
wanglin2
c0e0fb23e4 Demo:修改本地文件操作功能不可用的提示 2023-11-20 14:00:32 +08:00
wanglin2
e0d46055a7 Feat:新增禁止双指缩放画布的配置信息 2023-11-20 11:46:46 +08:00
wanglin2
a1ec72b401 Feat:新增禁止拖动画布的配置选项 2023-11-20 11:39:08 +08:00
wanglin2
ffc7bf5b7e Merge branch 'feature' of https://github.com/wanglin2/mind-map into feature 2023-11-20 11:04:52 +08:00
wanglin2
4e6e0e2221 Fix:修复历史记录数据概要节点的激活状态未被删除的问题 2023-11-20 11:04:23 +08:00
小牛仔
a1bbe543ba 修复判断 2023-11-17 17:03:35 +08:00
小牛仔
48b5ff5f1c 修复判断 2023-11-17 16:56:15 +08:00
小牛仔
13dc61a585 旧版本xmind8支持导入概要,老的注解被搞成了一个主题了 2023-11-17 16:20:02 +08:00
小牛仔
6e476489f5 feat:Xmind导入支持概要,导入支持概要并且导出为最新版本的xmind格式 2023-11-17 13:47:04 +08:00
wanglin2
e6b82db674 Doc: update 2023-11-04 15:10:47 +08:00
wanglin2
4c60c52a8f 打包demo 2023-11-03 10:33:01 +08:00
wanglin2
29ed8f7a8d Merge branch 'main' into feature 2023-11-03 10:32:07 +08:00
街角小林
4b880e13a5 Merge pull request #373 from maxchang3/docs-typo
chore: typo
2023-11-03 10:30:54 +08:00
wanglin2
b5a08a2414 Doc: update 2023-11-03 10:27:04 +08:00
wanglin2
57e9379f49 Doc: update 2023-11-03 10:01:01 +08:00
MaxChang3
134fd69ffa chore: typo 2023-10-24 12:31:59 +08:00
wanglin2
e2830ccfb1 Doc: update 2023-10-20 11:45:00 +08:00
wanglin2
b0dd90f5b3 Feat:节点增加getPureData方法获取该节点的纯数据 2023-10-19 10:47:53 +08:00
wanglin2
32fc6937d2 Fix:修复极少数情况下输入中文时文本样式丢失的问题 2023-10-18 15:34:14 +08:00
wanglin2
23a3e26800 打包0.8.0-fix.1 2023-10-18 14:34:35 +08:00
wanglin2
c7b0cbc128 Fix:修复粘贴方式创建新节点时如果粘贴的内容带有<>等html标签符号时新创建的节点内容为空的问题 2023-10-18 14:30:00 +08:00
wanglin2
9915479354 打包0.8.0 2023-10-18 14:02:16 +08:00
wanglin2
abddafa3cf Demo:调整界面样式 2023-10-18 13:45:42 +08:00
wanglin2
5b057ff9de Doc: update 2023-10-18 11:21:43 +08:00
wanglin2
6dcbc0604d Fix:修复协同插件当创建新节点时新节点未显示创建人头像的问题 2023-10-18 11:19:55 +08:00
wanglin2
0e5fed6645 Doc: update 2023-10-18 10:49:27 +08:00
wanglin2
b6d91ec07a Feat:指定时间内只允许添加一次历史记录,避免添加没有必要的中间状态 2023-10-18 10:45:25 +08:00
wanglin2
baf36acc97 Doc: update 2023-10-17 16:56:07 +08:00
wanglin2
e0db4ad4f6 Doc: update 2023-10-17 16:14:56 +08:00
wanglin2
4fc2d79616 Fix:修复存在水印时小地图渲染非常慢的问题 2023-10-17 16:09:31 +08:00
wanglin2
9e4652b7b5 Fix:修复容器尺寸改变后没有水印没有重新绘制的问题 2023-10-17 16:01:03 +08:00
wanglin2
8466a3e99f Demo:小地图改为通过图片渲染 2023-10-17 15:35:32 +08:00
wanglin2
e85f187199 Feat:小地图插件支持返回图片类型的小地图 2023-10-17 15:35:21 +08:00
wanglin2
77167c572d Doc: update 2023-10-17 14:05:21 +08:00
wanglin2
9aae8bf55c Fix:修复存在水印时导出图片、svg、pdf时每个节点都会显示边框的问题 2023-10-17 14:02:08 +08:00
wanglin2
250fb2eb50 Fix:修复关联线插件computeNodePoints方法返回undefined时报错的问题 2023-10-17 13:45:39 +08:00
wanglin2
2a49dd9140 Doc: update 2023-10-17 11:13:06 +08:00
wanglin2
4dedaaea3b Demo:完善多语言 2023-10-16 18:43:07 +08:00
wanglin2
7bc666be36 Feat:粘贴带换行的文本支持控制是否按换行分割节点 2023-10-16 15:52:52 +08:00
wanglin2
93a56ef4ee Demo:支持手动输入缩放倍数 2023-10-16 15:08:31 +08:00
wanglin2
83b916d3c9 Demo:顶部工具栏支持根据窗口宽度自动收起到更多中 2023-10-16 14:26:10 +08:00
wanglin2
20157fcc8d Feat:被删除的节点同步从激活节点列表里删除;优化代码:1.移除父节点的连线逻辑合并到node.destroy方法内;2.提取render类中派发节点激活事件的重复代码 2023-10-16 10:29:45 +08:00
wanglin2
2c3fb4d7ea Feat:画布右键菜单事件清除当前激活的节点列表 2023-10-16 10:08:24 +08:00
wanglin2
75ad40ffbc Feat:鼠标右键单击画布时清除当前激活节点 2023-10-16 09:52:45 +08:00
wanglin2
e263eb8252 优化代码:render类中删除和更新概要时无需手动调用更新方法 2023-10-16 09:19:43 +08:00
wanglin2
8e43cd609f Feat:1.插入概要时自动展开子节点;2.删除插入和删除概要的方法中手动调用node.update方法的逻辑 2023-10-15 17:00:39 +08:00
wanglin2
1a3401fd1a 优化代码:修改unexpandAllNode,expandToLevel方法,没有子节点的节点无法收起 2023-10-15 16:46:08 +08:00
wanglin2
d1dcef2537 Feat:增加插入概要的默认文本配置选项 2023-10-15 15:15:27 +08:00
wanglin2
e732415aa3 优化代码:删除render类的unexpandAllNode,expandToLevel方法中重置_node属性的逻辑 2023-10-15 14:50:12 +08:00
wanglin2
ddbde0141a 优化代码:去除render类的setNodeExpand方法中的调用节点移除方法的逻辑 2023-10-15 11:18:44 +08:00
wanglin2
bc907f4b37 优化代码:1.将render类的setNodeActive方法的部分逻辑移到node类;2.将node类的updateNodeActive方法名称改为updateNodeActiveClass 2023-10-15 09:51:02 +08:00
wanglin2
1caf2c7f15 优化代码:将render类的setNodeStyle和setNodeStyles方法的公共逻辑提取到richText插件 2023-10-15 09:13:44 +08:00
wanglin2
22b56fb8dc 代码优化:通过addNodeToActiveList方法优化render类中的重复逻辑 2023-10-13 17:46:09 +08:00
wanglin2
87eccc298c 代码优化:删除节点时无需调用节点的删除方法,只需修改节点的nodeData.data数据即可 2023-10-13 17:30:21 +08:00
wanglin2
ca9e47183d 代码优化:将render类的onPaste方法中的读取剪贴板数据的逻辑提取为工具函数 2023-10-13 16:25:05 +08:00
wanglin2
1fbfe6f5ac 代码优化:将render类的setCopyDataToClipboard方法提取为工具方法 2023-10-13 16:18:28 +08:00
wanglin2
84d2a374d1 Demo:给节点的getData方法的返回值增加默认值 2023-10-13 16:09:35 +08:00
wanglin2
74a000723b 代码优化:读取和设置节点的nodeData.data改为通过setData和getData方法 2023-10-13 16:09:10 +08:00
wanglin2
5079ad2190 代码优化:将调用render类的setNodeData方法的地方改为调用SET_NODE_DATA命令 2023-10-13 15:26:00 +08:00
wanglin2
21053c43c9 Fix:修复同时给多个节点插入父节点时报错的问题 2023-10-13 15:17:19 +08:00
wanglin2
4c6270881a Demo:修改右键菜单的宽度 2023-10-13 14:29:51 +08:00
wanglin2
c17e5430ed 优化代码:使用getNodeDataIndex工具函数去除render类重复逻辑 2023-10-13 13:57:07 +08:00
wanglin2
0a36555343 代码优化:提取render类前进回退方法公共逻辑 2023-10-13 13:51:33 +08:00
wanglin2
bce2bb8fc4 代码优化:提取getNodeIndexInNodeList工具函数 2023-10-13 12:07:26 +08:00
wanglin2
d6ae06dbd6 代码优化:1.将render类的removeActiveNode函数名称改为removeNodeFromActiveList;2.addNodeToActiveList和removeNodeFromActiveList方法增加修改节点的激活状态数据 2023-10-13 11:46:29 +08:00
wanglin2
9221c404ee 代码优化:1.将render类的addActiveNode函数名称改为addNodeToActiveList;2.将调用render类的setNodeActive方法的地方改为调用SET_NODE_ACTIVE命令 2023-10-13 11:28:34 +08:00
wanglin2
697e53ff7d 代码优化:节点右键事件,如果有且只有当前节点被激活了,不再重复激活 2023-10-13 11:16:08 +08:00
wanglin2
9360aff6c9 代码优化:将render类的clearActive函数名称改为clearActiveNodeList 2023-10-13 11:05:34 +08:00
wanglin2
c68d629b7a 代码优化:将调用clearAllActive方法的地方改为调用CLEAR_ACTIVE_NODE命令 2023-10-13 10:55:02 +08:00
wanglin2
caedfb46a9 Demo:支持传入父节点和仅删除当前节点 2023-10-13 09:26:36 +08:00
wanglin2
2e4c6bc08e Feat:新增仅删除当前节点的命令 2023-10-13 09:26:12 +08:00
wanglin2
777eafcd2f Feat:新增插入父节点的命令;Fix:修复插入概要、上移、下移、一键整理布局的快捷键操作没有触发data_change事件的问题 2023-10-12 09:36:50 +08:00
wanglin2
20780a0c59 Doc: update 2023-10-12 09:11:41 +08:00
wanglin2
5d433cce16 Demo:修复覆盖方式切换主题时第一次切换不生效的问题 2023-10-11 17:13:35 +08:00
wanglin2
ba77fde93b Feat:setTheme、setThemeConfig、setLayout函数增加不触发重新渲染的参数 2023-10-11 17:12:37 +08:00
wanglin2
45b8850493 Fix:修复存在排队渲染时,最后一次渲染参数丢失的问题 2023-10-11 17:11:12 +08:00
wanglin2
39c2c15259 Demo:修改回到根节点的方法及文案 2023-10-11 15:49:22 +08:00
wanglin2
6d780c6c26 Feat:修复调整容器大小后回到根节点的操作异常的问题 2023-10-11 15:48:22 +08:00
wanglin2
4cf66adc18 Feat:优化代码,导出和适应画布操作时不再重新获取容器元素尺寸位置信息 2023-10-11 15:09:47 +08:00
wanglin2
7bff14e1bb Doc: update 2023-10-11 13:57:29 +08:00
wanglin2
7986e0d0cc Fix:修复导出图片和svg时关联线的箭头消失的问题 2023-10-11 13:49:28 +08:00
wanglin2
a316d0f0fe Feat:优化水印插件 2023-10-11 11:36:54 +08:00
wanglin2
88fa6225eb Feat:优化画布DOM结构,将节点、连线、关联线分层渲染 2023-10-11 11:36:34 +08:00
wanglin2
7ec720823f Demo build 2023-10-10 09:49:52 +08:00
wanglin2
c39daf72b4 Doc: update 2023-10-10 09:47:40 +08:00
158 changed files with 33669 additions and 1923 deletions

View File

@@ -202,4 +202,32 @@ 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/小逗比.png" 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/敬明朗.jpg" style="width: 50px;height: 50px;" />
<span>敬明朗</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>飞箭</span>
</span>
<span>
<img src="./web/src/assets/avatar/戚永峰.png" style="width: 50px;height: 50px;" />
<span>戚永峰</span>
</span>
<span>
<img src="./web/src/assets/avatar/moom.jpg" style="width: 50px;height: 50px;" />
<span>moom</span>
</span>
<span>
<img src="./web/src/assets/avatar/张扬.png" style="width: 50px;height: 50px;" />
<span>张扬</span>
</span>
</p>

View File

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

View File

@@ -35,21 +35,16 @@ class MindMap {
// 容器元素
this.el = this.opt.el
if (!this.el) throw new Error('缺少容器元素el')
this.elRect = this.el.getBoundingClientRect()
// 画布宽高
this.width = this.elRect.width
this.height = this.elRect.height
if (this.width <= 0 || this.height <= 0)
throw new Error('容器元素el的宽高不能为0')
// 获取容器尺寸位置信息
this.getElRectInfo()
// 添加css
this.cssEl = null
this.addCss()
// 画布
this.svg = SVG().addTo(this.el).size(this.width, this.height)
this.draw = this.svg.group()
this.initContainer()
// 初始化主题
this.initTheme()
@@ -79,8 +74,7 @@ class MindMap {
// 视图操作类
this.view = new View({
mindMap: this,
draw: this.draw
mindMap: this
})
// 批量执行类
@@ -111,6 +105,46 @@ class MindMap {
return opt
}
// 创建容器元素
initContainer() {
const { associativeLineIsAlwaysAboveNode } = this.opt
// 节点关联线容器
const createAssociativeLineDraw = () => {
this.associativeLineDraw = this.draw.group()
this.associativeLineDraw.addClass('smm-associative-line-container')
}
// 画布
this.svg = SVG().addTo(this.el).size(this.width, this.height)
// 容器
this.draw = this.svg.group()
this.draw.addClass('smm-container')
// 节点连线容器
this.lineDraw = this.draw.group()
this.lineDraw.addClass('smm-line-container')
// 默认处于节点下方
if (!associativeLineIsAlwaysAboveNode) {
createAssociativeLineDraw()
}
// 节点容器
this.nodeDraw = this.draw.group()
this.nodeDraw.addClass('smm-node-container')
// 关联线始终处于节点上方
if (associativeLineIsAlwaysAboveNode) {
createAssociativeLineDraw()
}
// 其他内容的容器
this.otherDraw = this.draw.group()
this.otherDraw.addClass('smm-other-container')
}
// 清空各容器
clearDraw() {
this.lineDraw.clear()
this.associativeLineDraw.clear()
this.nodeDraw.clear()
this.otherDraw.clear()
}
// 添加必要的css样式到页面
addCss() {
this.cssEl = document.createElement('style')
@@ -136,19 +170,27 @@ class MindMap {
// 重新渲染
reRender(callback, source = '') {
this.batchExecution.push('render', () => {
this.draw.clear()
this.clearDraw()
this.initTheme()
this.renderer.reRender = true
this.renderer.render(callback, source)
})
}
// 容器尺寸变化,调整尺寸
resize() {
// 获取或更新容器尺寸位置信息
getElRectInfo() {
this.elRect = this.el.getBoundingClientRect()
this.width = this.elRect.width
this.height = this.elRect.height
if (this.width <= 0 || this.height <= 0)
throw new Error('容器元素el的宽高不能为0')
}
// 容器尺寸变化,调整尺寸
resize() {
this.getElRectInfo()
this.svg.size(this.width, this.height)
this.emit('resize')
}
// 监听事件
@@ -192,10 +234,12 @@ class MindMap {
}
// 设置主题
setTheme(theme) {
this.renderer.clearAllActive()
setTheme(theme, notRender = false) {
this.execCommand('CLEAR_ACTIVE_NODE')
this.opt.theme = theme
this.render(null, CONSTANTS.CHANGE_THEME)
if (!notRender) {
this.render(null, CONSTANTS.CHANGE_THEME)
}
this.emit('view_theme_change', theme)
}
@@ -205,13 +249,15 @@ class MindMap {
}
// 设置主题配置
setThemeConfig(config) {
setThemeConfig(config, notRender = false) {
// 计算改变了的配置
const changedConfig = getObjectChangedProps(this.themeConfig, config)
this.opt.themeConfig = config
// 检查改变的是否是节点大小无关的主题属性
let res = checkIsNodeSizeIndependenceConfig(changedConfig)
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
if (!notRender) {
// 检查改变的是否是节点大小无关的主题属性
let res = checkIsNodeSizeIndependenceConfig(changedConfig)
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
}
}
// 获取自定义主题配置
@@ -240,7 +286,7 @@ class MindMap {
}
// 设置布局结构
setLayout(layout) {
setLayout(layout, notRender = false) {
// 检查布局配置
if (!layoutValueList.includes(layout)) {
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
@@ -248,7 +294,9 @@ class MindMap {
this.opt.layout = layout
this.view.reset()
this.renderer.setLayout()
this.render(null, CONSTANTS.CHANGE_LAYOUT)
if (!notRender) {
this.render(null, CONSTANTS.CHANGE_LAYOUT)
}
}
// 执行命令
@@ -334,20 +382,20 @@ class MindMap {
this.opt.readonly = mode === CONSTANTS.MODE.READONLY
if (this.opt.readonly) {
// 取消当前激活的元素
this.renderer.clearAllActive()
this.execCommand('CLEAR_ACTIVE_NODE')
}
this.emit('mode_change', mode)
}
// 获取svg数据
getSvgData({ paddingX = 0, paddingY = 0 } = {}) {
getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false } = {}) {
const svg = this.svg
const draw = this.draw
// 保存原始信息
const origWidth = svg.width()
const origHeight = svg.height()
const origTransform = draw.transform()
const elRect = this.el.getBoundingClientRect()
const elRect = this.elRect
// 去除放大缩小的变换效果
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
// 获取变换后的位置尺寸信息其实是getBoundingClientRect方法的包装方法
@@ -362,10 +410,9 @@ class MindMap {
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
// 克隆一份数据
let clone = svg.clone()
// 添加必要的样式
clone.add(SVG(`<style>${cssContent}</style>`))
// 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题
if (
!ignoreWatermark &&
(rect.width > origWidth || rect.height > origHeight) &&
this.watermark &&
this.watermark.hasWatermark()
@@ -378,6 +425,16 @@ class MindMap {
this.height = origHeight
this.watermark.draw()
}
// 添加必要的样式
clone.add(SVG(`<style>${cssContent}</style>`))
// 修正关联线箭头marker的id
const markerList = svg.find('marker')
if (markerList && markerList.length > 0) {
const id = markerList[0].attr('id')
clone.find('marker').forEach(item => {
item.attr('id', id)
})
}
// 恢复原先的大小和变换信息
svg.size(origWidth, origHeight)
draw.transform(origTransform)
@@ -429,6 +486,13 @@ class MindMap {
// 销毁
destroy() {
this.emit('beforeDestroy')
// 清除节点编辑框
this.renderer.textEdit.hideEditTextBox()
// 清除关联线文字编辑框
if (this.associativeLine) {
this.associativeLine.hideEditTextBox()
}
// 移除插件
;[...MindMap.pluginList].forEach(plugin => {
if (this[plugin.instanceName].beforePluginDestroy) {

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.7.3-fix.2",
"version": "0.9.1",
"description": "一个简单的web在线思维导图",
"authors": [
{
@@ -22,7 +22,7 @@
"scripts": {
"lint": "eslint src/",
"format": "prettier --write .",
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017",
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck",
"wsServe": "node ./bin/wsServer.mjs"
},
"module": "index.js",

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { copyRenderTree, simpleDeepClone, nextTick } from '../../utils'
import { copyRenderTree, simpleDeepClone, throttle } from '../../utils'
// 命令类
class Command {
@@ -11,7 +11,11 @@ class Command {
this.activeHistoryIndex = 0
// 注册快捷键
this.registerShortcutKeys()
this.addHistory = nextTick(this.addHistory, this)
this.addHistory = throttle(
this.addHistory,
this.mindMap.opt.addHistoryTime,
this
)
}
// 清空历史数据

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

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 {
@@ -22,7 +23,9 @@ class Node {
// 渲染实例
this.renderer = opt.renderer
// 渲染器
this.draw = opt.draw || null
this.draw = this.mindMap.draw
this.nodeDraw = this.mindMap.nodeDraw
this.lineDraw = this.mindMap.lineDraw
// 样式实例
this.style = new Style(this)
// 形状实例
@@ -71,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
@@ -79,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
// 尺寸信息
@@ -127,7 +130,7 @@ class Node {
})
// 协同相关
if (this.mindMap.cooperate) {
Object.keys(nodeCooperateMethods).forEach((item) => {
Object.keys(nodeCooperateMethods).forEach(item => {
this[item] = nodeCooperateMethods[item].bind(this)
})
}
@@ -176,6 +179,7 @@ class Node {
let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt
if (isUseCustomNodeContent && customCreateNodeContent) {
this._customNodeContent = customCreateNodeContent(this)
this._customNodeContent.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
}
// 如果没有返回内容,那么还是使用内置的节点内容
if (this._customNodeContent) return
@@ -428,20 +432,17 @@ class Node {
// 多选和取消多选
if (e.ctrlKey && enableCtrlKeyNodeSelection) {
this.isMultipleChoice = true
let isActive = this.nodeData.data.isActive
let isActive = this.getData('isActive')
if (!isActive)
this.mindMap.emit(
'before_node_active',
this,
this.renderer.activeNodeList
)
this.mindMap.execCommand('SET_NODE_ACTIVE', this, !isActive)
this.mindMap.renderer[isActive ? 'removeActiveNode' : 'addActiveNode'](
this
)
this.mindMap.emit('node_active', isActive ? null : this, [
...this.mindMap.renderer.activeNodeList
])
this.mindMap.renderer[
isActive ? 'removeNodeFromActiveList' : 'addNodeToActiveList'
](this)
this.renderer.emitNodeActiveEvent(isActive ? null : this)
}
this.mindMap.emit('node_mousedown', this, e)
})
@@ -456,17 +457,23 @@ class Node {
this._isMouseenter = true
// 显示展开收起按钮
this.showExpandBtn()
if (this.isGeneralization) {
this.handleGeneralizationMouseenter()
}
this.mindMap.emit('node_mouseenter', this, e)
})
this.group.on('mouseleave', e => {
if (!this._isMouseenter) return
this._isMouseenter = false
this.hideExpandBtn()
if (this.isGeneralization) {
this.handleGeneralizationMouseleave()
}
this.mindMap.emit('node_mouseleave', this, e)
})
// 双击事件
this.group.on('dblclick', e => {
if (this.mindMap.opt.readonly) {
if (this.mindMap.opt.readonly || e.ctrlKey) {
return
}
e.stopPropagation()
@@ -489,10 +496,13 @@ class Node {
) {
return
}
if (this.nodeData.data.isActive) {
this.renderer.clearActive()
// 如果有且只有当前节点激活了,那么不需要重新激活
if (
!(this.getData('isActive') && this.renderer.activeNodeList.length === 1)
) {
this.renderer.clearActiveNodeList()
this.active(e)
}
this.active(e)
this.mindMap.emit('node_contextmenu', e, this)
})
}
@@ -503,14 +513,13 @@ class Node {
return
}
e && e.stopPropagation()
if (this.nodeData.data.isActive) {
if (this.getData('isActive')) {
return
}
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
this.renderer.clearActive()
this.mindMap.execCommand('SET_NODE_ACTIVE', this, true)
this.renderer.addActiveNode(this)
this.mindMap.emit('node_active', this, [...this.renderer.activeNodeList])
this.renderer.clearActiveNodeList()
this.renderer.addNodeToActiveList(this)
this.renderer.emitNodeActiveEvent(this)
}
// 更新节点
@@ -518,7 +527,7 @@ class Node {
if (!this.group) {
return
}
this.updateNodeActive()
this.updateNodeActiveClass()
let { alwaysShowExpandBtn } = this.mindMap.opt
if (alwaysShowExpandBtn) {
// 需要移除展开收缩按钮
@@ -529,7 +538,7 @@ class Node {
this.renderExpandBtn()
}
} else {
let { isActive, expand } = this.nodeData.data
let { isActive, expand } = this.getData()
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
if (expand && !isActive && !this._isMouseenter) {
this.hideExpandBtn()
@@ -594,12 +603,25 @@ class Node {
}
// 更新节点激活状态
updateNodeActive() {
updateNodeActiveClass() {
if (!this.group) return
const isActive = this.nodeData.data.isActive
const isActive = this.getData('isActive')
this.group[isActive ? 'addClass' : 'removeClass']('active')
}
// 根据是否激活更新节点
updateNodeByActive(active) {
if (this.group) {
// 切换激活状态,需要切换展开收起按钮的显隐
if (active) {
this.showExpandBtn()
} else {
this.hideExpandBtn()
}
this.updateNodeActiveClass()
}
}
// 递归渲染
render(callback = () => {}) {
// 节点
@@ -613,11 +635,11 @@ class Node {
cursor: 'default'
})
this.bindGroupEvent()
this.draw.add(this.group)
this.nodeDraw.add(this.group)
this.layout()
this.update()
} else {
this.draw.add(this.group)
this.nodeDraw.add(this.group)
if (this.needLayout) {
this.needLayout = false
this.layout()
@@ -629,7 +651,7 @@ class Node {
if (
this.children &&
this.children.length &&
this.nodeData.data.expand !== false
this.getData('expand') !== false
) {
let index = 0
this.children.forEach(item => {
@@ -674,6 +696,9 @@ class Node {
this.removeGeneralization()
this.removeLine()
this.group = null
if (this.parent) {
this.parent.removeLine()
}
}
// 隐藏节点
@@ -730,10 +755,7 @@ class Node {
item.setOpacity(val)
})
// 概要节点
if (this._generalizationNode) {
this._generalizationLine.opacity(val)
this._generalizationNode.group.opacity(val)
}
this.setGeneralizationOpacity(val)
}
// 隐藏子节点
@@ -774,7 +796,7 @@ class Node {
// 连线
renderLine(deep = false) {
if (this.nodeData.data.expand === false) {
if (this.getData('expand') === false) {
return
}
let childrenLen = this.nodeData.children.length
@@ -788,7 +810,7 @@ class Node {
if (childrenLen > this._lines.length) {
// 创建缺少的线
new Array(childrenLen - this._lines.length).fill(0).forEach(() => {
this._lines.push(this.draw.path())
this._lines.push(this.lineDraw.path())
})
} else if (childrenLen < this._lines.length) {
// 删除多余的线
@@ -828,7 +850,7 @@ class Node {
return this.customLeft !== undefined && this.customTop !== undefined
}
// 检查节点是否存在自定义位置的祖先节点
// 检查节点是否存在自定义位置的祖先节点,包含自身
ancestorHasCustomPosition() {
let node = this
while (node) {
@@ -840,6 +862,18 @@ class Node {
return false
}
// 检查是否存在有概要的祖先节点
ancestorHasGeneralization() {
let node = this.parent
while (node) {
if (node.checkHasGeneralization()) {
return true
}
node = node.parent
}
return false
}
// 添加子节点
addChildren(node) {
this.children.push(node)
@@ -870,7 +904,7 @@ class Node {
}
// 检测当前节点是否是某个节点的祖先节点
isParent(node) {
isAncestor(node) {
if (this.uid === node.uid) {
return false
}
@@ -884,6 +918,18 @@ class Node {
return false
}
// 检查当前节点是否是某个节点的父节点
isParent(node) {
if (this.uid === node.uid) {
return false
}
const parent = node.parent
if (parent && this.uid === parent.uid) {
return true
}
return false
}
// 检测当前节点是否是某个节点的兄弟节点
isBrother(node) {
if (!this.parent || this.uid === node.uid) {
@@ -894,9 +940,18 @@ 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.nodeData.data
let { isActive } = this.getData()
return {
paddingX: this.getStyle('paddingX', true, isActive),
paddingY: this.getStyle('paddingY', true, isActive)
@@ -939,13 +994,42 @@ class Node {
// 获取数据
getData(key) {
return key ? this.nodeData.data[key] || '' : this.nodeData.data
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

@@ -88,7 +88,7 @@ class Style {
// 获取自身自定义样式
getSelfStyle(prop) {
return this.ctx.nodeData.data[prop]
return this.ctx.getData(prop)
}
// 矩形
@@ -107,7 +107,7 @@ class Style {
// !this.ctx.isRoot &&
// !this.ctx.isGeneralization &&
// this.ctx.mindMap.themeConfig.nodeUseLineStyle &&
// !this.ctx.nodeData.data.isActive
// !this.ctx.getData('isActive')
// ) {
// return
// }
@@ -225,7 +225,7 @@ class Style {
// 是否设置了自定义的样式
hasCustomStyle() {
let res = false
Object.keys(this.ctx.nodeData.data).forEach(item => {
Object.keys(this.ctx.getData()).forEach(item => {
if (checkIsNodeStyleDataKey(item)) {
res = true
}

View File

@@ -12,14 +12,14 @@ import { CONSTANTS, commonCaches } from '../../../constants/constant'
// 创建图片节点
function createImgNode() {
let img = this.nodeData.data.image
let img = this.getData('image')
if (!img) {
return
}
let imgSize = this.getImgShowSize()
let node = new Image().load(img).size(...imgSize)
if (this.nodeData.data.imageTitle) {
node.attr('title', this.nodeData.data.imageTitle)
if (this.getData('imageTitle')) {
node.attr('title', this.getData('imageTitle'))
}
node.on('dblclick', e => {
this.mindMap.emit('node_img_dblclick', this, e)
@@ -42,7 +42,7 @@ function createImgNode() {
// 获取图片显示宽高
function getImgShowSize() {
const { custom, width, height } = this.nodeData.data.imageSize
const { custom, width, height } = this.getData('imageSize')
// 如果是自定义了图片的宽高,那么不受最大宽高限制
if (custom) return [width, height]
return resizeImgSize(
@@ -55,7 +55,7 @@ function getImgShowSize() {
// 创建icon节点
function createIconNode() {
let _data = this.nodeData.data
let _data = this.getData()
if (!_data.icon || _data.icon.length <= 0) {
return []
}
@@ -91,7 +91,7 @@ function createRichTextNode() {
let g = new G()
// 重新设置富文本节点内容
let recoverText = false
if (this.nodeData.data.resetRichText) {
if (this.getData('resetRichText')) {
delete this.nodeData.data.resetRichText
recoverText = true
}
@@ -102,7 +102,7 @@ function createRichTextNode() {
}
}
if (recoverText) {
let text = this.nodeData.data.text
let text = this.getData('text')
// 判断节点内容是否是富文本
let isRichText = checkIsRichText(text)
// 样式字符串
@@ -116,9 +116,11 @@ function createRichTextNode() {
// 非富文本
text = `<p><span style="${style}">${text}</span></p>`
}
this.nodeData.data.text = text
this.setData({
text: text
})
}
let html = `<div>${this.nodeData.data.text}</div>`
let html = `<div>${this.getData('text')}</div>`
if (!commonCaches.measureRichtextNodeTextSizeEl) {
commonCaches.measureRichtextNodeTextSizeEl = document.createElement('div')
commonCaches.measureRichtextNodeTextSizeEl.style.position = 'fixed'
@@ -157,7 +159,7 @@ function createRichTextNode() {
// 创建文本节点
function createTextNode() {
if (this.nodeData.data.richText) {
if (this.getData('richText')) {
return this.createRichTextNode()
}
let g = new G()
@@ -166,8 +168,8 @@ function createTextNode() {
// 文本超长自动换行
let textStyle = this.style.getTextFontStyle()
let textArr = []
if (!isUndef(this.nodeData.data.text)) {
textArr = String(this.nodeData.data.text).split(/\n/gim)
if (!isUndef(this.getData('text'))) {
textArr = String(this.getData('text')).split(/\n/gim)
}
let maxWidth = this.mindMap.opt.textAutoWrapWidth
let isMultiLine = false
@@ -215,7 +217,7 @@ function createTextNode() {
// 创建超链接节点
function createHyperlinkNode() {
let { hyperlink, hyperlinkTitle } = this.nodeData.data
let { hyperlink, hyperlinkTitle } = this.getData()
if (!hyperlink) {
return
}
@@ -245,7 +247,7 @@ function createHyperlinkNode() {
// 创建标签节点
function createTagNode() {
let tagData = this.nodeData.data.tag
let tagData = this.getData('tag')
if (!tagData || tagData.length <= 0) {
return []
}
@@ -274,7 +276,7 @@ function createTagNode() {
// 创建备注节点
function createNoteNode() {
if (!this.nodeData.data.note) {
if (!this.getData('note')) {
return null
}
let iconSize = this.mindMap.themeConfig.iconSize
@@ -302,19 +304,20 @@ function createNoteNode() {
this.mindMap.opt.customInnerElsAppendTo || document.body
targetNode.appendChild(this.noteEl)
}
this.noteEl.innerText = this.nodeData.data.note
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.nodeData.data.note,
this.getData('note'),
left,
top + iconSize
top,
this
)
}
})
@@ -332,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) {
@@ -366,6 +382,7 @@ export default {
createHyperlinkNode,
createTagNode,
createNoteNode,
getNoteContentPosition,
measureCustomNodeContentSize,
isUseCustomNodeContent
}

View File

@@ -52,7 +52,7 @@ function sumNode(data = []) {
}
// 创建或更新展开收缩按钮内容
function updateExpandBtnNode() {
let { expand } = this.nodeData.data
let { expand } = this.getData()
// 如果本次和上次的展开状态一样则返回
if (expand === this._lastExpandBtnType) return
if (this._expandBtn) {
@@ -129,7 +129,7 @@ function renderExpandBtn() {
this.mindMap.execCommand(
'SET_NODE_EXPAND',
this,
!this.nodeData.data.expand
!this.getData('expand')
)
this.mindMap.emit('expand_btn_click', this)
})
@@ -164,7 +164,7 @@ function showExpandBtn() {
function hideExpandBtn() {
if (this.mindMap.opt.alwaysShowExpandBtn || this._isMouseenter) return
// 非激活状态且展开状态鼠标移出才隐藏按钮
let { isActive, expand } = this.nodeData.data
let { isActive, expand } = this.getData()
if (!isActive && expand) {
setTimeout(() => {
this.removeExpandBtn()

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.nodeData.data.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,27 +32,47 @@ function createGeneralizationNode() {
if (this.isGeneralization || !this.checkHasGeneralization()) {
return
}
if (!this._generalizationLine) {
this._generalizationLine = this.draw.path()
}
if (!this._generalizationNode) {
this._generalizationNode = new Node({
data: {
data: this.nodeData.data.generalization
},
uid: createUid(),
renderer: this.renderer,
mindMap: this.mindMap,
draw: this.draw,
isGeneralization: true
})
this._generalizationNodeWidth = this._generalizationNode.width
this._generalizationNodeHeight = this._generalizationNode.height
this._generalizationNode.generalizationBelongNode = this
if (this.nodeData.data.generalization.isActive) {
this.renderer.addActiveNode(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
}
// 更新概要节点
@@ -44,42 +85,67 @@ 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.nodeData.data.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.removeActiveNode(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.draw
this.nodeDraw
.find('.generalization_' + this.generalizationBelongNode.uid)
.remove()
}
@@ -88,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

@@ -25,24 +25,19 @@ class View {
this.mindMap.keyCommand.addShortcut('Control+-', () => {
this.narrow()
})
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
this.reset()
})
this.mindMap.keyCommand.addShortcut('Control+i', () => {
this.fit()
})
this.mindMap.svg.on('dblclick', () => {
if (!this.mindMap.opt.enableDblclickReset) return
this.reset()
})
// 拖动视图
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) {
@@ -77,7 +72,7 @@ class View {
return customHandleMousewheel(e)
}
// 鼠标滚轮事件控制缩放
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM) {
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM || e.ctrlKey) {
if (disableMouseWheelZoom) return
const { x: clientX, y: clientY } = this.mindMap.toPos(
e.clientX,
@@ -267,7 +262,7 @@ class View {
let drawHeight = rect.height / origTransform.scaleY
let drawRatio = drawWidth / drawHeight
let { width: elWidth, height: elHeight } =
this.mindMap.el.getBoundingClientRect()
this.mindMap.elRect
elWidth = elWidth - fitPadding * 2
elHeight = elHeight - fitPadding * 2
let elRatio = elWidth / elHeight

View File

@@ -13,6 +13,7 @@ class Base {
this.mindMap = renderer.mindMap
// 绘图对象
this.draw = this.mindMap.draw
this.lineDraw = this.mindMap.lineDraw
// 根节点
this.root = null
this.lru = new Lru(this.mindMap.opt.maxNodeCacheCount)
@@ -90,7 +91,7 @@ class Base {
// 数据上没有保存节点引用但是通过uid找到了缓存的节点也可以复用
newNode = this.lru.get(data.data.uid)
// 保存该节点上一次的数据
let lastData = JSON.stringify(newNode.nodeData.data)
let lastData = JSON.stringify(newNode.getData())
let isLayerTypeChange = this.checkIsLayerTypeChange(
newNode.layerIndex,
layerIndex
@@ -126,12 +127,14 @@ class Base {
// 数据关联实际节点
data._node = newNode
if (data.data.isActive) {
this.renderer.addActiveNode(newNode)
this.renderer.addNodeToActiveList(newNode)
}
}
// 如果当前节点在激活节点列表里,那么添加上激活的状态
if (this.mindMap.renderer.findActiveNodeIndex(newNode) !== -1) {
newNode.nodeData.data.isActive = true
newNode.setData({
isActive: true
})
}
// 根节点
if (isRoot) {
@@ -297,12 +300,12 @@ class Base {
let { left, right, top, bottom } = walk(child)
// 概要内容的宽度
let generalizationWidth =
child.checkHasGeneralization() && child.nodeData.data.expand
child.checkHasGeneralization() && child.getData('expand')
? child._generalizationNodeWidth + generalizationNodeMargin
: 0
// 概要内容的高度
let generalizationHeight =
child.checkHasGeneralization() && child.nodeData.data.expand
child.checkHasGeneralization() && child.getData('expand')
? child._generalizationNodeHeight + generalizationNodeMargin
: 0
if (left - (dir === 'h' ? generalizationWidth : 0) < _left) {
@@ -343,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

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
// 目录组织图
class CatalogOrganization extends Base {
@@ -72,11 +72,7 @@ class CatalogOrganization extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.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) {
@@ -114,7 +110,7 @@ class CatalogOrganization extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
// 调整left
@@ -159,9 +155,7 @@ class CatalogOrganization extends Base {
updateBrothersLeft(node, addWidth) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition() || _index <= index) {
// 适配自定义位置
@@ -182,9 +176,7 @@ class CatalogOrganization extends Base {
updateBrothersTop(node, addHeight) {
if (node.parent && !node.parent.isRoot) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
@@ -247,14 +239,14 @@ class CatalogOrganization extends Base {
minx = Math.min(minx, x1)
maxx = Math.max(maxx, x1)
// 父节点的竖线
let line1 = this.draw.path()
let line1 = this.lineDraw.path()
node.style.line(line1)
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
node._lines.push(line1)
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.draw.path()
let lin2 = this.lineDraw.path()
node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
node._lines.push(lin2)
@@ -315,7 +307,7 @@ class CatalogOrganization extends Base {
})
// 竖线
if (len > 0) {
let lin2 = this.draw.path()
let lin2 = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
node.style.line(lin2)
if (maxy < y1 + expandBtnSize) {
@@ -343,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

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun, degToRad } from '../utils'
import { walk, asyncRun, degToRad, getNodeIndexInNodeList } from '../utils'
import { CONSTANTS } from '../constants/constant'
import utils from './fishboneUtils'
@@ -112,7 +112,7 @@ class Fishbone extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
let params = { node, parent, layerIndex, ctx: this }
@@ -193,9 +193,7 @@ class Fishbone extends Base {
updateBrothersTop(node, addHeight) {
if (node.parent && !node.parent.isRoot) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
@@ -252,7 +250,7 @@ class Fishbone extends Base {
let nodeLineX = item.left
let offset = node.height / 2 + marginY
let offsetX = offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
let line = this.draw.path()
let line = this.lineDraw.path()
if (this.checkIsTop(item)) {
line.plot(
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
@@ -273,7 +271,7 @@ class Fishbone extends Base {
// 从根节点出发的水平线
let nodeHalfTop = node.top + node.height / 2
let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1)
let line = this.draw.path()
let line = this.lineDraw.path()
line.plot(
`M ${node.left + node.width},${nodeHalfTop} L ${
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
@@ -308,7 +306,7 @@ class Fishbone extends Base {
})
// 斜线
if (len >= 0) {
let line = this.draw.path()
let line = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
let lineLength = maxx - node.left - node.width * this.indent
lineLength = Math.max(lineLength, 0)
@@ -359,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

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
import { CONSTANTS } from '../utils/constant'
const degToRad = deg => {
@@ -127,7 +127,7 @@ class Fishbone extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
// 调整top
@@ -237,9 +237,7 @@ class Fishbone extends Base {
updateBrothersTop(node, addHeight) {
if (node.parent && !node.parent.isRoot) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
@@ -307,7 +305,7 @@ class Fishbone extends Base {
})
// 竖线
if (len > 0) {
let line = this.draw.path()
let line = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
let lineLength = maxx - node.left - node.width * 0.3
if (node.parent && node.parent.isRoot) {

View File

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
import { CONSTANTS } from '../utils/constant'
const degToRad = deg => {
@@ -110,7 +110,7 @@ class Fishbone extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
// 调整top
@@ -206,9 +206,7 @@ class Fishbone extends Base {
updateBrothersTop(node, addHeight) {
if (node.parent && !node.parent.isRoot) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
@@ -276,7 +274,7 @@ class Fishbone extends Base {
})
// 竖线
if (len > 0) {
let line = this.draw.path()
let line = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
let lineLength = maxx - node.left - node.width * 0.3
if (

View File

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
// 逻辑结构图
class LogicalStructure extends Base {
@@ -77,11 +77,7 @@ class LogicalStructure extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.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
@@ -103,7 +99,7 @@ class LogicalStructure extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
@@ -124,9 +120,7 @@ class LogicalStructure extends Base {
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.uid === node.uid || item.hasCustomPosition()) {
// 适配自定义位置
@@ -271,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

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
import { CONSTANTS } from '../constants/constant'
// 思维导图
@@ -117,7 +117,7 @@ class MindMap extends Base {
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.getData('expand') &&
node.children &&
node.children.length
) {
@@ -148,7 +148,7 @@ class MindMap extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
@@ -171,9 +171,7 @@ class MindMap extends Base {
let childrenList = node.parent.children.filter(item => {
return item.dir === node.dir
})
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
@@ -360,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

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
// 组织结构图
// 和逻辑结构图基本一样只是方向变成向下生长所以先计算节点的top后计算节点的left、最后调整节点的left即可
@@ -79,7 +79,7 @@ class OrganizationStructure extends Base {
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.getData('expand') &&
node.children &&
node.children.length
) {
@@ -104,7 +104,7 @@ class OrganizationStructure extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
// 判断子节点所占的宽度之和是否大于该节点自身,大于则需要调整位置
@@ -125,9 +125,7 @@ class OrganizationStructure extends Base {
updateBrothers(node, addWidth) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
@@ -218,7 +216,7 @@ class OrganizationStructure extends Base {
minx = Math.min(x1, minx)
maxx = Math.max(x1, maxx)
// 父节点的竖线
let line1 = this.draw.path()
let line1 = this.lineDraw.path()
node.style.line(line1)
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
line1.plot(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
@@ -226,7 +224,7 @@ class OrganizationStructure extends Base {
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.draw.path()
let lin2 = this.lineDraw.path()
node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
node._lines.push(lin2)
@@ -245,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

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
import { CONSTANTS } from '../constants/constant'
// 时间轴
@@ -80,11 +80,7 @@ class Timeline extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex, index) => {
if (
node.nodeData.data.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) {
@@ -122,7 +118,7 @@ class Timeline extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
// 调整left
@@ -208,9 +204,7 @@ class Timeline extends Base {
updateBrothersTop(node, addHeight) {
if (node.parent && !node.parent.isRoot) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
@@ -275,7 +269,7 @@ class Timeline extends Base {
})
// 竖线
if (len > 0) {
let line = this.draw.path()
let line = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
if (
node.parent &&
@@ -317,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

@@ -1,5 +1,5 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
import { CONSTANTS } from '../constants/constant'
// 竖向时间轴
@@ -97,11 +97,7 @@ class VerticalTimeline extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex, index) => {
if (
node.nodeData.data.expand &&
node.children &&
node.children.length
) {
if (node.getData('expand') && node.children && node.children.length) {
let marginY = this.getMarginY(layerIndex + 1)
// 定位二级节点的top
if (isRoot) {
@@ -135,7 +131,7 @@ class VerticalTimeline extends Base {
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
if (!node.getData('expand')) {
return
}
if (isRoot) return
@@ -155,9 +151,7 @@ class VerticalTimeline extends Base {
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
// 自定义节点位置
if (item.hasCustomPosition()) return
@@ -201,9 +195,7 @@ class VerticalTimeline extends Base {
updateBrothersTop(node, addHeight) {
if (node.parent && !node.parent.isRoot) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
@@ -390,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

@@ -15,7 +15,7 @@ import associativeLineTextMethods from './associativeLine/associativeLineText'
class AssociativeLine {
constructor(opt = {}) {
this.mindMap = opt.mindMap
this.draw = this.mindMap.draw
this.associativeLineDraw = this.mindMap.associativeLineDraw
// 当前所有连接线
this.lineList = []
// 当前激活的连接线
@@ -98,7 +98,7 @@ class AssociativeLine {
// 创建箭头
createMarker() {
return this.draw.marker(20, 20, add => {
return this.associativeLineDraw.marker(20, 20, add => {
add.ref(12, 5)
add.size(10, 10)
add.attr('orient', 'auto-start-reverse')
@@ -142,7 +142,7 @@ class AssociativeLine {
null,
cur => {
if (!cur) return
let data = cur.nodeData.data
let data = cur.getData()
if (
data.associativeLineTargets &&
data.associativeLineTargets.length > 0
@@ -161,7 +161,7 @@ class AssociativeLine {
ids.forEach((uid, index) => {
let toNode = idToNode.get(uid)
if (!node || !toNode) return
const associativeLinePoint = (node.nodeData.data.associativeLinePoint ||
const associativeLinePoint = (node.getData('associativeLinePoint') ||
[])[index]
// 切换结构和布局,都会更新坐标
const [startPoint, endPoint] = this.updateAllLinesPos(
@@ -194,7 +194,7 @@ class AssociativeLine {
toNode
)
// 虚线
let path = this.draw.path()
let path = this.associativeLineDraw.path()
path
.stroke({
width: associativeLineWidth,
@@ -205,7 +205,7 @@ class AssociativeLine {
path.plot(pathStr)
path.marker('end', this.marker)
// 不可见的点击线
let clickPath = this.draw.path()
let clickPath = this.associativeLineDraw.path()
clickPath
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
.fill({ color: 'none' })
@@ -276,6 +276,7 @@ class AssociativeLine {
controlPoints[1]
)
this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
this.front()
}
// 移除所有连接线
@@ -300,9 +301,10 @@ class AssociativeLine {
let { associativeLineWidth, associativeLineColor } =
this.mindMap.themeConfig
if (this.isCreatingLine || !fromNode) return
this.front()
this.isCreatingLine = true
this.creatingStartNode = fromNode
this.creatingLine = this.draw.path()
this.creatingLine = this.associativeLineDraw.path()
this.creatingLine
.stroke({
width: associativeLineWidth,
@@ -357,12 +359,13 @@ class AssociativeLine {
height
}
}
// 检测当前移动到的目标节点
checkOverlapNode(x, y) {
this.overlapNode = null
bfsWalk(this.mindMap.renderer.root, node => {
if (node.nodeData.data.isActive) {
this.mindMap.renderer.setNodeActive(node, false)
if (node.getData('isActive')) {
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
}
if (node.uid === this.creatingStartNode.uid || this.overlapNode) {
return
@@ -374,8 +377,8 @@ class AssociativeLine {
this.overlapNode = node
}
})
if (this.overlapNode && !this.overlapNode.nodeData.data.isActive) {
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
if (this.overlapNode && !this.overlapNode.getData('isActive')) {
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
}
}
@@ -383,21 +386,22 @@ class AssociativeLine {
completeCreateLine(node) {
if (this.creatingStartNode.uid === node.uid) return
this.addLine(this.creatingStartNode, node)
if (this.overlapNode && this.overlapNode.nodeData.data.isActive) {
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
if (this.overlapNode && this.overlapNode.getData('isActive')) {
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, false)
}
this.isCreatingLine = false
this.creatingStartNode = null
this.creatingLine.remove()
this.creatingLine = null
this.overlapNode = null
this.back()
}
// 添加连接线
addLine(fromNode, toNode) {
if (!fromNode || !toNode) return
// 目标节点如果没有id则生成一个id
let uid = toNode.nodeData.data.uid
let uid = toNode.getData('uid')
if (!uid) {
uid = uuid()
this.mindMap.execCommand('SET_NODE_DATA', toNode, {
@@ -405,7 +409,7 @@ class AssociativeLine {
})
}
// 将目标节点id保存起来
let list = fromNode.nodeData.data.associativeLineTargets || []
let list = fromNode.getData('associativeLineTargets') || []
// 连线节点是否存在相同的id,存在则阻止添加关联线
const sameLine = list.some(item => item === uid)
if (sameLine) {
@@ -421,7 +425,7 @@ class AssociativeLine {
endPoint.y
)
let offsetList =
fromNode.nodeData.data.associativeLineTargetControlOffsets || []
fromNode.getData('associativeLineTargetControlOffsets') || []
// 保存的实际是控制点和端点的差值,否则当节点位置改变了,控制点还是原来的位置,连线就不对了
offsetList[list.length - 1] = [
{
@@ -433,7 +437,7 @@ class AssociativeLine {
y: controlPoints[1].y - endPoint.y
}
]
let associativeLinePoint = fromNode.nodeData.data.associativeLinePoint || []
let associativeLinePoint = fromNode.getData('associativeLinePoint') || []
// 记录关联的起始|结束坐标
associativeLinePoint[list.length - 1] = { startPoint, endPoint }
this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
@@ -453,14 +457,14 @@ class AssociativeLine {
associativeLinePoint,
associativeLineTargetControlOffsets,
associativeLineText
} = node.nodeData.data
} = node.getData()
associativeLinePoint = associativeLinePoint || []
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
// 更新关联线文本数据
let newAssociativeLineText = {}
if (associativeLineText) {
Object.keys(associativeLineText).forEach(item => {
if (item !== toNode.nodeData.data.uid) {
if (item !== toNode.getData('uid')) {
newAssociativeLineText[item] = associativeLineText[item]
}
})
@@ -500,6 +504,7 @@ class AssociativeLine {
}
this.activeLine = null
this.removeControls()
this.back()
}
}
@@ -526,6 +531,19 @@ class AssociativeLine {
this.showControls()
this.isNodeDragging = false
}
// 关联线顶层显示
front() {
if (this.mindMap.opt.associativeLineIsAlwaysAboveNode) return
this.associativeLineDraw.front()
}
// 关联线回到原有层级
back() {
if (this.mindMap.opt.associativeLineIsAlwaysAboveNode) return
this.associativeLineDraw.back() // 最底层
this.associativeLineDraw.forward() // 连线层上面
}
}
AssociativeLine.instanceName = 'associativeLine'

View File

@@ -16,6 +16,7 @@ class Cooperate {
// 感知数据
this.awareness = null
this.currentAwarenessData = []
this.waitNodeUidMap = {} // 该列表中的uid对应的节点还未渲染完毕
// 当前的平级对象类型的思维导图数据
this.currentData = null
// 用户信息
@@ -79,6 +80,10 @@ class Cooperate {
this.onNodeActive = this.onNodeActive.bind(this)
this.mindMap.on('node_active', this.onNodeActive)
// 监听思维导图渲染完毕事件
this.onNodeTreeRenderEnd = this.onNodeTreeRenderEnd.bind(this)
this.mindMap.on('node_tree_render_end', this.onNodeTreeRenderEnd)
// 监听设置思维导图数据事件
this.initData = this.initData.bind(this)
this.mindMap.on('set_data', this.initData)
@@ -91,6 +96,7 @@ class Cooperate {
}
this.mindMap.off('data_change', this.onDataChange)
this.mindMap.off('node_active', this.onNodeActive)
this.mindMap.off('node_tree_render_end', this.onNodeTreeRenderEnd)
this.mindMap.off('set_data', this.initData)
this.ydoc.destroy()
}
@@ -153,6 +159,17 @@ class Cooperate {
}
}
// 节点树渲染完毕事件
onNodeTreeRenderEnd() {
Object.keys(this.waitNodeUidMap).forEach(uid => {
const node = this.mindMap.renderer.findNodeByUid(uid)
if (node) {
node.addUser(this.waitNodeUidMap[uid])
}
})
this.waitNodeUidMap = {}
}
// 设置用户信息
/**
* {
@@ -183,23 +200,27 @@ class Cooperate {
const nodeIdList = data.nodeIdList
nodeIdList.forEach(uid => {
const node = this.mindMap.renderer.findNodeByUid(uid)
if (node) {
callback(node, userInfo)
}
callback(uid, node, userInfo)
})
})
}
// 清除之前的数据
walk(this.currentAwarenessData, (node, userInfo) => {
node.removeUser(userInfo)
walk(this.currentAwarenessData, (uid, node, userInfo) => {
if (node) {
node.removeUser(userInfo)
}
})
// 设置当前数据
const data = Array.from(this.awareness.getStates().values())
this.currentAwarenessData = data
walk(data, (node, userInfo) => {
walk(data, (uid, node, userInfo) => {
// 不显示自己
if (userInfo.id === this.userInfo.id) return
node.addUser(userInfo)
if (node) {
node.addUser(userInfo)
} else {
this.waitNodeUidMap[uid] = userInfo
}
})
}

View File

@@ -1,4 +1,9 @@
import { bfsWalk, throttle, getTopAncestorsFomNodeList } from '../utils'
import {
bfsWalk,
throttle,
getTopAncestorsFomNodeList,
getNodeIndexInNodeList
} from '../utils'
import Base from '../layouts/Base'
// 节点拖动插件
@@ -108,14 +113,12 @@ class Drag extends Base {
node.endDrag()
})
this.removeCloneNode()
let overlapNodeUid = this.overlapNode
? this.overlapNode.nodeData.data.uid
: ''
let prevNodeUid = this.prevNode ? this.prevNode.nodeData.data.uid : ''
let nextNodeUid = this.nextNode ? this.nextNode.nodeData.data.uid : ''
let overlapNodeUid = this.overlapNode ? this.overlapNode.getData('uid') : ''
let prevNodeUid = this.prevNode ? this.prevNode.getData('uid') : ''
let nextNodeUid = this.nextNode ? this.nextNode.getData('uid') : ''
// 存在重叠子节点,则移动作为其子节点
if (this.overlapNode) {
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, false)
this.mindMap.execCommand(
'MOVE_NODE_TO',
this.beingDragNodeList,
@@ -123,7 +126,7 @@ class Drag extends Base {
)
} else if (this.prevNode) {
// 存在前一个相邻节点,作为其下一个兄弟节点
this.mindMap.renderer.setNodeActive(this.prevNode, false)
this.mindMap.execCommand('SET_NODE_ACTIVE', this.prevNode, false)
this.mindMap.execCommand(
'INSERT_AFTER',
this.beingDragNodeList,
@@ -131,7 +134,7 @@ class Drag extends Base {
)
} else if (this.nextNode) {
// 存在下一个相邻节点,作为其前一个兄弟节点
this.mindMap.renderer.setNodeActive(this.nextNode, false)
this.mindMap.execCommand('SET_NODE_ACTIVE', this.nextNode, false)
this.mindMap.execCommand(
'INSERT_BEFORE',
this.beingDragNodeList,
@@ -162,12 +165,14 @@ class Drag extends Base {
)
this.mindMap.render()
}
if (this.isDragging) {
this.mindMap.emit('node_dragend', {
overlapNodeUid,
prevNodeUid,
nextNodeUid
})
}
this.reset()
this.mindMap.emit('node_dragend', {
overlapNodeUid,
prevNodeUid,
nextNodeUid
})
}
// 拖动中
@@ -205,7 +210,7 @@ class Drag extends Base {
this.offsetX = this.mouseDownX - (node.left * scaleX + translateX)
this.offsetY = this.mouseDownY - (node.top * scaleY + translateY)
// 如果鼠标按下的节点是激活节点,那么保存当前所有激活的节点
if (node.nodeData.data.isActive) {
if (node.getData('isActive')) {
// 找出这些激活节点中的最顶层节点
this.beingDragNodeList = getTopAncestorsFomNodeList(
// 过滤掉根节点和概要节点
@@ -222,7 +227,7 @@ class Drag extends Base {
// 创建克隆节点
this.createCloneNode()
// 清除当前所有激活的节点
this.mindMap.renderer.clearAllActive()
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
}
}
@@ -261,7 +266,7 @@ class Drag extends Base {
const lineColor = node.style.merge('lineColor', true)
// 如果当前被拖拽的节点数量大于1那么创建一个矩形示意
if (this.beingDragNodeList.length > 1) {
this.clone = this.draw
this.clone = this.mindMap.otherDraw
.rect()
.size(rectWidth, rectHeight)
.radius(rectHeight / 2)
@@ -278,12 +283,12 @@ class Drag extends Base {
if (expandEl) {
expandEl.remove()
}
this.mindMap.draw.add(this.clone)
this.mindMap.otherDraw.add(this.clone)
}
this.clone.opacity(dragOpacityConfig.cloneNodeOpacity)
this.clone.css('z-index', 99999)
// 同级位置提示元素
this.placeholder = this.draw.rect().fill({
this.placeholder = this.mindMap.otherDraw.rect().fill({
color: dragPlaceholderRectFill || lineColor
})
// 当前被拖拽的节点的临时设置
@@ -317,8 +322,8 @@ class Drag extends Base {
this.nextNode = null
this.placeholder.size(0, 0)
this.nodeList.forEach(node => {
if (node.nodeData.data.isActive) {
this.mindMap.renderer.setNodeActive(node, false)
if (node.getData('isActive')) {
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
}
if (this.overlapNode || (this.prevNode && this.nextNode)) {
return
@@ -353,7 +358,7 @@ class Drag extends Base {
}
})
if (this.overlapNode) {
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
}
}
@@ -487,9 +492,7 @@ class Drag extends Base {
getNodeDistanceToSiblingNode(checkList, node, nodeRect, dir) {
let dir1 = dir === 'v' ? 'top' : 'left'
let dir2 = dir === 'v' ? 'bottom' : 'right'
let index = checkList.findIndex(item => {
return item.uid === node.uid
})
let index = getNodeIndexInNodeList(node, checkList)
let prevBrother = null
let nextBrother = null
if (index !== -1) {
@@ -679,7 +682,7 @@ class Drag extends Base {
// 检查某个节点是否在被拖拽节点内
checkIsInBeingDragNodeList(node) {
return !!this.beingDragNodeList.find(item => {
return item.uid === node.uid || item.isParent(node)
return item.uid === node.uid || item.isAncestor(node)
})
}
}

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
import {
isWhite,
isTransparent,
getVisibleColorFromTheme
getVisibleColorFromTheme,
readBlob
} from '../utils/index'
// 小地图插件
@@ -27,7 +28,9 @@ class MiniMap {
*/
calculationMiniMap(boxWidth, boxHeight) {
let { svg, rect, origWidth, origHeight, scaleX, scaleY } =
this.mindMap.getSvgData()
this.mindMap.getSvgData({
ignoreWatermark: true
})
// 计算数据
const elRect = this.mindMap.elRect
rect.x -= elRect.left
@@ -85,10 +88,18 @@ class MiniMap {
Object.keys(viewBoxStyle).forEach(key => {
viewBoxStyle[key] = viewBoxStyle[key] + 'px'
})
this.removeNodeContent(svg)
const svgStr = svg.svg()
return {
svgHTML: svg.svg(), // 小地图html
getImgUrl: async callback => {
const blob = new Blob([svgStr], {
type: 'image/svg+xml'
})
const res = await readBlob(blob)
callback(res)
},
svgHTML: svgStr, // 小地图html
viewBoxStyle, // 视图框的位置信息
miniMapBoxScale, // 视图框的缩放值
miniMapBoxLeft, // 视图框的left值

View File

@@ -205,7 +205,7 @@ class NodeImgAdjust {
// 隐藏节点实际图片
this.hideNodeImage()
// 将节点图片渲染到自定义元素上
this.handleEl.style.backgroundImage = `url(${this.node.nodeData.data.image})`
this.handleEl.style.backgroundImage = `url(${this.node.getData('image')})`
}
// 鼠标移动
@@ -214,7 +214,7 @@ class NodeImgAdjust {
e.preventDefault()
// 计算当前拖拽位置对应的图片的实时大小
let { width: imageOriginWidth, height: imageOriginHeight } =
this.node.nodeData.data.imageSize
this.node.getData('imageSize')
let newWidth = e.clientX - this.rect.x
let newHeight = e.clientY - this.rect.y
if (newWidth <= 0 || newHeight <= 0) return
@@ -237,7 +237,7 @@ class NodeImgAdjust {
// 隐藏自定义元素
this.hideHandleEl()
// 更新节点图片为新的大小
let { image, imageTitle } = this.node.nodeData.data
let { image, imageTitle } = this.node.getData()
let { scaleX, scaleY } = this.mindMap.draw.transform()
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, {
url: image,

View File

@@ -38,6 +38,7 @@ class Painter {
}
onEndPainter() {
if (!this.isInPainter) return
this.endPainter()
this.mindMap.emit('painter_end')
}
@@ -53,7 +54,7 @@ class Painter {
)
return
const style = {}
const painterNodeData = this.painterNode.nodeData.data
const painterNodeData = this.painterNode.getData()
Object.keys(painterNodeData).forEach(key => {
if (checkIsNodeStyleDataKey(key)) {
style[key] = painterNodeData[key]

View File

@@ -153,7 +153,7 @@ class RichText {
}
// 显示文本编辑控件
showEditText(node, rect, isInserting, isFromKeyDown) {
showEditText({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
if (this.showTextEdit) {
return
}
@@ -167,7 +167,9 @@ class RichText {
this.node = node
this.isInserting = isInserting
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
this.mindMap.emit('before_show_text_edit')
if (!isFromScale) {
this.mindMap.emit('before_show_text_edit')
}
this.mindMap.renderer.textEdit.registerTmpShortcut()
// 原始宽高
let g = node._textData.node
@@ -236,17 +238,17 @@ class RichText {
this.textEditNode.style.borderRadius = (node.height || 50) + 'px'
}
}
if (!node.nodeData.data.richText) {
if (!node.getData('richText')) {
// 还不是富文本的情况
let text = ''
if (!isUndef(node.nodeData.data.text)) {
text = String(node.nodeData.data.text).split(/\n/gim).join('<br>')
if (!isUndef(node.getData('text'))) {
text = String(node.getData('text')).split(/\n/gim).join('<br>')
}
let html = `<p>${text}</p>`
this.textEditNode.innerHTML = this.cacheEditingText || html
} else {
this.textEditNode.innerHTML =
this.cacheEditingText || node.nodeData.data.text
this.cacheEditingText || node.getData('text')
}
this.initQuillEditor()
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
@@ -256,7 +258,7 @@ class RichText {
this.focus(
isInserting || (selectTextOnEnterEditText && !isFromKeyDown) ? 0 : null
)
if (!node.nodeData.data.richText) {
if (!node.getData('richText')) {
// 如果是非富文本的情况,需要手动应用文本样式
this.setTextStyleIfNotRichText(node)
}
@@ -422,10 +424,13 @@ class RichText {
// 中文输入结束
onCompositionEnd() {
if (!this.showTextEdit || !this.lostStyle) {
if (!this.showTextEdit) {
return
}
this.isCompositing = false
if (!this.lostStyle) {
return
}
this.setTextStyleIfNotRichText(this.node)
}
@@ -494,7 +499,7 @@ class RichText {
})
} else {
let data = this.richTextStyleToNormalStyle(config)
this.mindMap.renderer.setNodeData(this.node, data)
this.mindMap.execCommand('SET_NODE_DATA', this.node, data)
}
}
@@ -564,6 +569,16 @@ class RichText {
return data
}
// 给未激活的节点设置富文本样式
setNotActiveNodeStyle(node, style) {
const config = this.normalStyleToRichTextStyle(style)
if (Object.keys(config).length > 0) {
this.showEditText({ node })
this.formatAllText(config)
this.hideEditText([node])
}
}
// 处理导出为图片
async handleExportPng(node) {
let el = document.createElement('div')
@@ -638,11 +653,13 @@ class RichText {
beforePluginRemove() {
this.transformAllNodesToNormalNode()
document.head.removeChild(this.styleEl)
this.unbindEvent()
}
// 插件被卸载前做的事情
beforePluginDestroy() {
document.head.removeChild(this.styleEl)
this.unbindEvent()
}
}

View File

@@ -65,6 +65,9 @@ class Search {
this.currentIndex = -1
this.notResetSearchText = false
this.isSearching = false
if (this.mindMap.opt.readonly) {
this.mindMap.renderer.closeHighlightNode()
}
this.emitEvent()
}
@@ -73,7 +76,7 @@ class Search {
this.matchNodeList = []
this.currentIndex = -1
bfsWalk(this.mindMap.renderer.root, node => {
let { richText, text } = node.nodeData.data
let { richText, text } = node.getData()
if (richText) {
text = getTextFromHtml(text)
}
@@ -96,6 +99,10 @@ class Search {
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
this.notResetSearchText = false
callback()
// 只读模式下节点无法激活,所以通过高亮的方式
if (this.mindMap.opt.readonly) {
this.mindMap.renderer.highlightNode(currentNode)
}
})
}
@@ -115,7 +122,7 @@ class Search {
if (!currentNode) return
let text = this.getReplacedText(currentNode, this.searchText, replaceText)
this.notResetSearchText = true
currentNode.setText(text, currentNode.nodeData.data.richText, true)
currentNode.setText(text, currentNode.getData('richText'), true)
this.matchNodeList = this.matchNodeList.filter(node => {
return currentNode !== node
})
@@ -143,7 +150,7 @@ class Search {
node,
{
text,
resetRichText: !!node.nodeData.data.richText
resetRichText: !!node.getData('richText')
},
true
)
@@ -155,7 +162,7 @@ class Search {
// 获取某个节点替换后的文本
getReplacedText(node, searchText, replaceText) {
let { richText, text } = node.nodeData.data
let { richText, text } = node.getData()
if (richText) {
return replaceHtmlText(text, searchText, replaceText)
} else {

View File

@@ -124,7 +124,7 @@ class Select {
let cur = this.cacheActiveList[i]
if (
!this.mindMap.renderer.activeNodeList.find(item => {
return item.nodeData.data.uid === cur.nodeData.data.uid
return item.getData('uid') === cur.getData('uid')
})
) {
isNodeChange = true
@@ -133,9 +133,7 @@ class Select {
}
}
if (isNumChange || isNodeChange) {
this.mindMap.emit('node_active', null, [
...this.mindMap.renderer.activeNodeList
])
this.mindMap.renderer.emitNodeActiveEvent()
}
}
@@ -218,17 +216,15 @@ class Select {
if (
checkTwoRectIsOverlap(minx, maxx, miny, maxy, left, right, top, bottom)
) {
if (node.nodeData.data.isActive) {
if (node.getData('isActive')) {
return
}
this.mindMap.renderer.setNodeActive(node, true)
this.mindMap.renderer.addActiveNode(node)
} else if (node.nodeData.data.isActive) {
if (!node.nodeData.data.isActive) {
this.mindMap.renderer.addNodeToActiveList(node)
} else if (node.getData('isActive')) {
if (!node.getData('isActive')) {
return
}
this.mindMap.renderer.setNodeActive(node, false)
this.mindMap.renderer.removeActiveNode(node)
this.mindMap.renderer.removeNodeFromActiveList(node)
}
})
}

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

@@ -11,13 +11,48 @@ class Watermark {
this.angle = 0 // 旋转角度
this.text = '' // 水印文字
this.textStyle = {} // 水印文字样式
this.watermarkDraw = null // 容器
this.maxLong = this.getMaxLong()
this.updateWatermark(this.mindMap.opt.watermarkConfig || {})
this.bindEvent()
}
getMaxLong() {
return Math.sqrt(
Math.pow(this.mindMap.width, 2) + Math.pow(this.mindMap.height, 2)
)
}
bindEvent() {
this.onResize = this.onResize.bind(this)
this.mindMap.on('resize', this.onResize)
}
unBindEvent() {
this.mindMap.off('resize', this.onResize)
}
onResize() {
this.maxLong = this.getMaxLong()
this.draw()
}
// 创建水印容器
createContainer() {
if (this.watermarkDraw) return
this.watermarkDraw = this.mindMap.svg
.group()
.css({ 'pointer-events': 'none', 'user-select': 'none' })
this.maxLong = Math.sqrt(
Math.pow(this.mindMap.width, 2) + Math.pow(this.mindMap.height, 2)
)
this.updateWatermark(this.mindMap.opt.watermarkConfig || {})
.addClass('smm-water-mark-container')
}
// 删除水印容器
removeContainer() {
if (!this.watermarkDraw) {
return
}
this.watermarkDraw.remove()
this.watermarkDraw = null
}
// 获取是否存在水印
@@ -40,10 +75,14 @@ class Watermark {
// 绘制水印
// 非精确绘制,会绘制一些超出可视区域的水印
draw() {
this.watermarkDraw.clear()
// 清空之前的水印
if (this.watermarkDraw) this.watermarkDraw.clear()
// 如果没有水印数据,那么水印容器也删除掉
if (!this.hasWatermark()) {
this.removeContainer()
return
}
this.createContainer()
let x = 0
while (x < this.mindMap.width) {
this.drawText(x)
@@ -116,6 +155,16 @@ class Watermark {
this.handleConfig(config)
this.draw()
}
// 插件被移除前做的事情
beforePluginRemove() {
this.unBindEvent()
}
// 插件被卸载前做的事情
beforePluginDestroy() {
this.unBindEvent()
}
}
Watermark.instanceName = 'watermark'

View File

@@ -9,10 +9,10 @@ import {
function createControlNodes() {
let { associativeLineActiveColor } = this.mindMap.themeConfig
// 连线
this.controlLine1 = this.draw
this.controlLine1 = this.associativeLineDraw
.line()
.stroke({ color: associativeLineActiveColor, width: 2 })
this.controlLine2 = this.draw
this.controlLine2 = this.associativeLineDraw
.line()
.stroke({ color: associativeLineActiveColor, width: 2 })
// 控制点
@@ -23,7 +23,7 @@ function createControlNodes() {
// 创建控制点
function createOneControlNode(pointKey) {
let { associativeLineActiveColor } = this.mindMap.themeConfig
return this.draw
return this.associativeLineDraw
.circle(this.controlPointDiameter)
.stroke({ color: associativeLineActiveColor })
.fill({ color: '#fff' })
@@ -64,7 +64,7 @@ function onControlPointMousemove(e) {
let [, , , node, toNode] = this.activeLine
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
let { associativeLinePoint, associativeLineTargetControlOffsets } =
node.nodeData.data
node.getData()
associativeLinePoint = associativeLinePoint || []
const nodePos = this.getNodePos(node)
const toNodePos = this.getNodePos(toNode)
@@ -160,7 +160,7 @@ function onControlPointMouseup(e) {
let [, , , node] = this.activeLine
let offsetList = []
let { associativeLinePoint, associativeLineTargetControlOffsets } =
node.nodeData.data
node.getData()
if (!associativeLinePoint) {
associativeLinePoint = []
}

View File

@@ -7,7 +7,7 @@ import {
// 创建文字节点
function createText(data) {
let g = this.draw.group()
let g = this.associativeLineDraw.group()
const setActive = () => {
if (
!this.activeLine ||
@@ -110,8 +110,8 @@ function hideEditTextBox() {
str = isDefaultText ? '' : str
this.mindMap.execCommand('SET_NODE_DATA', node, {
associativeLineText: {
...(node.nodeData.data.associativeLineText || {}),
[toNode.nodeData.data.uid]: str
...(node.getData('associativeLineText') || {}),
[toNode.getData('uid')]: str
}
})
this.textEditNode.style.display = 'none'
@@ -123,11 +123,11 @@ function hideEditTextBox() {
// 获取某根关联线的文字
function getText(node, toNode) {
let obj = node.nodeData.data.associativeLineText
let obj = node.getData('associativeLineText')
if (!obj) {
return ''
}
return obj[toNode.nodeData.data.uid] || ''
return obj[toNode.getData('uid')] || ''
}
// 渲染关联线文字

View File

@@ -1,7 +1,7 @@
// 获取目标节点在起始节点的目标数组中的索引
export const getAssociativeLineTargetIndex = (node, toNode) => {
return node.nodeData.data.associativeLineTargets.findIndex(item => {
return item === toNode.nodeData.data.uid
return node.getData('associativeLineTargets').findIndex(item => {
return item === toNode.getData('uid')
})
}
@@ -202,7 +202,7 @@ export const computeNodePoints = (fromNode, toNode) => {
// 中心点坐标的差值
let offsetX = toCx - fromCx
let offsetY = toCy - fromCy
if (offsetX === 0 && offsetY === 0) return
if (offsetX === 0 && offsetY === 0) return []
let fromDir = ''
let toDir = ''
if (offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY) {
@@ -231,7 +231,7 @@ export const getNodeLinePath = (startPoint, endPoint, node, toNode) => {
// 控制点
let controlPoints = []
let associativeLineTargetControlOffsets =
node.nodeData.data.associativeLineTargetControlOffsets
node.getData('associativeLineTargetControlOffsets')
if (
associativeLineTargetControlOffsets &&
associativeLineTargetControlOffsets[targetIndex]

View File

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

View File

@@ -1,5 +1,8 @@
import { v4 as uuidv4 } from 'uuid'
import { nodeDataNoStylePropList } from '../constants/constant'
import {
nodeDataNoStylePropList,
selfCloseTagList
} from '../constants/constant'
import MersenneTwister from './mersenneTwister'
// 深度优先遍历树
export const walk = (
@@ -152,6 +155,9 @@ export const copyRenderTree = (tree, root, removeActiveState = false) => {
tree.data = simpleDeepClone(root.data)
if (removeActiveState) {
tree.data.isActive = false
if (tree.data.generalization) {
tree.data.generalization.isActive = false
}
}
tree.children = []
if (root.children && root.children.length > 0) {
@@ -173,7 +179,8 @@ export const copyNodeTree = (
// 移除节点uid
if (removeId) {
delete tree.data.uid
} else if (!tree.data.uid) {// 否则保留或生成
} else if (!tree.data.uid) {
// 否则保留或生成
tree.data.uid = createUid()
}
if (removeActiveState) {
@@ -625,7 +632,7 @@ export const textToNodeRichTextWithWrap = html => {
}
return list
.map(item => {
return `<p><span>${item}</span></p>`
return `<p><span>${htmlEscape(item)}</span></p>`
})
.join('')
}
@@ -709,7 +716,7 @@ export const getTopAncestorsFomNodeList = list => {
list.forEach(node => {
if (
!list.find(item => {
return item.uid !== node.uid && item.isParent(node)
return item.uid !== node.uid && item.isAncestor(node)
})
) {
res.push(node)
@@ -718,6 +725,71 @@ export const getTopAncestorsFomNodeList = list => {
return res
}
// 从给定的节点实例列表里判断是否存在上下级关系
export const checkHasSupSubRelation = list => {
for (let i = 0; i < list.length; i++) {
const cur = list[i]
if (
list.find(item => {
return item.uid !== cur.uid && cur.isAncestor(item)
})
) {
return true
}
}
return false
}
// 解析要添加概要的节点实例列表
export const parseAddGeneralizationNodeList = list => {
const cache = {}
const uidToParent = {}
list.forEach(node => {
const parent = node.parent
if (parent) {
const pUid = parent.uid
uidToParent[pUid] = parent
const index = node.getIndexInBrothers()
const data = {
node,
index
}
if (cache[pUid]) {
if (
!cache[pUid].find(item => {
return item.index === data.index
})
) {
cache[pUid].push(data)
}
} else {
cache[pUid] = [data]
}
}
})
const res = []
Object.keys(cache).forEach(uid => {
if (cache[uid].length > 1) {
const rangeList = cache[uid]
.map(item => {
return item.index
})
.sort((a, b) => {
return a - b
})
res.push({
node: uidToParent[uid],
range: [rangeList[0], rangeList[rangeList.length - 1]]
})
} else {
res.push({
node: cache[uid][0].node
})
}
})
return res
}
// 判断两个矩形是否重叠
export const checkTwoRectIsOverlap = (
minx1,
@@ -795,14 +867,21 @@ export const formatDataToArray = data => {
}
// 获取节点在同级里的位置索引
export const getNodeIndex = node => {
export const getNodeDataIndex = node => {
return node.parent
? node.parent.children.findIndex(item => {
return item.uid === node.uid
? node.parent.nodeData.children.findIndex(item => {
return item.data.uid === node.uid
})
: 0
}
// 从一个节点列表里找出某个节点的索引
export const getNodeIndexInNodeList = (node, nodeList) => {
return nodeList.findIndex(item => {
return item.uid === node.uid
})
}
// 根据内容生成颜色
export const generateColorByContent = str => {
let hash = 0
@@ -874,3 +953,68 @@ export const isSameObject = (a, b) => {
return a === b
}
}
// 将数据设置到用户剪切板中
export const setDataToClipboard = data => {
if (navigator.clipboard) {
navigator.clipboard.writeText(JSON.stringify(data))
}
}
// 从用户剪贴板中读取文字和图片
export const getDataFromClipboard = async () => {
let text = null
let img = null
if (navigator.clipboard) {
text = await navigator.clipboard.readText()
const items = await navigator.clipboard.read()
if (items && items.length > 0) {
for (const clipboardItem of items) {
for (const type of clipboardItem.types) {
if (/^image\//.test(type)) {
img = await clipboardItem.getType(type)
break
}
}
}
}
}
return {
text,
img
}
}
// 从节点的父节点的nodeData.children列表中移除该节点的数据
export const removeFromParentNodeData = node => {
if (!node || !node.parent) return
const index = getNodeDataIndex(node)
if (index === -1) return
node.parent.nodeData.children.splice(index, 1)
}
// 给html自闭合标签添加闭合状态
export const handleSelfCloseTags = str => {
selfCloseTagList.forEach(tagName => {
str = str.replaceAll(
new RegExp(`<${tagName}([^>]*)>`, 'g'),
`<${tagName} $1 />`
)
})
return str
}
// 检查两个节点列表是否包含的节点是一样的
export const checkNodeListIsEqual = (list1, list2) => {
if (list1.length !== list2.length) return false
for (let i = 0; i < list1.length; i++) {
if (
!list2.find(item => {
return item.uid === list1[i].uid
})
) {
return false
}
}
return true
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -74,9 +74,10 @@ declare class MindMap {
richTextEditFakeInPlace: boolean;
customHandleClipboardText: any;
disableMouseWheelZoom: boolean;
disableTouchZoom: boolean;
errorHandler: (code: any, error: any) => void;
resetCss: string;
enableDblclickReset: boolean;
enableDblclickBackToRootNode: boolean;
minExportImgCanvasScale: number;
hoverRectColor: string;
hoverRectPadding: number;
@@ -99,15 +100,20 @@ declare class MindMap {
avatarSize: number;
fontSize: number;
};
associativeLineIsAlwaysAboveNode: boolean;
defaultGeneralizationText: string;
handleIsSplitByWrapOnPasteCreateNewNode: any;
addHistoryTime: number;
isDisableDrag: boolean;
highlightNodeBoxStyle: {
stroke: string;
fill: string;
};
createNewNodeBehavior: string;
});
opt: any;
el: any;
elRect: any;
width: any;
height: any;
cssEl: HTMLStyleElement;
svg: any;
draw: any;
event: Event;
keyCommand: KeyCommand;
command: Command;
@@ -115,10 +121,22 @@ declare class MindMap {
view: View;
batchExecution: BatchExecution;
handleOpt(opt: any): any;
initContainer(): void;
associativeLineDraw: any;
svg: any;
draw: any;
lineDraw: any;
nodeDraw: any;
otherDraw: any;
clearDraw(): void;
addCss(): void;
removeCss(): void;
render(callback: any, source?: string): void;
reRender(callback: any, source?: string): void;
getElRectInfo(): void;
elRect: any;
width: any;
height: any;
resize(): void;
on(event: any, fn: any): void;
emit(event: any, ...args: any[]): void;
@@ -126,15 +144,15 @@ declare class MindMap {
initCache(): void;
initTheme(): void;
themeConfig: any;
setTheme(theme: any): void;
setTheme(theme: any, notRender?: boolean): void;
getTheme(): any;
setThemeConfig(config: any): void;
setThemeConfig(config: any, notRender?: boolean): void;
getCustomThemeConfig(): any;
getThemeConfig(prop: any): any;
getConfig(prop: any): any;
updateConfig(opt?: {}): void;
getLayout(): any;
setLayout(layout: any): void;
setLayout(layout: any, notRender?: boolean): void;
execCommand(...args: any[]): void;
setData(data: any): void;
setFullData(data: any): void;
@@ -145,9 +163,10 @@ declare class MindMap {
y: number;
};
setMode(mode: any): void;
getSvgData({ paddingX, paddingY }?: {
getSvgData({ paddingX, paddingY, ignoreWatermark }?: {
paddingX?: number;
paddingY?: number;
ignoreWatermark?: boolean;
}): {
svg: any;
svgHTML: any;

View File

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

View File

@@ -70,9 +70,10 @@ export namespace defaultOpt {
const richTextEditFakeInPlace: boolean;
const customHandleClipboardText: any;
const disableMouseWheelZoom: boolean;
const disableTouchZoom: boolean;
function errorHandler(code: any, error: any): void;
const resetCss: string;
const enableDblclickReset: boolean;
const enableDblclickBackToRootNode: boolean;
const minExportImgCanvasScale: number;
const hoverRectColor: string;
const hoverRectPadding: number;
@@ -97,4 +98,15 @@ export namespace defaultOpt {
const fontSize_2: number;
export { fontSize_2 as fontSize };
}
const associativeLineIsAlwaysAboveNode: boolean;
const defaultGeneralizationText: string;
const handleIsSplitByWrapOnPasteCreateNewNode: any;
const addHistoryTime: number;
const isDisableDrag: boolean;
namespace highlightNodeBoxStyle {
export const stroke: string;
const fill_2: string;
export { fill_2 as fill };
}
const createNewNodeBehavior: string;
}

View File

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

View File

@@ -4,11 +4,11 @@ declare class Render {
opt: {};
mindMap: any;
themeConfig: any;
draw: any;
renderTree: any;
reRender: boolean;
isRendering: boolean;
hasWaitRendering: boolean;
waitRenderingParams: any[];
nodeCache: {};
lastNodeCache: {};
renderSource: string;
@@ -20,6 +20,9 @@ declare class Render {
beingPasteText: string;
beingPasteImgSize: number;
currentBeingPasteType: string;
highlightBoxNode: any;
lastActiveNode: any;
lastActiveNodeList: any[];
setLayout(): void;
layout: MindMap | CatalogOrganization | OrganizationStructure | Timeline | VerticalTimeline;
setData(data: any): void;
@@ -32,18 +35,20 @@ declare class Render {
insertMultiNode(appointNodes: any, nodeList: any): void;
insertChildNode(openEdit?: boolean, appointNodes?: any[], appointData?: any, appointChildren?: any[]): void;
insertMultiChildNode(appointNodes: any, childList: any): void;
insertParentNode(openEdit: boolean, appointNodes: any, appointData: any): void;
upNode(): void;
downNode(): void;
insertAfter(node: any, exist: any): void;
insertBefore(node: any, exist: any): void;
moveNodeTo(node: any, toNode: any): void;
removeNode(appointNodes?: any[]): void;
removeCurrentNode(appointNodes?: any[]): void;
pasteNode(data: any): void;
cutNode(callback: any): void;
setNodeStyle(node: any, prop: any, value: any): void;
setNodeStyles(node: any, style: any): void;
setNodeActive(node: any, active: any): void;
clearAllActive(): void;
clearActiveNode(): void;
setNodeExpand(node: any, expand: any): void;
expandAllNode(): void;
unexpandAllNode(): void;
@@ -63,30 +68,38 @@ declare class Render {
setNodeShape(node: any, shape: any): void;
goTargetNode(node: any, callback?: () => void): void;
registerShortcutKeys(): void;
insertNodeWrap: () => void;
toggleActiveExpand(): void;
removeNodeWrap: () => void;
copy(): void;
cut(): void;
emitNodeActiveEvent(node?: any, activeNodeList?: any[]): void;
clearActiveNodeListOnDrawClick(e: any, eventType: any): void;
startTextEdit(): void;
endTextEdit(): void;
render(callback: () => void, source: any): void;
clearActive(): void;
addActiveNode(node: any): void;
removeActiveNode(node: any): void;
findActiveNodeIndex(node: any): number;
setCopyDataToClipboard(data: any): void;
clearActiveNodeList(): void;
addNodeToActiveList(node: any): void;
removeNodeFromActiveList(node: any): void;
findActiveNodeIndex(node: any): any;
backForward(type: any, step: any): void;
getNewNodeBehavior(openEdit?: boolean, handleMultiNodes?: boolean): {
focusNewNode: boolean;
inserting: boolean;
};
copy(): void;
cut(): void;
paste(): void;
onPaste(): Promise<void>;
insertTo(node: any, exist: any, dir?: string): void;
checkNodeLayerChange(node: any, toNode: any): void;
removeOneNode(node: any): void;
deleteNodeGeneralization(node: any): void;
getNextActiveNode(): any;
copyNode(): any;
toggleNodeExpand(node: any): void;
setNodeDataRender(node: any, data: any, notRender?: boolean): void;
moveNodeToCenter(node: any): void;
setRootNodeCenter(): void;
expandToNodeUid(uid: any, callback?: () => void): void;
findNodeByUid(uid: any): any;
highlightNode(node: any, range: any): void;
closeHighlightNode(): void;
}
import TextEdit from "./TextEdit";
import MindMap from "../../layouts/MindMap";

View File

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

View File

@@ -6,6 +6,8 @@ declare class Node {
mindMap: any;
renderer: any;
draw: any;
nodeDraw: any;
lineDraw: any;
style: Style;
shapeInstance: Shape;
shapePadding: {
@@ -37,6 +39,7 @@ declare class Node {
_tagData: any;
_noteData: any;
noteEl: any;
noteContentIsShow: boolean;
_expandBtn: any;
_lastExpandBtnType: any;
_showExpandBtn: boolean;
@@ -45,8 +48,7 @@ declare class Node {
_fillExpandNode: any;
_userListGroup: any;
_lines: any[];
_generalizationLine: any;
_generalizationNode: any;
_generalizationList: any[];
_unVisibleRectRegionNode: any;
_isMouseenter: boolean;
_rectInfo: {
@@ -84,7 +86,8 @@ declare class Node {
top: any;
};
reRender(): boolean;
updateNodeActive(): void;
updateNodeActiveClass(): void;
updateNodeByActive(active: any): void;
render(callback?: () => void): void;
remove(): void;
destroy(): void;
@@ -99,11 +102,14 @@ declare class Node {
getShape(): any;
hasCustomPosition(): boolean;
ancestorHasCustomPosition(): boolean;
ancestorHasGeneralization(): boolean;
addChildren(node: any): void;
styleLine(line: any, node: any): void;
removeLine(): void;
isAncestor(node: any): boolean;
isParent(node: any): boolean;
isBrother(node: any): any;
getIndexInBrothers(): any;
getPaddingVale(): {
paddingX: any;
paddingY: any;
@@ -114,7 +120,17 @@ declare class Node {
getSelfInhertStyle(prop: any): any;
getBorderWidth(): any;
getData(key: any): any;
getPureData(removeActiveState?: boolean, removeId?: boolean): any;
hasCustomStyle(): boolean;
getRect(): any;
getRectInSvg(): {
left: any;
right: any;
top: any;
bottom: any;
width: number;
height: number;
};
}
import Style from "./Style";
import Shape from "./Shape";

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ declare class Base {
renderer: any;
mindMap: any;
draw: any;
lineDraw: any;
root: any;
lru: Lru;
doLayout(): void;
@@ -38,6 +39,22 @@ declare class Base {
generalizationLineMargin: any;
generalizationNodeMargin: any;
};
getChildrenBoundaries(node: any, dir: any, startIndex: number, endIndex: any): {
left: number;
right: number;
top: number;
bottom: number;
generalizationLineMargin: any;
generalizationNodeMargin: any;
};
getNodeGeneralizationRenderBoundaries(item: any, dir: any): {
left: any;
right: any;
top: any;
bottom: any;
generalizationLineMargin: any;
generalizationNodeMargin: any;
};
getNodeActChildrenLength(node: any): any;
}
import Lru from "../utils/Lru";

View File

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

View File

@@ -58,13 +58,24 @@ export function getObjectChangedProps(oldObject: any, newObject: any): {};
export function checkIsNodeStyleDataKey(key: any): boolean;
export function mergerIconList(list: any): any;
export function getTopAncestorsFomNodeList(list: any): any[];
export function checkHasSupSubRelation(list: any): boolean;
export function parseAddGeneralizationNodeList(list: any): any[];
export function checkTwoRectIsOverlap(minx1: any, maxx1: any, miny1: any, maxy1: any, minx2: any, maxx2: any, miny2: any, maxy2: any): boolean;
export function focusInput(el: any): void;
export function selectAllInput(el: any): void;
export function addDataToAppointNodes(appointNodes: any, data?: {}): any;
export function createUidForAppointNodes(appointNodes: any, createNewId?: boolean): any;
export function formatDataToArray(data: any): any[];
export function getNodeIndex(node: any): any;
export function getNodeDataIndex(node: any): any;
export function getNodeIndexInNodeList(node: any, nodeList: any): any;
export function generateColorByContent(str: any): string;
export function htmlEscape(str: any): any;
export function isSameObject(a: any, b: any): boolean;
export function setDataToClipboard(data: any): void;
export function getDataFromClipboard(): Promise<{
text: string;
img: any;
}>;
export function removeFromParentNodeData(node: any): void;
export function handleSelfCloseTags(str: any): any;
export function checkNodeListIsEqual(list1: any, list2: any): boolean;

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1695365666344') format('woff2'),
url('iconfont.woff?t=1695365666344') format('woff'),
url('iconfont.ttf?t=1695365666344') format('truetype');
src: url('iconfont.woff2?t=1697073602349') format('woff2'),
url('iconfont.woff?t=1697073602349') format('woff'),
url('iconfont.ttf?t=1697073602349') format('truetype');
}
.iconfont {
@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icondodeparent:before {
content: "\e70f";
}
.icongongshi:before {
content: "\e617";
}

View File

@@ -209,6 +209,11 @@ export const shortcutKeyList = [
name: 'Insert sibling node',
value: 'Enter'
},
{
icon: 'icondodeparent',
name: 'Insert parent node',
value: 'Shift + Tab'
},
{
icon: 'iconshangyi',
name: 'Move up node',
@@ -234,6 +239,11 @@ export const shortcutKeyList = [
name: 'Delete node',
value: 'Delete | Backspace'
},
{
icon: 'iconshanchu',
name: 'Delete current node',
value: 'Shift + Backspace'
},
{
icon: 'iconfuzhi',
name: 'Copy node',
@@ -304,9 +314,14 @@ export const shortcutKeyList = [
name: 'Zoom out',
value: 'Ctrl + -'
},
{
icon: 'iconfangda',
name: 'Zoom in/Zoom out',
value: 'Ctrl + Mouse wheel'
},
{
icon: 'icondingwei',
name: 'Reset',
name: 'Back root node',
value: 'Ctrl + Enter'
},
{

View File

@@ -276,6 +276,11 @@ export const shortcutKeyList = [
name: '插入同级节点',
value: 'Enter'
},
{
icon: 'icondodeparent',
name: '插入父节点',
value: 'Shift + Tab'
},
{
icon: 'iconshangyi',
name: '上移节点',
@@ -301,6 +306,11 @@ export const shortcutKeyList = [
name: '删除节点',
value: 'Delete | Backspace'
},
{
icon: 'iconshanchu',
name: '仅删除当前节点',
value: 'Shift + Backspace'
},
{
icon: 'iconfuzhi',
name: '复制节点',
@@ -371,9 +381,14 @@ export const shortcutKeyList = [
name: '缩小',
value: 'Ctrl + -'
},
{
icon: 'iconfangda',
name: '放大/缩小',
value: 'Ctrl + 鼠标滚动'
},
{
icon: 'icondingwei',
name: '恢复默认',
name: '回到根节点',
value: 'Ctrl + Enter'
},
{

View File

@@ -47,6 +47,10 @@ export default {
mousewheelZoomActionReverse: 'Mouse Wheel Zoom',
mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back',
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back',
createNewNodeBehavior: 'Behavior of creating new node',
default: 'Active new node and editing',
notActive: 'Not active new node',
activeOnly: 'Only active new node but not editing',
rootStyle: 'Root Node',
associativeLineText: 'Associative line text',
fontFamily: 'Font family',
@@ -59,14 +63,16 @@ export default {
contextmenu: {
insertSiblingNode: 'Insert sibling node',
insertChildNode: 'Insert child node',
insertParentNode: 'Insert parent node',
insertSummary: 'Insert summary',
moveUpNode: 'Move up node',
moveDownNode: 'Move down node',
deleteNode: 'Delete node',
deleteCurrentNode: 'Only del cur node',
copyNode: 'Copy node',
cutNode: 'Cut node',
pasteNode: 'Paste node',
backCenter: 'Back center',
backCenter: 'Back root node',
expandAll: 'Expand all',
unExpandAll: 'Un expand all',
expandTo: 'Expand to',
@@ -112,7 +118,8 @@ export default {
'If the download is not triggered, check whether it is blocked by the browser',
paddingX: 'Padding x',
paddingY: 'Padding y',
useMultiPageExport: 'Export multi page'
useMultiPageExport: 'Export multi page',
defaultFileName: 'Mind map'
},
fullscreen: {
fullscreenShow: 'Full screen show',
@@ -121,7 +128,13 @@ export default {
import: {
title: 'Import',
selectFile: 'Select file',
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file'
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file',
enableFileTip: 'Please select .smm、.json、.xmind、.xlsx、.md file',
maxFileNum: 'At most one file can be selected',
notSelectTip: 'Please select the file to import',
fileContentError: 'The file content is incorrect',
importSuccess: 'Import success',
fileParsingFailed: 'File parsing failed'
},
navigatorToolbar: {
openMiniMap: 'Open mini map',
@@ -190,13 +203,21 @@ export default {
vertical: 'Vertical'
},
theme: {
title: 'Theme'
title: 'Theme',
classics: 'Classics',
dark: 'Darkness',
simple: 'Simple',
coverTip:
'You have currently customized the basic style, do you want to overwrite it?',
tip: 'Tip',
cover: 'Cover',
reserve: 'Reserve'
},
toolbar: {
undo: 'Undo',
redo: 'Redo',
insertSiblingNode: 'Insert sibling node',
insertChildNode: 'Insert child node',
insertSiblingNode: 'Sibling node',
insertChildNode: 'Child node',
deleteNode: 'Delete node',
image: 'Image',
icon: 'Icon',
@@ -216,12 +237,28 @@ export default {
shortcutKey: 'Shortcut key',
associativeLine: 'Associative line',
painter: 'Painter',
formula: 'Formula'
formula: 'Formula',
more: 'More',
selectFileTip: 'Please select a file',
notSupportTip:
'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',
fileContentError: 'File content error',
fileOpenFailed: 'File open failed',
defaultFileName: 'Mind map',
creatingTip: 'Creating file'
},
edit: {
newFeatureNoticeTitle: 'New feature reminder',
newFeatureNoticeMessage:
'This update supports node rich text editing, But there are some defects, The most important impact is that the time to export the image is proportional to the number of nodes, Therefore, if you are more dependent on export requirements, you can use【Base style】-【Other config】-【Enable node rich text editing】Set to turn off rich text editing mode.'
'This update supports node rich text editing, But there are some defects, The most important impact is that the time to export the image is proportional to the number of nodes, Therefore, if you are more dependent on export requirements, you can use【Base style】-【Other config】-【Enable node rich text editing】Set to turn off rich text editing mode.',
root: 'Root node',
splitByWrap: 'Is automatically split nodes based on line breaks?',
tip: 'Tip',
yes: 'Yes',
no: 'No'
},
mouseAction: {
tip1:
@@ -243,8 +280,23 @@ export default {
},
formulaSidebar: {
title: 'Formula',
placeholder: 'Please enter LaText syntax',
placeholder: 'Please enter LaTeX syntax',
confirm: 'Confirm',
common: 'Common formulas'
common: 'Common formulas',
tip: 'Inserting formulas is not supported in non rich text mode'
},
richTextToolbar: {
bold: 'Bold',
italic: 'Italic',
underline: 'Underline',
strike: 'Strike',
fontFamily: 'Font family',
fontSize: 'Font size',
color: 'Color',
backgroundColor: 'Background color',
removeFormat: 'Clear Style'
},
other: {
loading: 'Loading, please wait...'
}
}

View File

@@ -47,6 +47,10 @@ export default {
mousewheelZoomActionReverse: '鼠标滚轮缩放',
mousewheelZoomActionReverse1: '向前缩小向后放大',
mousewheelZoomActionReverse2: '向前放大向后缩小',
createNewNodeBehavior: '创建新节点的行为',
default: '激活新节点及进入编辑',
notActive: '不激活新节点',
activeOnly: '只激活新节点,不进入编辑',
rootStyle: '根节点',
associativeLineText: '关联线文字',
fontFamily: '字体',
@@ -59,14 +63,16 @@ export default {
contextmenu: {
insertSiblingNode: '插入同级节点',
insertChildNode: '插入子级节点',
insertParentNode: '插入父节点',
insertSummary: '插入概要',
moveUpNode: '上移节点',
moveDownNode: '下移节点',
deleteNode: '删除节点',
deleteCurrentNode: '仅删除当前节点',
copyNode: '复制节点',
cutNode: '剪切节点',
pasteNode: '粘贴节点',
backCenter: '回到中心',
backCenter: '回到根节点',
expandAll: '展开所有',
unExpandAll: '收起所有',
expandTo: '展开到',
@@ -110,7 +116,8 @@ export default {
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了',
paddingX: '水平内边距',
paddingY: '垂直内边距',
useMultiPageExport: '是否多页导出'
useMultiPageExport: '是否多页导出',
defaultFileName: '思维导图'
},
fullscreen: {
fullscreenShow: '全屏查看',
@@ -119,7 +126,13 @@ export default {
import: {
title: '导入',
selectFile: '选取文件',
supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件'
supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件',
enableFileTip: '请选择.smm、.json、.xmind、.xlsx、.md文件',
maxFileNum: '最多只能选择一个文件',
notSelectTip: '请选择要导入的文件',
fileContentError: '文件内容有误',
importSuccess: '导入成功',
fileParsingFailed: '文件解析失败'
},
navigatorToolbar: {
openMiniMap: '开启小地图',
@@ -188,13 +201,20 @@ export default {
vertical: '垂直'
},
theme: {
title: '主题'
title: '主题',
classics: '经典',
dark: '深色',
simple: '朴素',
coverTip: '你当前自定义过基础样式,是否覆盖?',
tip: '提示',
cover: '覆盖',
reserve: '保留'
},
toolbar: {
undo: '回退',
redo: '前进',
insertSiblingNode: '插入同级节点',
insertChildNode: '插入子节点',
insertSiblingNode: '同级节点',
insertChildNode: '子节点',
deleteNode: '删除节点',
image: '图片',
icon: '图标',
@@ -214,12 +234,27 @@ export default {
shortcutKey: '快捷键',
associativeLine: '关联线',
painter: '格式刷',
formula: '公式'
formula: '公式',
more: '更多',
selectFileTip: '请选择文件',
notSupportTip: '你的浏览器不支持该功能或者当前页面非https协议',
tip: '提示',
editingLocalFileTipFront: '当前正在编辑你本机的【',
editingLocalFileTipEnd: '】文件',
fileContentError: '文件内容有误',
fileOpenFailed: '文件打开失败',
defaultFileName: '思维导图',
creatingTip: '正在创建文件'
},
edit: {
newFeatureNoticeTitle: '新特性提醒',
newFeatureNoticeMessage:
'本次更新支持了节点富文本编辑,但是存在一定缺陷,最主要的影响是导出为图片的时间和节点数量成正比,所以对导出需求比较依赖的话可以通过【基础样式】-【其他配置】-【是否开启节点富文本编辑】设置关掉富文本编辑模式。'
'本次更新支持了节点富文本编辑,但是存在一定缺陷,最主要的影响是导出为图片的时间和节点数量成正比,所以对导出需求比较依赖的话可以通过【基础样式】-【其他配置】-【是否开启节点富文本编辑】设置关掉富文本编辑模式。',
root: '根节点',
splitByWrap: '是否按换行自动分割节点?',
tip: '提示',
yes: '是',
no: '否'
},
mouseAction: {
tip1: '当前:左键拖动画布,右键框选节点',
@@ -239,8 +274,23 @@ export default {
},
formulaSidebar: {
title: '公式',
placeholder: '请输入 LaText 语法',
placeholder: '请输入 LaTeX 语法',
confirm: '完成',
common: '常用公式'
common: '常用公式',
tip: '非富文本模式下不支持插入公式'
},
richTextToolbar: {
bold: '加粗',
italic: '斜体',
underline: '下划线',
strike: '删除线',
fontFamily: '字体',
fontSize: '字号',
color: '字体颜色',
backgroundColor: '背景颜色',
removeFormat: '清除样式'
},
other: {
loading: '正在加载,请稍后...'
}
}

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

@@ -88,3 +88,15 @@ Deletes the currently active associative line. Clicking on an associated line is
### clearActiveLine()
Clears the active state of the currently active association line.
### front()
> v0.8.0+
The top-level display of the associated line.
### back()
> v0.8.0+
The associated line returns to its original level.

View File

@@ -66,6 +66,16 @@ MindMap.usePlugin(AssociativeLine)
<p>Deletes the currently active associative line. Clicking on an associated line is considered active.</p>
<h3>clearActiveLine()</h3>
<p>Clears the active state of the currently active association line.</p>
<h3>front()</h3>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>The top-level display of the associated line.</p>
<h3>back()</h3>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>The associated line returns to its original level.</p>
</div>
</template>

View File

@@ -1,5 +1,150 @@
# Changelog
## 0.9.1
Fix:
> 1.Fix the issue of exporting images, SVGs, and PDFs with errors when customizing node content.
>
> 2.Optimize the distribution of node activation events, do not distribute events when the activation node has not changed, and skip intermediate events when distributing multiple events in a short period of time.
>
> 3.Fix the issue where the edit box and node detach when scrolling the canvas with the mouse while the node is in editing mode.
>
> 4.Fix the issue of shortcut keys becoming invalid when zooming the canvas with the mouse wheel and then exiting node editing while in node editing mode.
>
> 5.Fix the issue where clicking on a node can also trigger node_dragend event.
>
> 6.Fix that clicking on the canvas and nodes while not in the format brush will also trigger the painter_end event.
>
> 7.Fixed the issue where the mind map text editing box was not destroyed during node text editing and associated line text editing.
New:
> 1.When holding down the Ctrl key, disable the node double-click event.
>
> 2.Support configuring the behavior when creating new nodes: focusing and entering editing, not focusing, only focusing.
>
> 3.When searching in read-only mode, add a highlight effect to the currently matched node.
>
> 4.The default behavior of the mouse scroll wheel is to move the canvas up and down; The default is to scroll forward to enlarge the canvas and zoom back.
>
> 5.When the mouse scroll wheel behavior is to move the canvas up and down, it supports holding down the Ctrl key to zoom in and out of the canvas.
Demo支持配置创建新节点时的行为。
## 0.9.0
New:
1.Support adding summaries to some child nodes of the same node.
2.Moving the mouse into the summary will highlight its node.
3.Importing and exporting xmind files supports processing profiles.
## 0.8.1
Fix
> 1.Fix the issue where the activation status of the summary node in the history data has not been deleted, which can cause data to be triggered when clicking on the summary node_ Change event.
>
> 2.Fix the issue of blank pages and exceptions thrown by the console when running in Safari browser.
>
> 3.Fixed the issue of icon floating layer and note floating layer detached from nodes when scaling the canvas.
>
> 4.Fixed the issue of selecting all nodes in read-only mode.
>
> 5.Fix the presence of node content has &nbsp; in rich text mode; Error exporting as image.
>
> 6.Fixed the issue of overlapping profiles when adding profiles to oneself first and then to subordinates; Fix the issue of overlapping profiles when adding profiles to nodes with hierarchical relationships at the same time.
>
> 7.Fix the issue of exporting PDF errors when there are many nodes.
New
> 1.Add a configuration option that prohibits dragging the canvas.
>
> 2.Add a configuration option to prohibit double finger scaling of the canvas.
>
> 3.Add compression parameters to the method of exporting PNG; Optimize the problem of excessive volume when exporting PDF from nodes with large amounts of data.
>
> 4.Rename the isParent method of the node instance to isAncestor and add the isParent method at the same time.
Demo:
> 1.Fixed the issue of being able to search for replacement and edit outlines in read-only mode.
>
> 2.Fix the issue where the outline cannot be displayed and edited when the node content is an HTML tag.
>
> 3.Fix the issue where when multiple nodes are selected and icons are added at the same time, all node icons will be unified as the icon of the first node.
>
> 4.Adding loading to the export operation.
## 0.8.0-fix.1
Fix: Fixed the issue of creating a new node using direct paste if the pasted content contains HTML label symbols such as <> and the newly created node content is empty.
## 0.8.0
Breaking change: Greatly optimize some of the code and slightly improve performance, mainly by using the 'render' class to remove useless logic, adjust unreasonable implementations, and extract duplicate code; Modify function names, functions, etc.
Fix:
> 1.Fix the issue of the arrow of the associated line disappearing when exporting images and SVGs.
>
> 2.Fix the issue of abnormal operation returning to the root node after resizing the container.
>
> 3.Fix that the shortcut key operations for inserting summary, moving up, down, and organizing layout with one click did not trigger data_ The issue with the change event.
>
> 4.Fix the issue of each node displaying a border when exporting images, SVGs, and PDFs with watermarks.
>
> 5.Fixed the issue of no watermarks and no redrawing after the container size was changed.
>
> 6.Fix the issue of slow rendering of mini maps with watermarks.
>
> 7.Fixed the issue where the collaboration plugin did not display the creator's avatar when creating a new node.
New:
> 1.Optimize the canvas DOM structure and render nodes, lines, and associated lines in layers.
>
> 2.Optimize the watermark plugin.
>
> 3.The setTheme, setThemeConfig, and setLayout functions add parameters that do not trigger re rendering.
>
> 4.Add a command to insert a parent node.
>
> 5.Add a command to only delete the current node.
>
> 6.Automatically expand child nodes when inserting a summary.
>
> 7.Clear the current active node when right-clicking on the canvas.
>
> 8.The folded active nodes are synchronously deleted from the list of active nodes.
>
> 9.Pasting text with line breaks supports controlling whether nodes are split by line breaks.
>
> 10.The mini map plugin supports returning mini maps of image types.
>
> 11.Only one historical record can be added within a specified time period to avoid adding unnecessary intermediate states.
Demo:
> 1.Modify the method and copy to return to the root node.
>
> 2.Fix the issue of ineffective first switching when switching themes in overlay mode.
>
> 3.The right-click menu adds the function of inserting parent nodes and deleting only the current node.
>
> 4.The top toolbar supports automatic folding into more according to the window width.
>
> 5.Support manual input of zoom factor.
>
> 6.Improve the English translation of the interface.
>
> 7.Change the mini map to render through images.
## 0.7.3-fix.2
Fix some issues with collaborative editing:

View File

@@ -1,6 +1,94 @@
<template>
<div>
<h1>Changelog</h1>
<h2>0.9.1</h2>
<p>Fix:</p>
<blockquote>
<p>1.Fix the issue of exporting images, SVGs, and PDFs with errors when customizing node content.</p>
<p>2.Optimize the distribution of node activation events, do not distribute events when the activation node has not changed, and skip intermediate events when distributing multiple events in a short period of time.</p>
<p>3.Fix the issue where the edit box and node detach when scrolling the canvas with the mouse while the node is in editing mode.</p>
<p>4.Fix the issue of shortcut keys becoming invalid when zooming the canvas with the mouse wheel and then exiting node editing while in node editing mode.</p>
<p>5.Fix the issue where clicking on a node can also trigger node_dragend event.</p>
<p>6.Fix that clicking on the canvas and nodes while not in the format brush will also trigger the painter_end event.</p>
<p>7.Fixed the issue where the mind map text editing box was not destroyed during node text editing and associated line text editing.</p>
</blockquote>
<p>New:</p>
<blockquote>
<p>1.When holding down the Ctrl key, disable the node double-click event.</p>
<p>2.Support configuring the behavior when creating new nodes: focusing and entering editing, not focusing, only focusing.</p>
<p>3.When searching in read-only mode, add a highlight effect to the currently matched node.</p>
<p>4.The default behavior of the mouse scroll wheel is to move the canvas up and down; The default is to scroll forward to enlarge the canvas and zoom back.</p>
<p>5.When the mouse scroll wheel behavior is to move the canvas up and down, it supports holding down the Ctrl key to zoom in and out of the canvas.</p>
</blockquote>
<p>Demo支持配置创建新节点时的行为</p>
<h2>0.9.0</h2>
<p>New:</p>
<p>1.Support adding summaries to some child nodes of the same node.</p>
<p>2.Moving the mouse into the summary will highlight its node.</p>
<p>3.Importing and exporting xmind files supports processing profiles.</p>
<h2>0.8.1</h2>
<p>Fix</p>
<blockquote>
<p>1.Fix the issue where the activation status of the summary node in the history data has not been deleted, which can cause data to be triggered when clicking on the summary node_ Change event.</p>
<p>2.Fix the issue of blank pages and exceptions thrown by the console when running in Safari browser.</p>
<p>3.Fixed the issue of icon floating layer and note floating layer detached from nodes when scaling the canvas.</p>
<p>4.Fixed the issue of selecting all nodes in read-only mode.</p>
<p>5.Fix the presence of node content has   in rich text mode; Error exporting as image.</p>
<p>6.Fixed the issue of overlapping profiles when adding profiles to oneself first and then to subordinates; Fix the issue of overlapping profiles when adding profiles to nodes with hierarchical relationships at the same time.</p>
<p>7.Fix the issue of exporting PDF errors when there are many nodes.</p>
</blockquote>
<p>New</p>
<blockquote>
<p>1.Add a configuration option that prohibits dragging the canvas.</p>
<p>2.Add a configuration option to prohibit double finger scaling of the canvas.</p>
<p>3.Add compression parameters to the method of exporting PNG; Optimize the problem of excessive volume when exporting PDF from nodes with large amounts of data.</p>
<p>4.Rename the isParent method of the node instance to isAncestor and add the isParent method at the same time.</p>
</blockquote>
<p>Demo:</p>
<blockquote>
<p>1.Fixed the issue of being able to search for replacement and edit outlines in read-only mode.</p>
<p>2.Fix the issue where the outline cannot be displayed and edited when the node content is an HTML tag.</p>
<p>3.Fix the issue where when multiple nodes are selected and icons are added at the same time, all node icons will be unified as the icon of the first node.</p>
<p>4.Adding loading to the export operation.</p>
</blockquote>
<h2>0.8.0-fix.1</h2>
<p>Fix: Fixed the issue of creating a new node using direct paste if the pasted content contains HTML label symbols such as &lt;&gt; and the newly created node content is empty.</p>
<h2>0.8.0</h2>
<p>Breaking change: Greatly optimize some of the code and slightly improve performance, mainly by using the 'render' class to remove useless logic, adjust unreasonable implementations, and extract duplicate code; Modify function names, functions, etc.</p>
<p>Fix:</p>
<blockquote>
<p>1.Fix the issue of the arrow of the associated line disappearing when exporting images and SVGs.</p>
<p>2.Fix the issue of abnormal operation returning to the root node after resizing the container.</p>
<p>3.Fix that the shortcut key operations for inserting summary, moving up, down, and organizing layout with one click did not trigger data_ The issue with the change event.</p>
<p>4.Fix the issue of each node displaying a border when exporting images, SVGs, and PDFs with watermarks.</p>
<p>5.Fixed the issue of no watermarks and no redrawing after the container size was changed.</p>
<p>6.Fix the issue of slow rendering of mini maps with watermarks.</p>
<p>7.Fixed the issue where the collaboration plugin did not display the creator's avatar when creating a new node.</p>
</blockquote>
<p>New:</p>
<blockquote>
<p>1.Optimize the canvas DOM structure and render nodes, lines, and associated lines in layers.</p>
<p>2.Optimize the watermark plugin.</p>
<p>3.The setTheme, setThemeConfig, and setLayout functions add parameters that do not trigger re rendering.</p>
<p>4.Add a command to insert a parent node.</p>
<p>5.Add a command to only delete the current node.</p>
<p>6.Automatically expand child nodes when inserting a summary.</p>
<p>7.Clear the current active node when right-clicking on the canvas.</p>
<p>8.The folded active nodes are synchronously deleted from the list of active nodes.</p>
<p>9.Pasting text with line breaks supports controlling whether nodes are split by line breaks.</p>
<p>10.The mini map plugin supports returning mini maps of image types.</p>
<p>11.Only one historical record can be added within a specified time period to avoid adding unnecessary intermediate states.</p>
</blockquote>
<p>Demo:</p>
<blockquote>
<p>1.Modify the method and copy to return to the root node.</p>
<p>2.Fix the issue of ineffective first switching when switching themes in overlay mode.</p>
<p>3.The right-click menu adds the function of inserting parent nodes and deleting only the current node.</p>
<p>4.The top toolbar supports automatic folding into more according to the window width.</p>
<p>5.Support manual input of zoom factor.</p>
<p>6.Improve the English translation of the interface.</p>
<p>7.Change the mini map to render through images.</p>
</blockquote>
<h2>0.7.3-fix.2</h2>
<p>Fix some issues with collaborative editing:</p>
<p>1.The position of the new node is incorrect when inserting peer nodes;</p>

View File

@@ -37,15 +37,15 @@ const mindMap = new MindMap({
| textContentMargin | Number | 2 | The spacing between various text information in the node, such as the spacing between the icon and text | |
| selectTranslateStep | Number | 3 | The canvas offset when mouse moves to the edge during multi-select node | |
| selectTranslateLimit | Number | 20 | The distance from the edge when the canvas begins to offset during multi-select node | |
| customNoteContentShowv0.1.6+ | Object | null | Custom node note content display, object type, structure: {show: (noteContent, left, top) => {// your display node note logic }, hide: () => {// your hide node note logic }} | |
| customNoteContentShowv0.1.6+ | Object | null | Custom node note content display, object type, structure: {show: (noteContent, left, top, node) => {// your display node note logic. node is a new parameter added in v0.8.1+ version, representing node instances }, hide: () => {// your hide node note logic }} | |
| readonlyv0.1.7+ | Boolean | false | Whether it is read-only mode | |
| enableFreeDragv0.2.4+ | Boolean | false | Enable node free drag | |
| enableFreeDragv0.2.4+ | Boolean | false | Enable node free(Free drag means that nodes can be dragged to any position on the canvas. Please note that it is not a function of dragging nodes to become siblings of other nodes. The connection of free drag may have certain problems, so it is best not to use this feature) drag | |
| watermarkConfigv0.2.4+ | Object | | Watermark config, Please refer to the table 【Watermark config】 below for detailed configuration | |
| textAutoWrapWidthv0.3.4+ | Number | 500 | Each line of text in the node will wrap automatically when it reaches the width | |
| customHandleMousewheelv0.4.3+ | Function | null | User-defined mouse wheel event processing can pass a function, and the callback parameter is the event object | |
| mousewheelActionv0.4.3+ | String | zoom | The behavior of the mouse wheel, `zoom`(Zoom in and out)、`move`(Move up and down). If `customHandleMousewheel` passes a custom function, this property will not take effect | |
| mousewheelActionv0.4.3+ | String | zoomv0.9.1+ default is move | The behavior of the mouse wheel, `zoom`(Zoom in and out)、`move`(Move up and down). If `customHandleMousewheel` passes a custom function, this property will not take effect | |
| mousewheelMoveStepv0.4.3+ | Number | 100 | When the `mousewheelAction` is set to `move`, you can use this attribute to control the step length of the view movement when the mouse scrolls. The unit is `px` | |
| mousewheelZoomActionReversev0.6.5+ | Boolean | false | When `mousewheelAction` is set to `zoom`, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed | |
| mousewheelZoomActionReversev0.6.5+ | Boolean | falsev0.9.1+ default is true | When `mousewheelAction` is set to `zoom`, Or when holding down the Ctrl key, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed | |
| defaultInsertSecondLevelNodeTextv0.4.7+ | String | 二级节点 | Text of the default inserted secondary node | |
| defaultInsertBelowSecondLevelNodeTextv0.4.7+ | String | 分支主题 | Text for nodes below the second level inserted by default | |
| expandBtnStylev0.5.0+ | Object | { color: '#808080', fill: '#fff', fontSize: 13, strokeColor: '#333333' } | Expand the color of the stow button, (The fontSize and strokeColor fields were added in version 0.7.0+to set the text style for displaying the number of nodes when folded) | |
@@ -82,7 +82,8 @@ const mindMap = new MindMap({
| errorHandlerv0.6.15+ | Function | | Custom error handling functions currently only throw some asynchronous logic errors. Can pass a function that takes two parameters, the first being the wrong type and the second being the wrong object | |
| disableMouseWheelZoomv0.6.15+ | Boolean | false | Prohibit mouse wheel scaling, you can still use the API for scaling | |
| resetCssv0.6.16+ | String | * { margin: 0; padding: 0; box-sizing: border-box; } | When exporting images and SVGs, the default style overlay for rich text node content, which is embedded in HTML nodes in SVGs, will occur. If not overlaid, the node content will be offset | |
| enableDblclickResetv0.6.17+ | Boolean | true(v0.7.0+changed to false) | Turn on the mouse and double-click to reset the position and zoom of the mind map | |
| enableDblclickResetv0.6.17+(v0.8.0+this attribute has been deleted) | Boolean | true(v0.7.0+changed to false) | Turn on the mouse and double-click to reset the position and zoom of the mind map | |
| enableDblclickBackToRootNodev0.8.0+ | Boolean | false | Whether to return to the root node when double clicking with the mouse, that is, to center the display of the root node | |
| minExportImgCanvasScalev0.7.0+ | Number | 2 | The scaling factor of canvas when exporting images and PDFs, which is set to the maximum value of window.devicePixelRatio to improve image clarity | |
| hoverRectColorv0.7.0+ | String | rgb(94, 200, 248) | The node mouse hover and the rectangular border color displayed when activated will add a transparency of 0.6 when hovering | |
| hoverRectPaddingv0.7.0+ | Number | 2 | The distance between the node mouse hover and the displayed rectangular border when activated and the node content | |
@@ -95,6 +96,14 @@ const mindMap = new MindMap({
| dragOpacityConfigv0.7.2+ | Object | { cloneNodeOpacity: 0.5, beingDragNodeOpacity: 0.3 } | The transparency configuration during node dragging, passing an object, and the field meanings are: the transparency of the cloned node or rectangle that follows the mouse movement, and the transparency of the dragged node | |
| tagsColorMapv0.7.2+ | Object | {} | The color of a custom node label can be transferred to an object, where key is the label content to be assigned a color, and value is the color of the label content. If not transferred internally, a corresponding color will be generated based on the label content | |
| cooperateStylev0.7.3+ | Object | { avatarSize: 22, fontSize: 12 } | The configuration of personnel avatar style during node collaboration editing, with field meanings as follows: avatar size, and if it is a text avatar, the size of the text | |
| associativeLineIsAlwaysAboveNodev0.8.0+ | Boolean | true | Is the associated line always displayed above the node? If set to false, it will be at the top level when creating and activating the associated line, and in other cases, it will be below the node | |
| defaultGeneralizationTextv0.8.0+ | String | 概要 | Insert default text for summary | |
| handleIsSplitByWrapOnPasteCreateNewNodev0.8.0+ | Function / null | null | When creating a new node by pasting text, control whether to automatically split the nodes based on line breaks. If there is a line break, multiple nodes will be created based on the line break. Otherwise, only one node will be created, and a function can be passed to return promise. resolve represents splitting based on line breaks, and reject represents ignoring line breaks | |
| addHistoryTimev0.8.0+ | Number | 100 | Only one historical record can be added within the specified time to avoid adding unnecessary intermediate states. Unit: ms | |
| isDisableDragv0.8.1+ | Boolean | false | Is disable dragging the canvas | |
| disableTouchZoomv0.8.1+ | Boolean | false | Prohibit double finger scaling, you can still use the API for scaling, which takes effect on the TouchEvent plugin | |
| highlightNodeBoxStylev0.9.0+ | Object | { stroke: 'rgb(94, 200, 248)', fill: 'transparent' } | Highlight box style when the mouse moves into the summary to highlight the node it belongs to | |
| createNewNodeBehaviorv0.9.1+ | String | default | Behavior when creating a new node. defaultBy default, newly created nodes will be activated and enter editing mode. If multiple new nodes are created simultaneously, they will only be activated and will not enter editing mode、notActiveDo not activate newly created nodes、activeOnlyOnly activate newly created nodes and do not enter editing mode | |
### Data structure
@@ -210,17 +219,103 @@ Get whether a plugin is registered, The index of the plugin in the registered pl
List of all currently registered plugins.
## Instance props
### el
Container element.
### opt
Config options object.
### svg
> @svgdotjs/svg.js library calls the node instance returned by the SVG() method
Canvas SVG element.
### draw
> @svgdotjs/svg.js library calls the node instance returned by the group() method
>
> Child node of SVG node
Container element, used to carry content such as nodes and connections.
### lineDraw
> v0.8.0+
>
> @svgdotjs/svg.js library calls the node instance returned by the group() method
>
> Child node of draw node
Container for node wiring elements.
### nodeDraw
> v0.8.0+
>
> @svgdotjs/svg.js library calls the node instance returned by the group() method
>
> Child node of draw node
Container for node elements.
### associativeLineDraw
> v0.8.0+
>
> @svgdotjs/svg.js library calls the node instance returned by the group() method
>
> Available when the associated line plugin is registered
>
> Child node of draw node
Container for associative line content.
### otherDraw
> v0.8.0+
>
> @svgdotjs/svg.js library calls the node instance returned by the group() method
>
> Child node of draw node
Container for other content.
### elRect
The size and position information of the container element 'el'. The return result of calling the 'getBoundingClientRect()' method.
### width
The width of the container element 'el'.
### height
The height of the container element 'el'.
### themeConfig
Current Theme Configuration.
## Instance methods
### clearDraw()
> v0.8.0+
Clear `lineDraw``associativeLineDraw``nodeDraw``otherDraw` containers.
### destroy()
> v0.6.0+
Destroy mind maps. It will remove registered plugins, remove listening events, and delete all nodes on the canvas.
### getSvgData({ paddingX = 0, paddingY = 0 })
### getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false })
> v0.3.0+
@@ -228,6 +323,8 @@ Destroy mind maps. It will remove registered plugins, remove listening events, a
`paddingY`: Padding y
`ignoreWatermark`v0.8.0+, Do not draw watermarks. If you do not need to draw watermarks, you can pass 'true' because drawing watermarks is very slow
Get the `svg` data and return an object. The detailed structure is as follows:
```js
@@ -314,6 +411,8 @@ Listen to an event. Event list:
| node_icon_clickv0.6.10+ | Triggered when clicking on an icon within a node | thisnode instance、itemClick on the icon name、eevent object |
| 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)
@@ -323,7 +422,9 @@ Trigger an event, which can be one of the events listed above or a custom event.
Unbind an event.
### setTheme(theme)
### setTheme(theme, notRender = false)
- `notRender`: v0.8.0+, Is not call the render method to update the canvas.
Switches the theme. Available themes can be found in the options table above.
@@ -331,7 +432,9 @@ Switches the theme. Available themes can be found in the options table above.
Gets the current theme.
### setThemeConfig(config)
### setThemeConfig(config, notRender = false)
- `notRender`: v0.8.0+, Is not call the render method to update the canvas.
Sets the theme configuration. `config` is the same as the `themeConfig` option
in the options table above.
@@ -372,7 +475,9 @@ This method only updates the configuration and has no other side effects, such a
Gets the current layout structure.
### setLayout(layout)
### setLayout(layout, notRender = false)
- `notRender`: v0.8.0+, Is not call the render method to update the canvas.
Sets the layout structure. Available values can be found in the `layout` field
in the options table above.
@@ -419,7 +524,9 @@ 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 |
### setData(data)
@@ -490,4 +597,4 @@ Register plugin, Use `MindMap.usePlugin` to register plugin only before instanti
> v0.4.0+
Remove registered plugin, Plugins registered through the `usePlugin` or `addPlugin` methods can be removed.
Remove registered plugin, Plugins registered through the `usePlugin` or `addPlugin` methods can be removed.

View File

@@ -123,7 +123,7 @@
<td>customNoteContentShowv0.1.6+</td>
<td>Object</td>
<td>null</td>
<td>Custom node note content display, object type, structure: {show: (noteContent, left, top) =&gt; {// your display node note logic }, hide: () =&gt; {// your hide node note logic }}</td>
<td>Custom node note content display, object type, structure: {show: (noteContent, left, top, node) =&gt; {// your display node note logic. node is a new parameter added in v0.8.1+ version, representing node instances }, hide: () =&gt; {// your hide node note logic }}</td>
<td></td>
</tr>
<tr>
@@ -137,7 +137,7 @@
<td>enableFreeDragv0.2.4+</td>
<td>Boolean</td>
<td>false</td>
<td>Enable node free drag</td>
<td>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</td>
<td></td>
</tr>
<tr>
@@ -164,7 +164,7 @@
<tr>
<td>mousewheelActionv0.4.3+</td>
<td>String</td>
<td>zoom</td>
<td>zoomv0.9.1+ default is move</td>
<td>The behavior of the mouse wheel, <code>zoom</code>(Zoom in and out)<code>move</code>(Move up and down). If <code>customHandleMousewheel</code> passes a custom function, this property will not take effect</td>
<td></td>
</tr>
@@ -178,8 +178,8 @@
<tr>
<td>mousewheelZoomActionReversev0.6.5+</td>
<td>Boolean</td>
<td>false</td>
<td>When <code>mousewheelAction</code> is set to <code>zoom</code>, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed</td>
<td>falsev0.9.1+ default is true</td>
<td>When <code>mousewheelAction</code> is set to <code>zoom</code>, Or when holding down the Ctrl key, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed</td>
<td></td>
</tr>
<tr>
@@ -435,13 +435,20 @@
<td></td>
</tr>
<tr>
<td>enableDblclickResetv0.6.17+</td>
<td>enableDblclickResetv0.6.17+(v0.8.0+this attribute has been deleted)</td>
<td>Boolean</td>
<td>true(v0.7.0+changed to false)</td>
<td>Turn on the mouse and double-click to reset the position and zoom of the mind map</td>
<td></td>
</tr>
<tr>
<td>enableDblclickBackToRootNodev0.8.0+</td>
<td>Boolean</td>
<td>false</td>
<td>Whether to return to the root node when double clicking with the mouse, that is, to center the display of the root node</td>
<td></td>
</tr>
<tr>
<td>minExportImgCanvasScalev0.7.0+</td>
<td>Number</td>
<td>2</td>
@@ -525,6 +532,62 @@
<td>The configuration of personnel avatar style during node collaboration editing, with field meanings as follows: avatar size, and if it is a text avatar, the size of the text</td>
<td></td>
</tr>
<tr>
<td>associativeLineIsAlwaysAboveNodev0.8.0+</td>
<td>Boolean</td>
<td>true</td>
<td>Is the associated line always displayed above the node? If set to false, it will be at the top level when creating and activating the associated line, and in other cases, it will be below the node</td>
<td></td>
</tr>
<tr>
<td>defaultGeneralizationTextv0.8.0+</td>
<td>String</td>
<td>概要</td>
<td>Insert default text for summary</td>
<td></td>
</tr>
<tr>
<td>handleIsSplitByWrapOnPasteCreateNewNodev0.8.0+</td>
<td>Function / null</td>
<td>null</td>
<td>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</td>
<td></td>
</tr>
<tr>
<td>addHistoryTimev0.8.0+</td>
<td>Number</td>
<td>100</td>
<td>Only one historical record can be added within the specified time to avoid adding unnecessary intermediate states. Unit: ms</td>
<td></td>
</tr>
<tr>
<td>isDisableDragv0.8.1+</td>
<td>Boolean</td>
<td>false</td>
<td>Is disable dragging the canvas</td>
<td></td>
</tr>
<tr>
<td>disableTouchZoomv0.8.1+</td>
<td>Boolean</td>
<td>false</td>
<td>Prohibit double finger scaling, you can still use the API for scaling, which takes effect on the TouchEvent plugin</td>
<td></td>
</tr>
<tr>
<td>highlightNodeBoxStylev0.9.0+</td>
<td>Object</td>
<td>{ stroke: 'rgb(94, 200, 248)', fill: 'transparent' }</td>
<td>Highlight box style when the mouse moves into the summary to highlight the node it belongs to</td>
<td></td>
</tr>
<tr>
<td>createNewNodeBehaviorv0.9.1+</td>
<td>String</td>
<td>default</td>
<td>Behavior when creating a new node. defaultBy default, newly created nodes will be activated and enter editing mode. If multiple new nodes are created simultaneously, they will only be activated and will not enter editing mode、notActiveDo not activate newly created nodes、activeOnlyOnly activate newly created nodes and do not enter editing mode</td>
<td></td>
</tr>
</tbody>
</table>
<h3>Data structure</h3>
@@ -679,18 +742,77 @@ mindMap.setTheme(<span class="hljs-string">&#x27;Theme name&#x27;</span>)
<p>v0.3.0+</p>
</blockquote>
<p>List of all currently registered plugins.</p>
<h2>Instance props</h2>
<h3>el</h3>
<p>Container element.</p>
<h3>opt</h3>
<p>Config options object.</p>
<h3>svg</h3>
<blockquote>
<p>@svgdotjs/svg.js library calls the node instance returned by the SVG() method</p>
</blockquote>
<p>Canvas SVG element.</p>
<h3>draw</h3>
<blockquote>
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
<p>Child node of SVG node</p>
</blockquote>
<p>Container element, used to carry content such as nodes and connections.</p>
<h3>lineDraw</h3>
<blockquote>
<p>v0.8.0+</p>
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
<p>Child node of draw node</p>
</blockquote>
<p>Container for node wiring elements.</p>
<h3>nodeDraw</h3>
<blockquote>
<p>v0.8.0+</p>
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
<p>Child node of draw node</p>
</blockquote>
<p>Container for node elements.</p>
<h3>associativeLineDraw</h3>
<blockquote>
<p>v0.8.0+</p>
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
<p>Available when the associated line plugin is registered</p>
<p>Child node of draw node</p>
</blockquote>
<p>Container for associative line content.</p>
<h3>otherDraw</h3>
<blockquote>
<p>v0.8.0+</p>
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
<p>Child node of draw node</p>
</blockquote>
<p>Container for other content.</p>
<h3>elRect</h3>
<p>The size and position information of the container element 'el'. The return result of calling the 'getBoundingClientRect()' method.</p>
<h3>width</h3>
<p>The width of the container element 'el'.</p>
<h3>height</h3>
<p>The height of the container element 'el'.</p>
<h3>themeConfig</h3>
<p>Current Theme Configuration.</p>
<h2>Instance methods</h2>
<h3>clearDraw()</h3>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Clear <code>lineDraw</code>、<code>associativeLineDraw</code>、<code>nodeDraw</code>、<code>otherDraw</code> containers.</p>
<h3>destroy()</h3>
<blockquote>
<p>v0.6.0+</p>
</blockquote>
<p>Destroy mind maps. It will remove registered plugins, remove listening events, and delete all nodes on the canvas.</p>
<h3>getSvgData({ paddingX = 0, paddingY = 0 })</h3>
<h3>getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false })</h3>
<blockquote>
<p>v0.3.0+</p>
</blockquote>
<p><code>paddingX</code>: Padding x</p>
<p><code>paddingY</code>: Padding y</p>
<p><code>ignoreWatermark</code>v0.8.0+, Do not draw watermarks. If you do not need to draw watermarks, you can pass 'true' because drawing watermarks is very slow</p>
<p>Get the <code>svg</code> data and return an object. The detailed structure is as follows:</p>
<pre class="hljs"><code>{
svg, <span class="hljs-comment">// Element, the overall svg element of the mind map graphics, including: svg (canvas container), g (actual mind map group)</span>
@@ -933,17 +1055,33 @@ poor performance and should be used sparingly.</p>
<td>Triggered when the setData method is called to dynamically set mind map data</td>
<td>dataNew Mind Map Data</td>
</tr>
<tr>
<td>resizev0.8.0+</td>
<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>
<p>Trigger an event, which can be one of the events listed above or a custom event.</p>
<h3>off(event, fn)</h3>
<p>Unbind an event.</p>
<h3>setTheme(theme)</h3>
<h3>setTheme(theme, notRender = false)</h3>
<ul>
<li><code>notRender</code>: v0.8.0+, Is not call the render method to update the canvas.</li>
</ul>
<p>Switches the theme. Available themes can be found in the options table above.</p>
<h3>getTheme()</h3>
<p>Gets the current theme.</p>
<h3>setThemeConfig(config)</h3>
<h3>setThemeConfig(config, notRender = false)</h3>
<ul>
<li><code>notRender</code>: v0.8.0+, Is not call the render method to update the canvas.</li>
</ul>
<p>Sets the theme configuration. <code>config</code> is the same as the <code>themeConfig</code> option
in the options table above.</p>
<h3>getCustomThemeConfig()</h3>
@@ -969,7 +1107,10 @@ in the options table above.</p>
<p>This method only updates the configuration and has no other side effects, such as triggering canvas re-rendering</p>
<h3>getLayout()</h3>
<p>Gets the current layout structure.</p>
<h3>setLayout(layout)</h3>
<h3>setLayout(layout, notRender = false)</h3>
<ul>
<li><code>notRender</code>: v0.8.0+, Is not call the render method to update the canvas.</li>
</ul>
<p>Sets the layout structure. Available values can be found in the <code>layout</code> field
in the options table above.</p>
<h3>execCommand(name, ...args)</h3>
@@ -1162,7 +1303,17 @@ 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>
<td>Insert a parent node into the specified node, with the operation node being the currently active node or the specified node</td>
<td>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 <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js">exampleData.js</a></td>
</tr>
<tr>
<td>REMOVE_CURRENT_NODEv0.8.0+</td>
<td>Delete only the current node, operate on the currently active node or specified node</td>
<td>appointNodesOptional, specify the nodes to be deleted, and multiple nodes can be passed as an array</td>
</tr>
</tbody>
</table>

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

@@ -6,6 +6,18 @@
This plugin is used to support inserting formulas into nodes.
> 注意:公式是通过[KaTeX](https://github.com/KaTeX/KaTeX)库实现的,`KaTeX`提供了一些配置,插件默认的一个配置是:
> Note: The formula is implemented through the [KaTeX](https://github.com/KaTeX/KaTeX) library, and 'KaTeX' provides some configurations. The default configuration for the plugin is:
```js
{
output: 'mathml'
}
```
> This formula may not render successfully on a few browsers. If you need to be compatible with these browsers, you can consider changing the configuration to 'HTML'. For detailed documentation, please refer to [Options](https://katex.org/docs/options). Using this configuration may require the introduction of a 'KaTeX' style file, which you can test on your own.
## Register
```js

View File

@@ -8,6 +8,19 @@
<p>This plugin is only supported in rich text mode, so it needs to be used after registering the RichText plugin</p>
</blockquote>
<p>This plugin is used to support inserting formulas into nodes.</p>
<blockquote>
<p>注意公式是通过<a href="https://github.com/KaTeX/KaTeX">KaTeX</a>库实现的<code>KaTeX</code>提供了一些配置插件默认的一个配置是</p>
</blockquote>
<blockquote>
<p>Note: The formula is implemented through the <a href="https://github.com/KaTeX/KaTeX">KaTeX</a> library, and 'KaTeX' provides some configurations. The default configuration for the plugin is:</p>
</blockquote>
<pre class="hljs"><code>{
<span class="hljs-attr">output</span>: <span class="hljs-string">&#x27;mathml&#x27;</span>
}
</code></pre>
<blockquote>
<p>This formula may not render successfully on a few browsers. If you need to be compatible with these browsers, you can consider changing the configuration to 'HTML'. For detailed documentation, please refer to <a href="https://katex.org/docs/options">Options</a>. Using this configuration may require the introduction of a 'KaTeX' style file, which you can test on your own.</p>
</blockquote>
<h2>Register</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> Formula <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/plugins/Formula.js&#x27;</span>

View File

@@ -65,11 +65,15 @@ The folder containing the packaged resources for the `web` folder.
[Explore how to export HTML and SVG as images](https://juejin.cn/post/7276712861514170409)
[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.
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.
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.
If you have suggestions or find bugs, you can submit [issues](https://github.com/wanglin2/mind-map/issues) here.
@@ -223,4 +227,34 @@ 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 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/天清如愿.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>天清如愿</p>
</div>
</div>
<div style="display: flex;">
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/敬明朗.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>敬明朗</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>飞箭</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/戚永峰.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>戚永峰</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/moom.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>moom</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/张扬.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>张扬</p>
</div>
</div>

View File

@@ -8,17 +8,17 @@
</blockquote>
<h2>Features</h2>
<ul>
<li><input type="checkbox" id="checkbox48" checked="true" /><label for="checkbox48">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="checkbox49" checked="true" /><label for="checkbox49">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="checkbox50" checked="true" /><label for="checkbox50">Built-in multiple themes, allowing for highly customizable styles, and supporting registration of new themes</label></li>
<li><input type="checkbox" id="checkbox51" checked="true" /><label for="checkbox51">Node content supports text (regular text, rich text), images, icons, hyperlinks, notes, labels, summaries, and math formulas</label></li>
<li><input type="checkbox" id="checkbox52" checked="true" /><label for="checkbox52">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="checkbox53" checked="true" /><label for="checkbox53">Support canvas dragging and scaling</label></li>
<li><input type="checkbox" id="checkbox54" checked="true" /><label for="checkbox54">Supports two multi node selection methods: mouse button drag selection and Ctrl+left button selection</label></li>
<li><input type="checkbox" id="checkbox55" checked="true" /><label for="checkbox55">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="checkbox56" checked="true" /><label for="checkbox56">Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, watermarks, and scrollbar</label></li>
<li><input type="checkbox" id="checkbox57" checked="true" /><label for="checkbox57">Provide rich configurations to meet various scenarios and usage habits</label></li>
<li><input type="checkbox" id="checkbox58" checked="true" /><label for="checkbox58">Support collaborative editing</label></li>
<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>
</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="checkbox59" checked="true" /><label for="checkbox59">Toolbar, which supports inserting and deleting nodes, and editing node</label>
<li><input type="checkbox" id="checkbox27" checked="true" /><label for="checkbox27">Toolbar, which supports inserting and deleting nodes, and editing node</label>
images, icons, hyperlinks, notes, tags, and summaries</li>
<li><input type="checkbox" id="checkbox60" checked="true" /><label for="checkbox60">Sidebar, with panels for basic style settings, node style settings,</label>
<li><input type="checkbox" id="checkbox28" checked="true" /><label for="checkbox28">Sidebar, with panels for basic style settings, node style settings,</label>
outline, theme selection, and structure selection</li>
<li><input type="checkbox" id="checkbox61" checked="true" /><label for="checkbox61">Import and export functionality; data is saved in the browser's local</label>
<li><input type="checkbox" id="checkbox29" checked="true" /><label for="checkbox29">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="checkbox62" checked="true" /><label for="checkbox62">Right-click menu, which supports operations such as expanding, collapsing,</label>
<li><input type="checkbox" id="checkbox30" checked="true" /><label for="checkbox30">Right-click menu, which supports operations such as expanding, collapsing,</label>
and organizing layout</li>
<li><input type="checkbox" id="checkbox63" checked="true" /><label for="checkbox63">Bottom bar, which supports node and word count statistics, switching</label>
<li><input type="checkbox" id="checkbox31" checked="true" /><label for="checkbox31">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>
@@ -51,9 +51,11 @@ full screen, support mini map</li>
<p><a href="https://juejin.cn/post/7204854015463538744">How to simulate the background image style of css in canvas</a></p>
<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.</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>
<p>If you have suggestions or find bugs, you can submit <a href="https://github.com/wanglin2/mind-map/issues">issues</a> here.</p>
<p>The built-in themes and icons in the project part come from:</p>
<p><a href="https://naotu.baidu.com/">Baidu Mind Map</a></p>
@@ -181,6 +183,36 @@ 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 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/天清如愿.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>天清如愿</p>
</div>
</div>
<div style="display: flex;">
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/敬明朗.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>敬明朗</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>飞箭</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/戚永峰.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>戚永峰</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/moom.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>moom</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/张扬.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>张扬</p>
</div>
</div>
</div>
</template>

View File

@@ -35,7 +35,8 @@ Function return content:
```js
{
svgHTML, // small map html
getImgUrl,// v0.8.0+, An asynchronous function that you can call and pass a callback function. The callback function can receive a parameter representing a small map of the image type, and you can render it through the img tag
svgHTML, // Mini map HTML, it is recommended to use the getImgUrl method to obtain image type mini maps, reduce the number of page DOM, and optimize performance
viewBoxStyle, // view box position information
miniMapBoxScale, // view box zoom value
miniMapBoxLeft, // view box left value

View File

@@ -25,7 +25,8 @@ MindMap.usePlugin(MiniMap)
<p><code>boxHeight</code>: the height of the small map container</p>
<p>Function return content:</p>
<pre class="hljs"><code>{
svgHTML, <span class="hljs-comment">// small map html</span>
getImgUrl,<span class="hljs-comment">// v0.8.0+, An asynchronous function that you can call and pass a callback function. The callback function can receive a parameter representing a small map of the image type, and you can render it through the img tag</span>
svgHTML, <span class="hljs-comment">// Mini map HTML, it is recommended to use the getImgUrl method to obtain image type mini maps, reduce the number of page DOM, and optimize performance</span>
viewBoxStyle, <span class="hljs-comment">// view box position information</span>
miniMapBoxScale, <span class="hljs-comment">// view box zoom value</span>
miniMapBoxLeft, <span class="hljs-comment">// view box left value</span>

View File

@@ -56,6 +56,58 @@ 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+
- `active`Boolean, active status.
Update nodes based on whether they are activated or not. The main task is to update the display and hiding of the expand and collapse buttons for nodes.
### setOpacity(val)
> v0.7.2+
@@ -199,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)
@@ -215,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,52 @@
</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>
</blockquote>
<ul>
<li><code>active</code>Boolean, active status.</li>
</ul>
<p>Update nodes based on whether they are activated or not. The main task is to update the display and hiding of the expand and collapse buttons for nodes.</p>
<h3>setOpacity(val)</h3>
<blockquote>
<p>v0.7.2+</p>
@@ -135,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>
@@ -148,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

@@ -7,14 +7,38 @@ accessed through `mindMap.renderer`.
### activeNodeList
Gets the current list of active nodes
Gets the current list of active nodes.
### root
Gets the root node of the node tree
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+
Return to the central theme, that is, set the root node to the center of the canvas.
### setData(data)
> v0.7.3+
@@ -23,49 +47,81 @@ Dynamically set mind map data.
### clearActive()
Clears the currently active node
> v0.8.0+ abandoned
Clears the currently active node.
### clearAllActive()
Clears all currently active nodes and triggers the `node_active` event
> v0.8.0+ abandoned
Clears all currently active nodes and triggers the `node_active` event.
### clearActiveNode()
> v0.8.0+
Clears all currently active nodes and triggers the `node_active` event.
### clearActiveNodeList()
> v0.8.0+
Clears all currently active nodes but not triggers the `node_active` event.
### startTextEdit()
> v0.1.6+
If there is a text editing requirement, this method can be called to
disable the enter key and delete key related shortcuts to prevent conflicts
disable the enter key and delete key related shortcuts to prevent conflicts.
### endTextEdit()
> v0.1.6+
End text editing, restore enter key and delete key related shortcuts
End text editing, restore enter key and delete key related shortcuts.
### addActiveNode(node)
Add a node to the active list
> v0.8.0+ abandoned
Add a node to the active list.
### addNodeToActiveList(node)
> v0.8.0+
Add a node to the active list.
### removeActiveNode(node)
Remove a node from the active list
> v0.8.0+ abandoned
Remove a node from the active list.
### removeNodeFromActiveList(node)
> v0.8.0+
Remove a node from the active list.
### findActiveNodeIndex(node)
Search for the index of a node in the active list
Search for the index of a node in the active list.
### getNodeIndex(node)
Get the position index of a node among its siblings
Get the position index of a node among its siblings.
### removeOneNode(node)
Delete a specific node
Delete a specific node.
### copyNode()
Copy a node, the active node is the node to be operated on, if there are
multiple active nodes, only the first node will be operated on
multiple active nodes, only the first node will be operated on.
### setNodeDataRender(node, data, notRender)
@@ -73,25 +129,25 @@ multiple active nodes, only the first node will be operated on
Set node `data`, i.e. the data in the data field, and will determine whether the
node needs to be re-rendered based on whether the node size has changed, `data`
is an object, e.g. `{text: 'I am new text'}`
is an object, e.g. `{text: 'I am new text'}`.
### moveNodeTo(node, toNode)
> v0.1.5+
Move a node as a child of another node
Move a node as a child of another node.
### insertBefore(node, exist)
> v0.1.5+
Move a node in front of another node
Move a node in front of another node.
### insertAfter(node, exist)
> v0.1.5+
Move a node behind another node
Move a node behind another node.
### moveNodeToCenter(node)

View File

@@ -5,65 +5,121 @@
accessed through <code>mindMap.renderer</code>.</p>
<h2>Properties</h2>
<h3>activeNodeList</h3>
<p>Gets the current list of active nodes</p>
<p>Gets the current list of active nodes.</p>
<h3>root</h3>
<p>Gets the root node of the node tree</p>
<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>
</blockquote>
<p>Return to the central theme, that is, set the root node to the center of the canvas.</p>
<h3>setData(data)</h3>
<blockquote>
<p>v0.7.3+</p>
</blockquote>
<p>Dynamically set mind map data.</p>
<h3>clearActive()</h3>
<p>Clears the currently active node</p>
<blockquote>
<p>v0.8.0+ abandoned</p>
</blockquote>
<p>Clears the currently active node.</p>
<h3>clearAllActive()</h3>
<p>Clears all currently active nodes and triggers the <code>node_active</code> event</p>
<blockquote>
<p>v0.8.0+ abandoned</p>
</blockquote>
<p>Clears all currently active nodes and triggers the <code>node_active</code> event.</p>
<h3>clearActiveNode()</h3>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Clears all currently active nodes and triggers the <code>node_active</code> event.</p>
<h3>clearActiveNodeList()</h3>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Clears all currently active nodes but not triggers the <code>node_active</code> event.</p>
<h3>startTextEdit()</h3>
<blockquote>
<p>v0.1.6+</p>
</blockquote>
<p>If there is a text editing requirement, this method can be called to
disable the enter key and delete key related shortcuts to prevent conflicts</p>
disable the enter key and delete key related shortcuts to prevent conflicts.</p>
<h3>endTextEdit()</h3>
<blockquote>
<p>v0.1.6+</p>
</blockquote>
<p>End text editing, restore enter key and delete key related shortcuts</p>
<p>End text editing, restore enter key and delete key related shortcuts.</p>
<h3>addActiveNode(node)</h3>
<p>Add a node to the active list</p>
<blockquote>
<p>v0.8.0+ abandoned</p>
</blockquote>
<p>Add a node to the active list.</p>
<h3>addNodeToActiveList(node)</h3>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Add a node to the active list.</p>
<h3>removeActiveNode(node)</h3>
<p>Remove a node from the active list</p>
<blockquote>
<p>v0.8.0+ abandoned</p>
</blockquote>
<p>Remove a node from the active list.</p>
<h3>removeNodeFromActiveList(node)</h3>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Remove a node from the active list.</p>
<h3>findActiveNodeIndex(node)</h3>
<p>Search for the index of a node in the active list</p>
<p>Search for the index of a node in the active list.</p>
<h3>getNodeIndex(node)</h3>
<p>Get the position index of a node among its siblings</p>
<p>Get the position index of a node among its siblings.</p>
<h3>removeOneNode(node)</h3>
<p>Delete a specific node</p>
<p>Delete a specific node.</p>
<h3>copyNode()</h3>
<p>Copy a node, the active node is the node to be operated on, if there are
multiple active nodes, only the first node will be operated on</p>
multiple active nodes, only the first node will be operated on.</p>
<h3>setNodeDataRender(node, data, notRender)</h3>
<ul>
<li><code>notRender</code>: v0.6.9+, <code>Boolean</code>, Default is <code>false</code>, Do not trigger rendering.</li>
</ul>
<p>Set node <code>data</code>, i.e. the data in the data field, and will determine whether the
node needs to be re-rendered based on whether the node size has changed, <code>data</code>
is an object, e.g. <code>{text: 'I am new text'}</code></p>
is an object, e.g. <code>{text: 'I am new text'}</code>.</p>
<h3>moveNodeTo(node, toNode)</h3>
<blockquote>
<p>v0.1.5+</p>
</blockquote>
<p>Move a node as a child of another node</p>
<p>Move a node as a child of another node.</p>
<h3>insertBefore(node, exist)</h3>
<blockquote>
<p>v0.1.5+</p>
</blockquote>
<p>Move a node in front of another node</p>
<p>Move a node in front of another node.</p>
<h3>insertAfter(node, exist)</h3>
<blockquote>
<p>v0.1.5+</p>
</blockquote>
<p>Move a node behind another node</p>
<p>Move a node behind another node.</p>
<h3>moveNodeToCenter(node)</h3>
<blockquote>
<p>v0.2.17+</p>

View File

@@ -2,8 +2,6 @@
> v0.4.0+
> Note: This is a testing nature and imperfect function
This plugin provides the ability to edit rich text of nodes, and takes effect after registration.
By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, color, and backgroundColor. Underline and line height are not supported.
@@ -20,6 +18,8 @@ The principle of this plugin is to use [Quill](https://github.com/quilljs/quill)
> The compatibility of dom to image more is relatively poor, and exported images are empty on many browsers, so you can replace them with html2canvas according to your own needs.
After version `0.6.16+`, third-party libraries such as 'dom-to-image-more' and 'html2canvas' will no longer be used for export, Compatibility and export are no longer issues.
## Register
```js
@@ -68,6 +68,14 @@ Replace the built-in font size list during rich text editing. The built-in list
## Method
### setNotActiveNodeStyle(node, style)
> v0.8.0+
- `style`Object, style object.
Set rich text style for inactive nodes.
### selectAll()
Select All. When the node is being edited, you can select all the text in the node through this method.

View File

@@ -4,9 +4,6 @@
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<blockquote>
<p>Note: This is a testing nature and imperfect function</p>
</blockquote>
<p>This plugin provides the ability to edit rich text of nodes, and takes effect after registration.</p>
<p>By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, color, and backgroundColor. Underline and line height are not supported.</p>
<p>The principle of this plugin is to use <a href="https://github.com/quilljs/quill">Quill</a> editor implements rich text editing, and then uses the edited <code>DOM</code> node directly as the text data of the node, and embeds the <code>DOM</code> node through the <code>svg</code> <code>foreignObject</code> tag during rendering.</p>
@@ -21,6 +18,7 @@
<blockquote>
<p>The compatibility of dom to image more is relatively poor, and exported images are empty on many browsers, so you can replace them with html2canvas according to your own needs.</p>
</blockquote>
<p>After version <code>0.6.16+</code>, third-party libraries such as 'dom-to-image-more' and 'html2canvas' will no longer be used for export, Compatibility and export are no longer issues.</p>
<h2>Register</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> RichText <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/plugins/RichText.js&#x27;</span>
@@ -58,6 +56,14 @@ MindMap.usePlugin(RichText, opt?)
<pre class="hljs"><code>[<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, ..<span class="hljs-number">.100</span>]
</code></pre>
<h2>Method</h2>
<h3>setNotActiveNodeStyle(node, style)</h3>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<ul>
<li><code>style</code>Object, style object.</li>
</ul>
<p>Set rich text style for inactive nodes.</p>
<h3>selectAll()</h3>
<p>Select All. When the node is being edited, you can select all the text in the node through this method.</p>
<h3>focus()</h3>

View File

@@ -1,6 +1,6 @@
# Select plugin
The `Select` plugin provides the function of right-clicking to select multiple nodes.
The `Select` plugin provides the function of select multiple nodes.
## Register

View File

@@ -1,7 +1,7 @@
<template>
<div>
<h1>Select plugin</h1>
<p>The <code>Select</code> plugin provides the function of right-clicking to select multiple nodes.</p>
<p>The <code>Select</code> plugin provides the function of select multiple nodes.</p>
<h2>Register</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> Select <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/plugins/Select.js&#x27;</span>

View File

@@ -363,6 +363,67 @@ Generate colors based on incoming content, and the same content will generate th
Determine whether two objects are the same, only handling objects or arrays.
#### getNodeDataIndex(node)
> v0.8.0+
Gets the position index of a node within its sibling nodes.
#### getNodeIndexInNodeList(node, nodeList)
> v0.8.0+
Find the index of a node from a list of nodes.
#### setDataToClipboard(data)
> v0.8.0+
- `data`Object | Array
Set data to the user clipboard.
#### getDataFromClipboard()
> v0.8.0+
Reading text and images from the user's clipboard returns:
```js
{
text,
img
}
```
#### removeFromParentNodeData(node)
> v0.8.0+
Remove the data of a node from its parent node's `nodeData.children` list.
#### checkHasSupSubRelation()
> v0.8.1+
Determine whether there is a hierarchical relationship from the given node instance list.
#### handleSelfCloseTags(str)
> v0.9.1+
- `str`: html string
Add a closed state to HTML self closing tags, `<div><img src="xxx"></div>` -> `<div><img src="xxx" /></div>`
#### checkNodeListIsEqual(list1, list2)
> v0.9.1+
- `list1/list2`: Node instance list
Check if the two node instance lists contain the same nodes.
## Simulate CSS background in Canvas
Import:

View File

@@ -295,6 +295,60 @@ and copying the <code>data</code> of the data object, example:</p>
<li><code>a</code>、<code>b</code>: Object | Array, Two objects to compare</li>
</ul>
<p>Determine whether two objects are the same, only handling objects or arrays.</p>
<h4>getNodeDataIndex(node)</h4>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Gets the position index of a node within its sibling nodes.</p>
<h4>getNodeIndexInNodeList(node, nodeList)</h4>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Find the index of a node from a list of nodes.</p>
<h4>setDataToClipboard(data)</h4>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<ul>
<li><code>data</code>Object | Array</li>
</ul>
<p>Set data to the user clipboard.</p>
<h4>getDataFromClipboard()</h4>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Reading text and images from the user's clipboard returns:</p>
<pre class="hljs"><code>{
text,
img
}
</code></pre>
<h4>removeFromParentNodeData(node)</h4>
<blockquote>
<p>v0.8.0+</p>
</blockquote>
<p>Remove the data of a node from its parent node's <code>nodeData.children</code> list.</p>
<h4>checkHasSupSubRelation()</h4>
<blockquote>
<p>v0.8.1+</p>
</blockquote>
<p>Determine whether there is a hierarchical relationship from the given node instance list.</p>
<h4>handleSelfCloseTags(str)</h4>
<blockquote>
<p>v0.9.1+</p>
</blockquote>
<ul>
<li><code>str</code>: html string</li>
</ul>
<p>Add a closed state to HTML self closing tags, <code>&lt;div&gt;&lt;img src=&quot;xxx&quot;&gt;&lt;/div&gt;</code> -&gt; <code>&lt;div&gt;&lt;img src=&quot;xxx&quot; /&gt;&lt;/div&gt;</code>。</p>
<h4>checkNodeListIsEqual(list1, list2)</h4>
<blockquote>
<p>v0.9.1+</p>
</blockquote>
<ul>
<li><code>list1/list2</code>: Node instance list</li>
</ul>
<p>Check if the two node instance lists contain the same nodes.</p>
<h2>Simulate CSS background in Canvas</h2>
<p>Import:</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

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