Compare commits

...

134 Commits

Author SHA1 Message Date
街角小林
98f0d5e0fc 打包0.9.12 2024-05-13 11:23:29 +08:00
街角小林
6f3a02d39e Doc: update 2024-05-13 10:44:21 +08:00
街角小林
5cfc313f8e Feat:支持解析md文件中带html格式的标题文本 2024-05-13 10:13:48 +08:00
街角小林
d93825dd57 Fix:修复导入md文件时存在加粗的标题文本会解析为undefined的问题 2024-05-13 10:01:53 +08:00
街角小林
85171db778 Doc: update 2024-05-11 10:53:15 +08:00
街角小林
7d7ab9291a Demo:修复全屏查看模式下节点备注浮层无法显示的问题 2024-05-11 10:17:13 +08:00
街角小林
bb2502501e Feat:1.演示模式中禁止画布的所有内容响应鼠标事件;2.节点的超链接和备注图标支持响应鼠标事件;3.支持填空模式 2024-05-11 10:05:46 +08:00
街角小林
d60f30d97e Feat:1.优化代码:提取各处获取节点概要数据的兼容代码;2.演示插件支持概要;3.expandToNodeUid方法支持概要节点;4.findNodeByUid方法支持概要节点 2024-05-10 09:51:58 +08:00
街角小林
706b2ee65d Feat:新增添加节点附加的前置和后置内容的实例化选项 2024-05-08 19:02:19 +08:00
街角小林
e939b6132f Feat:新增节点标签的点击事件 2024-05-08 17:42:29 +08:00
街角小林
b69a0b620d Fix:1.全选、删除节点激活相邻节点、多选节点等操作增加派发before_node_active事件;2.多选节点改为实时派发激活事件 2024-05-08 09:54:16 +08:00
街角小林
d3d92a6e70 Demo:修复备注浮层显示时点击收起按钮收起节点后,备注浮层未消失的问题 2024-05-07 18:59:53 +08:00
街角小林
6751897309 Fix:修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题 2024-05-07 18:50:14 +08:00
街角小林
07a3f65911 Feat:移动指定节点到画布中心时默认不恢复缩放 2024-05-07 17:24:13 +08:00
街角小林
924b2660e1 打包Demo 2024-05-07 09:44:58 +08:00
街角小林
98d28a7b67 Doc: update 2024-05-07 09:33:21 +08:00
街角小林
a93518dee0 Merge branch 'feature' into main 2024-05-06 18:56:53 +08:00
街角小林
809c2c5666 Merge pull request #633 from Webb-L/hotfix
fix: 修复xss漏洞。
2024-05-06 18:27:06 +08:00
街角小林
cd90089b91 Doc: update 2024-05-06 18:11:10 +08:00
webb
4396c53d79 fix: 修复xss漏洞。 2024-04-28 23:26:04 +08:00
街角小林
4bea7d5e2b Doc: update 2024-04-24 09:51:32 +08:00
街角小林
cc62f98a9f 打包0.9.11 2024-04-19 14:34:56 +08:00
街角小林
244f2755a1 Doc: update 2024-04-19 14:18:59 +08:00
街角小林
23d38d9301 Doc: update 2024-04-19 14:18:24 +08:00
街角小林
73a61f81f8 Demo:新增演示模式 2024-04-19 09:30:32 +08:00
街角小林
6539a87cb2 Feat:新增演示插件 2024-04-19 09:29:48 +08:00
街角小林
75635ef2bb Doc: update 2024-04-17 17:50:18 +08:00
街角小林
20fae6270d Demo:优化鼠标在窗口边缘点击右键时菜单显示不全的问题 2024-04-17 09:14:58 +08:00
街角小林
6b40358f65 Demo:节点右键菜单新增导出为图片按钮 2024-04-16 19:02:57 +08:00
街角小林
e072dcb170 Feat:支持导出某个节点为图片 2024-04-16 19:02:35 +08:00
街角小林
6878d92ebe Fix:修复拖拽节点到边缘时画布自动移动无法停止的问题 2024-04-16 17:32:04 +08:00
街角小林
e9352a4f6c Fix:修复删除非当前激活的节点时,当前激活节点的激活状态无法取消的问题 2024-04-15 18:50:05 +08:00
wanglin2
6b9eee7fc6 Fix:修复删除当前激活的节点的所有子节点后,展开收起按钮没有消失的问题 2024-04-13 19:31:07 +08:00
wanglin2
c1217f1532 Feat:支持按住Command键和Win键多选节点 2024-04-13 15:28:56 +08:00
wanglin2
d73225f787 Doc: update 2024-04-13 14:17:59 +08:00
wanglin2
11b3270314 Doc: update 2024-04-12 23:08:33 +08:00
街角小林
5730a7aed5 Doc: update 2024-04-12 17:41:45 +08:00
街角小林
aeda3924a0 Fix:修复Dockerfile错误 2024-04-12 17:36:54 +08:00
街角小林
d9300395ff 新增docker文件及文档 2024-04-11 09:39:42 +08:00
街角小林
5bff885c00 Doc: update 2024-04-09 18:47:40 +08:00
街角小林
088fd398a9 Fix:修复同时创建多个实例时,文本编辑后节点宽高丢失的问题 2024-04-08 19:27:51 +08:00
街角小林
487aa38216 Fix:修复概要节点文本编辑中按回车结束时,相应的节点高亮框会错位显示 2024-04-08 18:20:23 +08:00
街角小林
e2403ae433 Fix:修复富文本插件转换节点数据时没有处理节点概要的问题 2024-04-08 18:07:37 +08:00
街角小林
7f0202e16e Fix:1.修复一键去除所有节点自定义样式命令不支持不为数组的概要的问题;2.修复富文本模式下创建的概要节点不是富文本的问题;Feat:插入概要时支持默认聚焦和进入编辑状态 2024-04-08 18:06:34 +08:00
街角小林
2b8d4ae225 Fix:修复点击概要会触发data_change_detail事件的问题 2024-04-08 18:02:59 +08:00
街角小林
513a1c342c Feat:复制知犀数据时,概要数据创建为数组形式 2024-04-08 18:01:59 +08:00
街角小林
d641b7e2ef Doc: update 2024-04-08 09:40:06 +08:00
街角小林
c769d4d203 Doc: update 2024-04-07 18:37:11 +08:00
街角小林
a36b9085bf 打包0.9.10 2024-04-02 18:14:01 +08:00
街角小林
42c934cb6d Doc: update 2024-04-02 13:57:57 +08:00
街角小林
728b1e1503 Doc: update 2024-04-02 11:53:30 +08:00
街角小林
1949d86abb Demo:支持拖拽文件到页面进行导入 2024-04-02 11:51:32 +08:00
街角小林
a7c68816f9 Doc: update 2024-04-02 09:01:45 +08:00
街角小林
ac3ad1681f Fix:修复节点文本存在svg不支持的实体字符时小地图无法渲染的问题 2024-04-02 09:00:09 +08:00
街角小林
92894d0341 Demo: update 2024-04-01 20:08:15 +08:00
街角小林
58dc232ebf Doc: update 2024-04-01 20:04:06 +08:00
街角小林
5abf09b560 Doc: update 2024-04-01 17:49:57 +08:00
街角小林
6694dffa06 update 2024-04-01 14:51:49 +08:00
街角小林
3673c6aafe Demo:支持添加附件内容(在线Demo不开放) 2024-04-01 13:53:09 +08:00
街角小林
979299f2e2 Feat:节点内容支持设置附件 2024-04-01 13:51:05 +08:00
街角小林
c0f69e038a Feat:节点内容支持设置附件 2024-04-01 13:50:20 +08:00
街角小林
80727b759d Fix:修复搜索时全部替换操作报错的问题 2024-03-29 18:02:02 +08:00
街角小林
57fe315345 打包0.9.9-fix.2 2024-03-29 16:16:08 +08:00
街角小林
231dbc00bc Demo:修复侧边栏大纲点击全屏编辑时打开的是源码编辑模式的问题 2024-03-29 16:02:09 +08:00
街角小林
02957e1fcf Fix:修复开启彩虹线条时切换结构会报错的问题 2024-03-29 15:51:32 +08:00
街角小林
38576a4860 Fix:修复插入父节点操作时原节点样式为更新的问题 2024-03-29 15:44:02 +08:00
街角小林
9b26ca9290 打包0.9.9-fix.1版本,修复搜索插件无法搜索的问题 2024-03-29 14:46:19 +08:00
街角小林
d36ff55335 打包0.9.9 2024-03-28 19:47:37 +08:00
街角小林
1ca6a34edf Doc: update 2024-03-28 19:31:49 +08:00
街角小林
c6f8f38648 Demo:导出png、pdf、svg支持设置底部自定义文字 2024-03-28 19:06:10 +08:00
街角小林
614aa1ec30 Feat:addContentToHeader方法支持返回空数据 2024-03-28 18:55:40 +08:00
街角小林
f0c08c7953 Feat:新增导出图片时添加自定义内容的实例化选项 2024-03-28 13:48:08 +08:00
街角小林
b2d5a626c7 Doc: update 2024-03-27 19:16:00 +08:00
街角小林
102cbeb821 Demo:新增源码编辑模式 2024-03-27 19:13:59 +08:00
街角小林
bff683cb5c Doc: update 2024-03-26 14:00:48 +08:00
街角小林
bf9cb99441 Fix:修复第一次创建关联线时,箭头颜色不正确的问题 2024-03-26 13:40:33 +08:00
街角小林
e966e5d57c Demo:update 2024-03-26 12:11:47 +08:00
街角小林
740c898bb1 Feat:实例化及setData方法支持传入空的data,画布空白显示 2024-03-26 12:08:20 +08:00
街角小林
3243e366b0 Doc: update 2024-03-26 10:43:26 +08:00
街角小林
7c6b67e8fb Feat:协同编辑时的人员头像增加鼠标事件 2024-03-25 18:08:59 +08:00
街角小林
3b4195acc5 Feat:节点实例新增getAncestorNodes方法用于获取祖先节点列表 2024-03-25 15:38:19 +08:00
街角小林
8b68b1fc48 Feat:节点中的图标添加鼠标移入和移出事件 2024-03-25 15:13:28 +08:00
街角小林
4614a87bdd Demo:支持配置彩虹线条 2024-03-25 15:03:53 +08:00
街角小林
c87c169dab Feat:新增彩虹线条插件 2024-03-25 15:03:38 +08:00
街角小林
bc6bf2f8f9 Feat:思维导图实例增加增量更新画布数据的方法 2024-03-22 09:30:37 +08:00
街角小林
2e5d17de16 update 2024-03-22 09:03:00 +08:00
街角小林
a12e72117e Doc: update 2024-03-22 08:58:15 +08:00
街角小林
b3d16a60b8 Doc: update 2024-03-21 09:29:08 +08:00
街角小林
2f91ea7199 Feat:增加beforeShortcutRun实例化选项用于拦截快捷键操作 2024-03-13 15:31:50 +08:00
街角小林
1085473463 Feat:支持insert键插入下级节点 2024-03-13 15:17:35 +08:00
街角小林
4c7dafe94e Doc: update 2024-03-13 15:10:17 +08:00
街角小林
f52e39eb16 打包demo 2024-03-12 18:01:10 +08:00
街角小林
27d3e977db 更新项目依赖 2024-03-12 18:00:58 +08:00
街角小林
3e59fa6ade 打包0.9.8 2024-03-08 14:01:11 +08:00
街角小林
c80916d0f2 Doc: update 2024-03-08 11:46:35 +08:00
街角小林
7bd73ba157 Fix:修复自由拖拽时,前进后退操作对节点位置不生效的问题 2024-03-08 11:44:29 +08:00
街角小林
6055a04ec5 代码优化 2024-03-08 11:15:02 +08:00
街角小林
a114631a66 Doc: update 2024-03-08 10:09:48 +08:00
街角小林
e22f67a831 Feat:修改协同编辑节点操作的更新逻辑 2024-03-08 10:06:15 +08:00
街角小林
792811f39e Doc: update 2024-03-07 11:36:05 +08:00
街角小林
ea29dad6fd Demo:新增txt文件的导出 2024-03-07 11:28:15 +08:00
街角小林
660ec00ca7 Feat:新增支持txt文件的导出 2024-03-07 11:27:52 +08:00
街角小林
2baa500c17 Fix:优化markdown的导出,修复概要丢失的问题 2024-03-07 11:27:09 +08:00
街角小林
70b6b0052f Demo:修复导入弹窗选择了一个文件后再把它删除实际上并没有删掉的问题 2024-03-07 10:40:21 +08:00
街角小林
798591f6f9 update 2024-03-07 10:36:39 +08:00
街角小林
f0b73d635e update 2024-03-07 10:34:15 +08:00
街角小林
0b049c5294 Doc: update 2024-03-07 09:40:50 +08:00
街角小林
4bf43ff338 Feat:概要节点增加uid字段 2024-03-06 16:43:45 +08:00
街角小林
58a3faae74 Fix:修复协同编辑插件:当选中一个节点时,再将该节点收起,该节点激活状态已消失,但其他客户端该节点的选中状态依旧存在的问题 2024-03-06 11:09:38 +08:00
街角小林
f3fe2dbc7b Feat:增加协同编辑节点操作同步前的生命周期函数配置信息 2024-03-06 10:05:00 +08:00
街角小林
a72d2e6748 Feat:增加协同编辑时同一节点不能多人选中的配置选项 2024-03-06 09:22:17 +08:00
街角小林
8c07209cea Fix:修复节点数据中根节点设置了expand:false时只渲染根节点的问题 2024-03-05 10:00:42 +08:00
街角小林
280afa6a73 Doc: update 2024-03-05 09:03:35 +08:00
街角小林
fd85085cb7 Demo: update 2024-02-28 14:39:55 +08:00
街角小林
95d7a3ac41 Demo: update 2024-02-28 14:13:41 +08:00
街角小林
a295d257d7 Demo:支持扫描电脑本地文件夹 2024-02-28 14:03:00 +08:00
街角小林
460d4ea558 Fix:修复删除正在编辑中的节点时实际上删除的是相邻节点的问题 2024-02-27 16:42:07 +08:00
街角小林
8b90557f70 Fix:修复某些情况下搜索时数据改变,搜索结果没有更新的问题 2024-02-27 10:15:09 +08:00
街角小林
c0fea992a9 update 2024-02-27 09:33:59 +08:00
街角小林
8bdb59c3ea 打包demo 2024-02-27 09:32:36 +08:00
街角小林
c4a846a195 Fix:修复某些情况下搜索时数据改变,搜索结果没有更新的问题 2024-02-27 09:17:00 +08:00
街角小林
7e3a1e405e '打包demo' 2024-02-26 18:25:57 +08:00
街角小林
952472a977 Feat:新增搜索所有节点(包含被收起的节点)的配置;搜索默认改为搜索所有节点; 2024-02-26 18:19:47 +08:00
街角小林
403aae4b3d Feat:1.节点实例新增高亮和取消高亮的方法;2.调整只读模式搜索高亮节点的方式;Fix:修复只读模式搜索高亮节点时收起节点高亮框未消失的问题; 2024-02-26 17:32:07 +08:00
街角小林
7999b5c260 Doc: update 2024-02-26 16:52:38 +08:00
街角小林
cdc5c7aa81 Demo:修改主题和暗色的关联逻辑 2024-02-26 16:50:00 +08:00
街角小林
9a8cd1dd24 Fix:修复导入某些旧版xmind文件时报错的问题 2024-02-23 15:06:07 +08:00
街角小林
c308cc7d44 update README 2024-02-22 16:03:33 +08:00
街角小林
1c0fe5ac8d 打包Demo 2024-02-22 10:51:35 +08:00
街角小林
44413b00fd Demo:修复打开标签弹窗、备注弹窗后点击遮罩关闭弹窗后快捷键会生效的问题 2024-02-22 10:39:40 +08:00
街角小林
8487e148ea Feat:INSERT_NODE、INSERT_MULIT_NODE、INSERT_CHILD_NODE、INSERT_MULIT_CHILD_NODE命令不会覆盖指定新插入节点数据的uid 2024-02-22 10:18:00 +08:00
街角小林
3effff95fa Fix:修复当画布大小改变后,限制思维导图在画布内和滚动条位置计算功能不正确的问题 2024-02-22 10:05:51 +08:00
街角小林
dd52873106 Doc: update 2024-02-21 11:48:22 +08:00
街角小林
a2cd6e0864 Doc: update 2024-02-21 09:55:37 +08:00
240 changed files with 12719 additions and 5042 deletions

5
Dockerfile Normal file
View File

@@ -0,0 +1,5 @@
FROM nginx
RUN mkdir /app
COPY ./index.html /app/
COPY ./dist /app/dist/
COPY nginx.conf /etc/nginx/nginx.conf

123
README.md
View File

@@ -11,19 +11,17 @@
本项目包含两部分:
1.一个 js 思维导图库,不依赖任何框架,可以使用它来快速完成 Web 思维导图产品的开发。
1.一个 js 思维导图库,不依赖任何框架,可以使用它来快速完成 Web 思维导图产品的开发。
开发文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)。
2.一个 Web 思维导图基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,所以你可以直接把它当做一个在线版思维导图应用使用,如果觉得 github 的响应速度慢,你也可以部署到你的服务器上
2.一个 Web 思维导图基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,可以当做一个在线版思维导图应用使用,也可以部署和二次开发
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)。
外也提供了客户端可供下载使用,支持`Windows``Mac``Linux`,下载地址:
外也提供了客户端可供下载使用,支持`Windows``Mac``Linux`,下载地址:
Github[releases](https://github.com/wanglin2/mind-map/releases)。
百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
Github[releases](https://github.com/wanglin2/mind-map/releases)。百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
> 客户端版本会落后于在线版本,尝试最新功能请优先使用在线版。
@@ -33,13 +31,26 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴(横向、竖向)、鱼骨图等结构
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
- [x] 节点内容支持文本(普通文本、富文本)、图片、图标、超链接、备注、标签、概要、数学公式
- [x] 节点支持拖拽(拖拽移动、自由调整)、多种节点形状支持使用 DDM 完全自定义节点内容
- [x] 节点支持拖拽(拖拽移动、自由调整)、多种节点形状;支持扩展节点内容、支持使用 DDM 完全自定义节点内容
- [x] 支持画布拖动、缩放
- [x] 支持鼠标按键拖动选择和 Ctrl+左键两种多选节点方式
- [x] 支持导出为`json``png``svg``pdf``markdown``xmind`,支持从`json``xmind``markdown`导入
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条
- [x] 支持导出为`json``png``svg``pdf``markdown``xmind``txt`,支持从`json``xmind``markdown`导入
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条、手绘风格、彩虹线条
- [x] 提供丰富的配置,满足各种场景各种使用习惯
- [x] 支持协同编辑
- [x] 支持演示模式
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
> RichText节点富文本插件、Select鼠标多选节点插件、Drag节点拖拽插件、AssociativeLine关联线插件、Export导出插件、KeyboardNavigation键盘导航插件、MiniMap小地图插件、Watermark水印插件、TouchEvent移动端触摸事件支持插件、NodeImgAdjust拖拽调整节点图片大小插件、Search搜索插件、Painter节点格式刷插件、Scrollbar滚动条插件、Formula数学公式插件、Cooperate协同编辑插件、RainbowLines彩虹线条插件、Demonstrate演示模式插件、HandDrawnLikeStyle手绘风格插件[收费]
本项目不会实现的特性:
> 1.自由节点,即多个根节点;
>
> 2.概要节点后面继续添加节点;
>
> 如果你需要以上特性,那么本库可能无法满足你的需求。
# 安装
@@ -86,20 +97,22 @@ const mindMap = new MindMap({
# License
[MIT](./LICENSE)
保留`mind-map`版权声明的情况下可随意商用。
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用。如不想保留可联系作者。
# 微信交流群
群聊人数较多,无法通过二维码入群,可以微信添加`wanglinguanfang`拉你入群。
群聊人数较多,无法通过二维码入群,可以微信添加`wanglinguanfang`拉你入群。思维导图相关问题皆可在群里提问,不必私聊作者。
# star
如果喜欢本项目,欢迎点个 star这对我们很重要。
[![Star History Chart](https://api.star-history.com/svg?repos=wanglin2/mind-map&type=Date)](https://star-history.com/#wanglin2/mind-map&Date)
# 请作者喝杯咖啡
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
<p>
@@ -272,4 +285,84 @@ 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/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/俊奇.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/pluvet.jpg" style="width: 50px;height: 50px;" />
<span>pluvet</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>SR</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>LiuJL</span>
</span>
<span>
<img src="./web/src/assets/avatar/L.jpg" style="width: 50px;height: 50px;" />
<span>L</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>sunniberg</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/阿晨.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/庆国.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/子豪.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>
</p>

View File

@@ -13,3 +13,4 @@ if (fs.existsSync(src)) {
fs.unlinkSync(src)
}
console.warn('请检查手绘风格选项是否开启!!!')

2
dist/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/img/L.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
dist/img/pluvet.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
dist/img/俊奇.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
dist/img/在下青铜五.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
dist/img/子豪.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
dist/img/孟照星.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
dist/img/宏涛.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
dist/img/庆国.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
dist/img/木星二号.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
dist/img/橘半.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
dist/img/皇登攀.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
dist/img/逆水行舟.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
dist/img/阿晨.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
dist/img/风格.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

2
dist/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/chunk-16bd07ee.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/chunk-1ba6eabc.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/chunk-2d0a4b03.js vendored Normal file
View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0a4b03"],{"0805":function(t,s,_){"use strict";_.r(s);var a=function(){var t=this;t._self._c;return t._m(0)},v=[function(){var t=this,s=t._self._c;return s("div",[s("h1",[t._v("快捷键操作如何传递自定义参数")]),s("p",[t._v("库提供了很多命令,比如插入子节点的"),s("code",[t._v("INSERT_CHILD_NODE")]),t._v("等这些命令大多可以接收一定参数比如在插入节点时我想指定初始文本和节点uid那么可以这样调用")]),s("pre",{staticClass:"hljs"},[s("code",[t._v("mindMap.execCommand("),s("span",{staticClass:"hljs-string"},[t._v("'INSERT_CHILD_NODE'")]),t._v(", "),s("span",{staticClass:"hljs-literal"},[t._v("true")]),t._v(", [], {\n "),s("span",{staticClass:"hljs-attr"},[t._v("text")]),t._v(": "),s("span",{staticClass:"hljs-string"},[t._v("'初始文本'")]),t._v(",\n "),s("span",{staticClass:"hljs-attr"},[t._v("uid")]),t._v(": "),s("span",{staticClass:"hljs-string"},[t._v("'xxx'")]),t._v("\n})\n")])]),s("p",[t._v("但是同时库内部也默认注册了很多快捷键,比如插入下级节点的"),s("code",[t._v("Tab")]),t._v("快捷键,很遗憾,目前快捷键操作无法让你传入自定义的参数,那么该怎么办呢,可以这样处理,首先确定你要给什么快捷键传入参数,比如"),s("code",[t._v("Tab")]),t._v(",那么首先可以调用如下方法删除库默认注册的快捷键:")]),s("pre",{staticClass:"hljs"},[s("code",[s("span",{staticClass:"hljs-keyword"},[t._v("const")]),t._v(" keyName = "),s("span",{staticClass:"hljs-string"},[t._v("'Tab'")]),t._v("\nmindMap.keyCommand.removeShortcut(keyName)\n")])]),s("p",[t._v("然后再重新注册即可:")]),s("pre",{staticClass:"hljs"},[s("code",[t._v("mindMap.keyCommand.addShortcut(keyName, "),s("span",{staticClass:"hljs-function"},[t._v("() =>")]),t._v(" {\n mindMap.execCommand("),s("span",{staticClass:"hljs-string"},[t._v("'INSERT_CHILD_NODE'")]),t._v(", "),s("span",{staticClass:"hljs-literal"},[t._v("true")]),t._v(", [], {\n "),s("span",{staticClass:"hljs-attr"},[t._v("text")]),t._v(": "),s("span",{staticClass:"hljs-string"},[t._v("'初始文本'")]),t._v(",\n "),s("span",{staticClass:"hljs-attr"},[t._v("uid")]),t._v(": "),s("span",{staticClass:"hljs-string"},[t._v("'xxx'")]),t._v("\n })\n})\n")])]),s("p",[t._v("库内部默认注册的快捷键对应的命令一览:")]),s("table",[s("thead",[s("tr",[s("th",[t._v("快捷键")]),s("th",[t._v("命令")])])]),s("tbody",[s("tr",[s("td",[t._v("Control+z")]),s("td",[t._v("BACK")])]),s("tr",[s("td",[t._v("Control+y")]),s("td",[t._v("FORWARD")])]),s("tr",[s("td",[t._v("Tab")]),s("td",[t._v("INSERT_CHILD_NODE")])]),s("tr",[s("td",[t._v("Insert")]),s("td",[t._v("INSERT_CHILD_NODE")])]),s("tr",[s("td",[t._v("Enter")]),s("td",[t._v("INSERT_NODE")])]),s("tr",[s("td",[t._v("Shift+Tab")]),s("td",[t._v("INSERT_PARENT_NODE")])]),s("tr",[s("td",[t._v("Control+g")]),s("td",[t._v("ADD_GENERALIZATION")])]),s("tr",[s("td",[t._v("Del或Backspace")]),s("td",[t._v("REMOVE_NODE")])]),s("tr",[s("td",[t._v("Shift+Backspace")]),s("td",[t._v("REMOVE_CURRENT_NODE")])]),s("tr",[s("td",[t._v("Control+a")]),s("td",[t._v("SELECT_ALL")])]),s("tr",[s("td",[t._v("Control+l")]),s("td",[t._v("RESET_LAYOUT")])]),s("tr",[s("td",[t._v("Control+Up")]),s("td",[t._v("UP_NODE")])]),s("tr",[s("td",[t._v("Control+Down")]),s("td",[t._v("DOWN_NODE")])])])])])}],n={},l=n,r=_("2877"),d=Object(r["a"])(l,a,v,!1,null,null,null);s["default"]=d.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/chunk-2d0b1be7.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/chunk-2d0c213a.js vendored Normal file
View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0c213a"],{4987:function(s,n,a){"use strict";a.r(n);var i=function(){var s=this;s._self._c;return s._m(0)},t=[function(){var s=this,n=s._self._c;return n("div",[n("h1",[s._v("RainbowLines插件")]),n("blockquote",[n("p",[s._v("v0.9.9+")])]),n("p",[s._v("该插件用于实现彩虹线条。")]),n("p",[s._v("开启彩虹线条及自定义颜色可以通过实例化选项"),n("code",[s._v("rainbowLinesConfig")]),s._v("设置。")]),n("p",[s._v("默认的颜色列表如下:")]),n("pre",{staticClass:"hljs"},[n("code",[s._v("[\n 'rgb(255, 213, 73)',\n 'rgb(255, 136, 126)',\n 'rgb(107, 225, 141)',\n 'rgb(151, 171, 255)',\n 'rgb(129, 220, 242)',\n 'rgb(255, 163, 125)',\n 'rgb(152, 132, 234)'\n]\n")])]),n("h2",[s._v("注册")]),n("pre",{staticClass:"hljs"},[n("code",[n("span",{staticClass:"hljs-keyword"},[s._v("import")]),s._v(" MindMap "),n("span",{staticClass:"hljs-keyword"},[s._v("from")]),s._v(" "),n("span",{staticClass:"hljs-string"},[s._v("'simple-mind-map'")]),s._v("\n"),n("span",{staticClass:"hljs-keyword"},[s._v("import")]),s._v(" RainbowLines "),n("span",{staticClass:"hljs-keyword"},[s._v("from")]),s._v(" "),n("span",{staticClass:"hljs-string"},[s._v("'simple-mind-map/src/plugins/RainbowLines.js'")]),s._v("\nMindMap.usePlugin(RainbowLines)\n")])]),n("p",[s._v("注册完且实例化"),n("code",[s._v("MindMap")]),s._v("后可通过"),n("code",[s._v("mindMap.rainbowLines")]),s._v("获取到该实例。")]),n("h2",[s._v("方法")]),n("h3",[s._v("updateRainLinesConfig(config = {})")]),n("p",[s._v("如果你在通过实例化选项"),n("code",[s._v("rainbowLinesConfig")]),s._v("设置了彩虹线条后想修改,那么可以使用该方法,参数"),n("code",[s._v("config")]),s._v("同"),n("code",[s._v("rainbowLinesConfig")]),s._v("。")]),n("pre",{staticClass:"hljs"},[n("code",[s._v("{\n "),n("span",{staticClass:"hljs-attr"},[s._v("open")]),s._v(": "),n("span",{staticClass:"hljs-literal"},[s._v("false")]),s._v(","),n("span",{staticClass:"hljs-comment"},[s._v("// 是否开启彩虹线条")]),s._v("\n "),n("span",{staticClass:"hljs-attr"},[s._v("colorsList")]),s._v(": []"),n("span",{staticClass:"hljs-comment"},[s._v("// 自定义彩虹线条的颜色列表,如果不设置,会使用默认颜色列表")]),s._v("\n}\n")])]),n("h3",[s._v("getColorsList()")]),n("p",[s._v("获取当前使用的彩虹线条颜色列表。")]),n("h3",[s._v("getNodeColor(node)")]),n("p",[s._v("获取指定的节点实例对应的彩虹线条颜色。")]),n("h3",[s._v("getSecondLayerAncestor(node)")]),n("p",[s._v("获取一个节点实例的第二层级的祖先节点实例。")])])}],v={},_=v,o=a("2877"),e=Object(o["a"])(_,i,t,!1,null,null,null);n["default"]=e.exports}}]);

1
dist/js/chunk-2d0d36df.js vendored Normal file
View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d36df"],{"5d71":function(e,t,n){"use strict";n.r(t);var i=function(){var e=this;e._self._c;return e._m(0)},o=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("Demonstrate plugin")]),t("blockquote",[t("p",[e._v("v0.9.11+")])]),t("p",[e._v("The "),t("code",[e._v("Demonstrate")]),e._v(" plugin provides demonstration functionality.")]),t("p",[e._v("When entering demonstration mode, the container elements will be automatically displayed in full screen, and then default to the root node. You can switch between the previous and next steps by pressing the left and right arrow keys on the keyboard, and exit demonstration mode by pressing the 'Esc' key.")]),t("p",[e._v("After entering demonstration mode, all shortcut keys on the mind map will be unavailable, and the mouse will not be able to operate the mind map.")]),t("h2",[e._v("Register")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-keyword"},[e._v("import")]),e._v(" MindMap "),t("span",{staticClass:"hljs-keyword"},[e._v("from")]),e._v(" "),t("span",{staticClass:"hljs-string"},[e._v("'simple-mind-map'")]),e._v("\n"),t("span",{staticClass:"hljs-keyword"},[e._v("import")]),e._v(" Demonstrate "),t("span",{staticClass:"hljs-keyword"},[e._v("from")]),e._v(" "),t("span",{staticClass:"hljs-string"},[e._v("'simple-mind-map/src/plugins/Demonstrate.js'")]),e._v("\n\nMindMap.usePlugin(Demonstrate)\n")])]),t("p",[e._v("After registration and instantiation of "),t("code",[e._v("MindMap")]),e._v(", the instance can be obtained through "),t("code",[e._v("mindMap.demonstrate")]),e._v(".")]),t("h3",[e._v("Config")]),t("p",[e._v("This plugin provides some configuration items for configuration, which can be configured through the instantiation option 'demonstrateConfig'. Please refer to the 【Instantiation options】 section in the 【Constructor】 section for details.")]),t("h3",[e._v("Event")]),t("p",[e._v("The plugin will dispatch the following events:")]),t("p",[t("code",[e._v("exit_demonstrate")]),e._v("Triggered when exiting the demonstration.")]),t("p",[t("code",[e._v("demonstrate_jump")]),e._v("Triggered when jumping.")]),t("p",[e._v("Please refer to the 'on' function in the 【Instance methods】 section of the 【Constructor】 chapter for details.")]),t("h2",[e._v("Props")]),t("h3",[e._v("stepList")]),t("p",[e._v("List of all steps demonstrated. Available when the 'enter' method is called.")]),t("h3",[e._v("currentStepIndex")]),t("p",[e._v("The index of the steps currently played, counting from 0.")]),t("h3",[e._v("config")]),t("p",[e._v("The current configuration of the plugin.")]),t("h2",[e._v("Methods")]),t("h3",[e._v("enter()")]),t("p",[e._v("Entering demonstration mode will automatically display the container elements in full screen.")]),t("h3",[e._v("exit()")]),t("p",[e._v("Exit demonstration mode, which can also be exited by pressing the 'Esc' key.")]),t("h3",[e._v("prev()")]),t("p",[e._v("Previous step.")]),t("h3",[e._v("next()")]),t("p",[e._v("Next step.")]),t("h3",[e._v("jump(index)")]),t("ul",[t("li",[t("code",[e._v("index")]),e._v("NumberTo jump to a certain step, count from 0.")])]),t("p",[e._v("Jump to a certain step.")])])}],s={},a=s,r=n("2877"),l=Object(r["a"])(a,i,o,!1,null,null,null);t["default"]=l.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0db0f2"],{"6df4":function(n,v,_){"use strict";_.r(v);var e=function(){var n=this;n._self._c;return n._m(0)},o=[function(){var n=this,v=n._self._c;return v("div",[v("h1",[n._v("Command实例")]),v("p",[v("code",[n._v("command")]),n._v("实例负责命令的添加及执行,内置了很多命令,也可以自行添加,命令指需要在历史堆栈数据里添加副本的操作。可通过"),v("code",[n._v("mindMap.command")]),n._v("获取到该实例")]),v("h2",[n._v("方法")]),v("h3",[n._v("add(name, fn)")]),v("p",[n._v("添加命令。")]),v("p",[v("code",[n._v("name")]),n._v(":命令名称")]),v("p",[v("code",[n._v("fn")]),n._v(":命令要执行的方法")]),v("h3",[n._v("remove(name, fn)")]),v("p",[n._v("移除命令。")]),v("p",[v("code",[n._v("name")]),n._v(":要移除的命令名称")]),v("p",[v("code",[n._v("fn")]),n._v(":要移除的方法,不传的话移除该命令所有的方法")]),v("h3",[n._v("getCopyData()")]),v("p",[n._v("获取渲染树数据副本")]),v("h3",[n._v("clearHistory()")]),v("p",[n._v("清空历史堆栈数据")])])}],a={},c=a,d=_("2877"),p=Object(d["a"])(c,e,o,!1,null,null,null);v["default"]=p.exports}}]);
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0db0f2"],{"6df4":function(v,_,e){"use strict";e.r(_);var n=function(){var v=this;v._self._c;return v._m(0)},o=[function(){var v=this,_=v._self._c;return _("div",[_("h1",[v._v("Command 实例")]),_("p",[_("code",[v._v("command")]),v._v("实例负责命令的添加及执行,内置了很多命令,也可以自行添加,命令指需要在历史堆栈数据里添加副本的操作。可通过"),_("code",[v._v("mindMap.command")]),v._v("获取到该实例")]),_("h2",[v._v("方法")]),_("h3",[v._v("pause()")]),_("blockquote",[_("p",[v._v("v0.9.11+")])]),_("p",[v._v("暂停收集历史数据。")]),_("h3",[v._v("recovery()")]),_("blockquote",[_("p",[v._v("v0.9.11+")])]),_("p",[v._v("恢复收集历史数据。")]),_("h3",[v._v("add(name, fn)")]),_("p",[v._v("添加命令。")]),_("p",[_("code",[v._v("name")]),v._v(":命令名称")]),_("p",[_("code",[v._v("fn")]),v._v(":命令要执行的方法")]),_("h3",[v._v("remove(name, fn)")]),_("p",[v._v("移除命令。")]),_("p",[_("code",[v._v("name")]),v._v(":要移除的命令名称")]),_("p",[_("code",[v._v("fn")]),v._v(":要移除的方法,不传的话移除该命令所有的方法")]),_("h3",[v._v("getCopyData()")]),_("p",[v._v("获取渲染树数据副本")]),_("h3",[v._v("clearHistory()")]),_("p",[v._v("清空历史堆栈数据")])])}],c={},p=c,a=e("2877"),d=Object(a["a"])(p,n,o,!1,null,null,null);_["default"]=d.exports}}]);

1
dist/js/chunk-2d0dd7d2.js vendored Normal file
View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0dd7d2"],{8235:function(n,s,t){"use strict";t.r(s);var i=function(){var n=this;n._self._c;return n._m(0)},e=[function(){var n=this,s=n._self._c;return s("div",[s("h1",[n._v("RainbowLines plugin")]),s("blockquote",[s("p",[n._v("v0.9.9+")])]),s("p",[n._v("This plugin is used to implement rainbow lines.")]),s("p",[n._v("Enabling rainbow lines and custom colors can be set through the instantiation option 'rainbowLinesConfig'.")]),s("p",[n._v("The default color list is as follows:")]),s("pre",{staticClass:"hljs"},[s("code",[n._v("[\n 'rgb(255, 213, 73)',\n 'rgb(255, 136, 126)',\n 'rgb(107, 225, 141)',\n 'rgb(151, 171, 255)',\n 'rgb(129, 220, 242)',\n 'rgb(255, 163, 125)',\n 'rgb(152, 132, 234)'\n]\n")])]),s("h2",[n._v("Register")]),s("pre",{staticClass:"hljs"},[s("code",[s("span",{staticClass:"hljs-keyword"},[n._v("import")]),n._v(" MindMap "),s("span",{staticClass:"hljs-keyword"},[n._v("from")]),n._v(" "),s("span",{staticClass:"hljs-string"},[n._v("'simple-mind-map'")]),n._v("\n"),s("span",{staticClass:"hljs-keyword"},[n._v("import")]),n._v(" RainbowLines "),s("span",{staticClass:"hljs-keyword"},[n._v("from")]),n._v(" "),s("span",{staticClass:"hljs-string"},[n._v("'simple-mind-map/src/plugins/RainbowLines.js'")]),n._v("\nMindMap.usePlugin(RainbowLines)\n")])]),s("p",[n._v("After registration and instantiation of "),s("code",[n._v("MindMap")]),n._v(", the instance can be obtained through "),s("code",[n._v("mindMap.rainbowLines")]),n._v(".")]),s("h2",[n._v("Method")]),s("h3",[n._v("updateRainLinesConfig(config = {})")]),s("p",[n._v("If you want to modify the rainbow lines after setting them through the instantiation option 'rainbowLinesConfig', you can use this method, option "),s("code",[n._v("config")]),n._v(" is same with "),s("code",[n._v("rainbowLinesConfig")]),n._v("。")]),s("pre",{staticClass:"hljs"},[s("code",[n._v("{\n "),s("span",{staticClass:"hljs-attr"},[n._v("open")]),n._v(": "),s("span",{staticClass:"hljs-literal"},[n._v("false")]),n._v(","),s("span",{staticClass:"hljs-comment"},[n._v("// Is turn on rainbow lines")]),n._v("\n "),s("span",{staticClass:"hljs-attr"},[n._v("colorsList")]),n._v(": []"),s("span",{staticClass:"hljs-comment"},[n._v("// Customize the color list for rainbow lines. If not set, the default color list will be used")]),n._v("\n}\n")])]),s("h3",[n._v("getColorsList()")]),s("p",[n._v("Get a list of currently used rainbow line colors.")]),s("h3",[n._v("getNodeColor(node)")]),s("p",[n._v("Retrieve the rainbow line color corresponding to the specified node instance.")]),s("h3",[n._v("getSecondLayerAncestor(node)")]),s("p",[n._v("Retrieve the second level ancestor node instance of a node instance.")])])}],o={},a=o,l=t("2877"),r=Object(l["a"])(a,i,e,!1,null,null,null);s["default"]=r.exports}}]);

View File

@@ -1 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e5089"],{9381:function(e,a,n){"use strict";n.r(a);var o=function(){var e=this;e._self._c;return e._m(0)},d=[function(){var e=this,a=e._self._c;return a("div",[a("h1",[e._v("command instance")]),a("p",[e._v("The "),a("code",[e._v("command")]),e._v(" instance is responsible for adding and executing commands. It includes many built-in commands and can also be added manually. A command refers to an operation that needs to add a copy to the history stack data. The "),a("code",[e._v("mindMap.command")]),e._v(' instance can be obtained through this."')]),a("h2",[e._v("Methods")]),a("h3",[e._v("add(name, fn)")]),a("p",[e._v("Add a command.")]),a("p",[a("code",[e._v("name")]),e._v(": Command name")]),a("p",[a("code",[e._v("fn")]),e._v(": Method to be executed by the command")]),a("h3",[e._v("remove(name, fn)")]),a("p",[e._v("Remove a command.")]),a("p",[a("code",[e._v("name")]),e._v(": Name of the command to be removed")]),a("p",[a("code",[e._v("fn")]),e._v(": Method to be removed, if not provided all methods for the command will be removed")]),a("h3",[e._v("getCopyData()")]),a("p",[e._v("Get a copy of the rendering tree data")]),a("h3",[e._v("clearHistory()")]),a("p",[e._v("Clear the history stack data")])])}],t={},c=t,m=n("2877"),v=Object(m["a"])(c,o,d,!1,null,null,null);a["default"]=v.exports}}]);
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e5089"],{9381:function(e,o,a){"use strict";a.r(o);var n=function(){var e=this;e._self._c;return e._m(0)},t=[function(){var e=this,o=e._self._c;return o("div",[o("h1",[e._v("command instance")]),o("p",[e._v("The "),o("code",[e._v("command")]),e._v(" instance is responsible for adding and executing commands. It includes many built-in commands and can also be added manually. A command refers to an operation that needs to add a copy to the history stack data. The "),o("code",[e._v("mindMap.command")]),e._v(' instance can be obtained through this."')]),o("h2",[e._v("Methods")]),o("h3",[e._v("pause()")]),o("blockquote",[o("p",[e._v("v0.9.11+")])]),o("p",[e._v("Pause collecting historical data.")]),o("h3",[e._v("recovery()")]),o("blockquote",[o("p",[e._v("v0.9.11+")])]),o("p",[e._v("Restore the collection of historical data.")]),o("h3",[e._v("add(name, fn)")]),o("p",[e._v("Add a command.")]),o("p",[o("code",[e._v("name")]),e._v(": Command name")]),o("p",[o("code",[e._v("fn")]),e._v(": Method to be executed by the command")]),o("h3",[e._v("remove(name, fn)")]),o("p",[e._v("Remove a command.")]),o("p",[o("code",[e._v("name")]),e._v(": Name of the command to be removed")]),o("p",[o("code",[e._v("fn")]),e._v(": Method to be removed, if not provided all methods for the command will be removed")]),o("h3",[e._v("getCopyData()")]),o("p",[e._v("Get a copy of the rendering tree data")]),o("h3",[e._v("clearHistory()")]),o("p",[e._v("Clear the history stack data")])])}],d={},c=d,v=a("2877"),m=Object(v["a"])(c,n,t,!1,null,null,null);o["default"]=m.exports}}]);

1
dist/js/chunk-2d0e96e3.js vendored Normal file
View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e96e3"],{"8e00":function(_,v,e){"use strict";e.r(v);var s=function(){var _=this;_._self._c;return _._m(0)},t=[function(){var _=this,v=_._self._c;return v("div",[v("h1",[_._v("Demonstrate 插件")]),v("blockquote",[v("p",[_._v("v0.9.11+")])]),v("p",[v("code",[_._v("Demonstrate")]),_._v("插件提供演示功能。")]),v("p",[_._v("进入演示模式时会自动将容器元素全屏,然后默认聚焦到根节点,可通过键盘方向键的左右来切换上一步和下一步,可通过"),v("code",[_._v("Esc")]),_._v("键退出演示模式。")]),v("p",[_._v("进入演示模式后思维导图所有的快捷键都将无法使用,鼠标也无法操作思维导图。")]),v("h2",[_._v("注册")]),v("pre",{staticClass:"hljs"},[v("code",[v("span",{staticClass:"hljs-keyword"},[_._v("import")]),_._v(" MindMap "),v("span",{staticClass:"hljs-keyword"},[_._v("from")]),_._v(" "),v("span",{staticClass:"hljs-string"},[_._v("'simple-mind-map'")]),_._v("\n"),v("span",{staticClass:"hljs-keyword"},[_._v("import")]),_._v(" Demonstrate "),v("span",{staticClass:"hljs-keyword"},[_._v("from")]),_._v(" "),v("span",{staticClass:"hljs-string"},[_._v("'simple-mind-map/src/plugins/Demonstrate.js'")]),_._v("\n\nMindMap.usePlugin(Demonstrate)\n")])]),v("p",[_._v("注册完且实例化"),v("code",[_._v("MindMap")]),_._v("后可通过"),v("code",[_._v("mindMap.demonstrate")]),_._v("获取到该实例。")]),v("h3",[_._v("配置")]),v("p",[_._v("该插件提供了一些配置项可供配置,可以通过实例化选项"),v("code",[_._v("demonstrateConfig")]),_._v("进行配置。详见【构造函数】篇章的【实例化选项】小节。")]),v("h3",[_._v("事件")]),v("p",[_._v("该插件会派发如下事件:")]),v("p",[v("code",[_._v("exit_demonstrate")]),_._v(":退出演示时触发。")]),v("p",[v("code",[_._v("demonstrate_jump")]),_._v(":跳转时触发。")]),v("p",[_._v("详见【构造函数】篇章的【实例方法】小节"),v("code",[_._v("on")]),_._v("函数。")]),v("h2",[_._v("属性")]),v("h3",[_._v("stepList")]),v("p",[_._v("演示的所有步骤列表。当调用了"),v("code",[_._v("enter")]),_._v("方法后可用。")]),v("h3",[_._v("currentStepIndex")]),v("p",[_._v("当前播放到的步骤索引从0开始计数。")]),v("h3",[_._v("config")]),v("p",[_._v("插件当前的配置。")]),v("h2",[_._v("方法")]),v("h3",[_._v("enter()")]),v("p",[_._v("进入演示模式,会自动将容器元素全屏。")]),v("h3",[_._v("exit()")]),v("p",[_._v("退出演示模式,通过"),v("code",[_._v("Esc")]),_._v("键也可退出。")]),v("h3",[_._v("prev()")]),v("p",[_._v("上一步。")]),v("h3",[_._v("next()")]),v("p",[_._v("下一步。")]),v("h3",[_._v("jump(index)")]),v("ul",[v("li",[v("code",[_._v("index")]),_._v("Number要跳转到的某一步从0开始计数。")])]),v("p",[_._v("跳转到某一步。")])])}],n={},p=n,a=e("2877"),o=Object(a["a"])(p,s,t,!1,null,null,null);v["default"]=o.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0f0784"],{"9d03":function(e,t,n){"use strict";n.r(t);var i=function(){var e=this;e._self._c;return e._m(0)},d=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit instance")]),t("p",[e._v("Node text editing instance. It can be obtained through "),t("code",[e._v("mindMap.renderer.textEdit")]),e._v(".")]),t("h2",[e._v("Methods")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("Get whether the current text editing box is in a display state, that is, whether it is in a text editing state.")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("Hiding the text editing box will set the content of the current text editing box as node text.")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("Register temporary shortcut keys, which means editing can be completed through the Enter and Tab keys.")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v("Node instance to enter for editing")])]),t("p",[e._v("Manually enable node editing. By default, it will enter node editing when double clicking or pressing F2 on the node.")])])}],o={},h=o,r=n("2877"),s=Object(r["a"])(h,i,d,!1,null,null,null);t["default"]=s.exports}}]);
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0f0784"],{"9d03":function(e,t,n){"use strict";n.r(t);var i=function(){var e=this;e._self._c;return e._m(0)},d=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit instance")]),t("p",[e._v("Node text editing instance. It can be obtained through "),t("code",[e._v("mindMap.renderer.textEdit")]),e._v(".")]),t("h2",[e._v("Methods")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("Get whether the current text editing box is in a display state, that is, whether it is in a text editing state.")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("Hiding the text editing box will set the content of the current text editing box as node text.")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("Register temporary shortcut keys, which means editing can be completed through the Enter and Tab keys.")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v("Node instance to enter for editing")])]),t("p",[e._v("Manually enable node editing. By default, it will enter node editing when double clicking or pressing F2 on the node.")]),t("h3",[e._v("getCurrentEditNode()")]),t("blockquote",[t("p",[e._v("v0.9.8+")])]),t("p",[e._v("Get the node instance currently being edited.")])])}],o={},r=o,h=n("2877"),s=Object(h["a"])(r,i,d,!1,null,null,null);t["default"]=s.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216037"],{c13f:function(s,t,n){"use strict";n.r(t);var a=function(){var s=this;s._self._c;return s._m(0)},e=[function(){var s=this,t=s._self._c;return t("div",[t("h1",[s._v("开启节点自由拖拽")]),t("blockquote",[t("p",[s._v("节点自由拖拽的连线可能不会符合你的预期,这个问题基本上不会改,所以自由拖拽功能不建议使用。")])]),t("p",[s._v("节点支持自由拖拽,也就是可以把它拖动到你指定的位置,默认是不开启的,如果需要开启可以在实例化"),t("code",[s._v("simple-mind-map")]),s._v("时传入开启的选项:")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-keyword"},[s._v("new")]),s._v(" MindMap({\n "),t("span",{staticClass:"hljs-comment"},[s._v("// ...")]),s._v("\n "),t("span",{staticClass:"hljs-attr"},[s._v("enableFreeDrag")]),s._v(": "),t("span",{staticClass:"hljs-literal"},[s._v("true")]),s._v("\n})\n")])]),t("p",[s._v("也可以动态切换是否开启:")]),t("pre",{staticClass:"hljs"},[t("code",[s._v("mindMap.updateConfig({\n "),t("span",{staticClass:"hljs-attr"},[s._v("enableFreeDrag")]),s._v(": "),t("span",{staticClass:"hljs-literal"},[s._v("true")]),t("span",{staticClass:"hljs-comment"},[s._v("// false")]),s._v("\n})\n")])]),t("p",[s._v("自由拖拽很容器将节点拖的乱七八糟,所以也可以通过命令回到默认的位置:")]),t("pre",{staticClass:"hljs"},[t("code",[s._v("mindMap.execCommand("),t("span",{staticClass:"hljs-string"},[s._v("'RESET_LAYOUT'")]),s._v(")\n")])]),t("p",[s._v("也可以使用快捷键"),t("code",[s._v("Ctrl + L")]),s._v("来恢复。")]),t("h2",[s._v("完整示例")]),t("iframe",{staticStyle:{width:"100%",height:"455px",border:"none"},attrs:{src:"https://wanglin2.github.io/playground/#eNrFVd1uG0UUfpVhEVoH2buuxJVxSksbJKSEolAuUDeqxrtje8rszLIzm8SyVgoF1EKIFASiFdxAVQFCQFWBEE2oeJmsnb4FZ3b2L44veldLa82c853znZnzM1PrchQ52wmxelZf+jGNFJJEJdFFj9MwErFCUxSTYRsJviESrkjQRnKMGRM7m2SIUjSMRYhs8GBXFhuUBxs4MirPkiBmpBOCtBPiyLM8jpDHGVFIyzRyFfGEMSN3XZQ9/Tr7/GD2397sr+P5d5+efnF7fvtJdvfe6Y+/eNwXXCqEfUW3yTsiIBKs64haN7ZWPF6iqLwWEf5WTMjVGI8ACEdpDTGTJAdpqrt3ZgcPZvcfZYc/Zf/uZYePTu/8Ov/m8Wz/29n+09KPEqMRIw0/rRW0ehFNdcRnOZxtzBICiJeWyTW+OLOTRAFW5IrgQzpq5Z4QIhwPap7eUt8QuEoh/LQ4wezjB9nDg2fH90//eJj980n2eK+MOiaQynU8EYk6G3IZAtkl/hURhpgHLXtz7b216zfXL39w7f3rdum/ynrrvLnOGtkps10dgfVQIPwkJFw5I6LWGNHLNydvA0dhCYdWmHIS2yttYwVXgXvGu/55lhZ4VkNkxIrsKi32rNkPT0xdmHrSv7RwpoH+mLIgJlyDb9Q+FtwtZVlkOjn6cn708yLZWcIlpFu1rol7QRGUy0JW2lFO1aYQSjfSu0JSRQUHS5uRobLbyPYhdZCmrQK+WJ95K2lVuvK66V6EoCTn33+VHf5m4jVdfHK0f3L8d7P2BG/ZHFhvmk4GspbetpH+X6dS1fWGmt1eNViJM/RQr/D1XTPDYHrBRhEYPNBjsEOoH9Bt5DMs5apnFUFcJaHwrFxdAGhQa6sqBUjfBW0TWHpSQrAB1hCj9FR/kCglOLrkM+p/mEOawwOQ0+niYHoD2dlnfz6793tz+NioB+JzM8lGadp3DUlBCjEtkjZ6HxjPz4gFD/X5ylXfbVwfbKWaMHOTl4ox71mOa2Z7MQIcIkPHl9KzqmJwGjddJnOHBmrcQxe63VdyHEJRVXkxAUZIda7Ii1Z/Ly9mpHRVG+KBFCxRxhAhXb891C12SkT15jz9mNDRGOCvdbvRbsm8nPfVkjnE8YgCb+k1wkFAObSEEVShO0WBPGfEF8oIiqCrPTiE4s5zYLUtkwH9mjq3pODweOfuvUIBGagmimfB22zGiOPC0olhntOQ6GR1BrHYkSQGJ55VtPiS91rbjpWKZM912e5HXE6kI6Ts+LwzIPQWHNvBjE4S7kvHF6ELnUGUXFIbmqY4TGql/wPpBfrv"}})])}],l={},r=l,i=n("2877"),p=Object(i["a"])(r,a,e,!1,null,null,null);t["default"]=p.exports}}]);
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216037"],{c13f:function(s,t,a){"use strict";a.r(t);var n=function(){var s=this;s._self._c;return s._m(0)},e=[function(){var s=this,t=s._self._c;return t("div",[t("h1",[s._v("开启节点自由拖拽")]),t("blockquote",[t("p",[s._v("节点自由拖拽的连线可能不会符合你的预期,这个问题基本上不会改,所以自由拖拽功能不建议使用。")])]),t("blockquote",[t("p",[s._v("另外不要把节点拖拽和自由拖拽搞混,节点拖拽指拖动节点到其他节点上成为其子节点或兄弟节点,自由拖拽只是可以把节点放置在你拖动到的画布位置,并不能改变节点的层级。")])]),t("blockquote",[t("p",[s._v("最后要注意这两个功能都需要先注册Drag插件。")])]),t("p",[s._v("节点支持自由拖拽,也就是可以把它拖动到你指定的位置,默认是不开启的,如果需要开启可以在实例化"),t("code",[s._v("simple-mind-map")]),s._v("时传入开启的选项:")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-keyword"},[s._v("new")]),s._v(" MindMap({\n "),t("span",{staticClass:"hljs-comment"},[s._v("// ...")]),s._v("\n "),t("span",{staticClass:"hljs-attr"},[s._v("enableFreeDrag")]),s._v(": "),t("span",{staticClass:"hljs-literal"},[s._v("true")]),s._v("\n})\n")])]),t("p",[s._v("也可以动态切换是否开启:")]),t("pre",{staticClass:"hljs"},[t("code",[s._v("mindMap.updateConfig({\n "),t("span",{staticClass:"hljs-attr"},[s._v("enableFreeDrag")]),s._v(": "),t("span",{staticClass:"hljs-literal"},[s._v("true")]),t("span",{staticClass:"hljs-comment"},[s._v("// false")]),s._v("\n})\n")])]),t("p",[s._v("自由拖拽很容器将节点拖的乱七八糟,所以也可以通过命令回到默认的位置:")]),t("pre",{staticClass:"hljs"},[t("code",[s._v("mindMap.execCommand("),t("span",{staticClass:"hljs-string"},[s._v("'RESET_LAYOUT'")]),s._v(")\n")])]),t("p",[s._v("也可以使用快捷键"),t("code",[s._v("Ctrl + L")]),s._v("来恢复。")]),t("h2",[s._v("完整示例")]),t("iframe",{staticStyle:{width:"100%",height:"455px",border:"none"},attrs:{src:"https://wanglin2.github.io/playground/#eNrFVd1uG0UUfpVhEVoH2buuxJVxSksbJKSEolAuUDeqxrtje8rszLIzm8SyVgoF1EKIFASiFdxAVQFCQFWBEE2oeJmsnb4FZ3b2L44veldLa82c853znZnzM1PrchQ52wmxelZf+jGNFJJEJdFFj9MwErFCUxSTYRsJviESrkjQRnKMGRM7m2SIUjSMRYhs8GBXFhuUBxs4MirPkiBmpBOCtBPiyLM8jpDHGVFIyzRyFfGEMSN3XZQ9/Tr7/GD2397sr+P5d5+efnF7fvtJdvfe6Y+/eNwXXCqEfUW3yTsiIBKs64haN7ZWPF6iqLwWEf5WTMjVGI8ACEdpDTGTJAdpqrt3ZgcPZvcfZYc/Zf/uZYePTu/8Ov/m8Wz/29n+09KPEqMRIw0/rRW0ehFNdcRnOZxtzBICiJeWyTW+OLOTRAFW5IrgQzpq5Z4QIhwPap7eUt8QuEoh/LQ4wezjB9nDg2fH90//eJj980n2eK+MOiaQynU8EYk6G3IZAtkl/hURhpgHLXtz7b216zfXL39w7f3rdum/ynrrvLnOGtkps10dgfVQIPwkJFw5I6LWGNHLNydvA0dhCYdWmHIS2yttYwVXgXvGu/55lhZ4VkNkxIrsKi32rNkPT0xdmHrSv7RwpoH+mLIgJlyDb9Q+FtwtZVlkOjn6cn708yLZWcIlpFu1rol7QRGUy0JW2lFO1aYQSjfSu0JSRQUHS5uRobLbyPYhdZCmrQK+WJ95K2lVuvK66V6EoCTn33+VHf5m4jVdfHK0f3L8d7P2BG/ZHFhvmk4GspbetpH+X6dS1fWGmt1eNViJM/RQr/D1XTPDYHrBRhEYPNBjsEOoH9Bt5DMs5apnFUFcJaHwrFxdAGhQa6sqBUjfBW0TWHpSQrAB1hCj9FR/kCglOLrkM+p/mEOawwOQ0+niYHoD2dlnfz6793tz+NioB+JzM8lGadp3DUlBCjEtkjZ6HxjPz4gFD/X5ylXfbVwfbKWaMHOTl4ox71mOa2Z7MQIcIkPHl9KzqmJwGjddJnOHBmrcQxe63VdyHEJRVXkxAUZIda7Ii1Z/Ly9mpHRVG+KBFCxRxhAhXb891C12SkT15jz9mNDRGOCvdbvRbsm8nPfVkjnE8YgCb+k1wkFAObSEEVShO0WBPGfEF8oIiqCrPTiE4s5zYLUtkwH9mjq3pODweOfuvUIBGagmimfB22zGiOPC0olhntOQ6GR1BrHYkSQGJ55VtPiS91rbjpWKZM912e5HXE6kI6Ts+LwzIPQWHNvBjE4S7kvHF6ELnUGUXFIbmqY4TGql/wPpBfrv"}})])}],l={},r=l,i=a("2877"),p=Object(i["a"])(r,n,e,!1,null,null,null);t["default"]=p.exports}}]);

View File

@@ -1 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216f87"],{c576:function(e,t,n){"use strict";n.r(t);var _=function(){var e=this;e._self._c;return e._m(0)},v=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit 实例")]),t("p",[e._v("节点文本编辑实例。可以通过"),t("code",[e._v("mindMap.renderer.textEdit")]),e._v("获取到。")]),t("h2",[e._v("方法")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("获取当前文本编辑框是否处于显示状态,也就是是否处在文本编辑状态。")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("隐藏文本编辑框,会将当前文本编辑框中的内容设置为节点文本。")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("注册临时快捷键,也就是可以通过 Enter 键和 Tab 键完成编辑。")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":要进入编辑的节点实例")])]),t("p",[e._v("手动开启节点编辑。默认会在节点双击、按 F2 时进入节点编辑。")])])}],i={},r=i,d=n("2877"),o=Object(d["a"])(r,_,v,!1,null,null,null);t["default"]=o.exports}}]);
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216f87"],{c576:function(e,t,v){"use strict";v.r(t);var _=function(){var e=this;e._self._c;return e._m(0)},n=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit 实例")]),t("p",[e._v("节点文本编辑实例。可以通过"),t("code",[e._v("mindMap.renderer.textEdit")]),e._v("获取到。")]),t("h2",[e._v("方法")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("获取当前文本编辑框是否处于显示状态,也就是是否处在文本编辑状态。")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("隐藏文本编辑框,会将当前文本编辑框中的内容设置为节点文本。")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("注册临时快捷键,也就是可以通过 Enter 键和 Tab 键完成编辑。")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":要进入编辑的节点实例")])]),t("p",[e._v("手动开启节点编辑。默认会在节点双击、按 F2 时进入节点编辑。")]),t("h3",[e._v("getCurrentEditNode()")]),t("blockquote",[t("p",[e._v("v0.9.8+")])]),t("p",[e._v("获取当前正在编辑中的节点实例。")])])}],i={},o=i,r=v("2877"),d=Object(r["a"])(o,_,n,!1,null,null,null);t["default"]=d.exports}}]);

File diff suppressed because one or more lines are too long

1
dist/js/chunk-2d21f249.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/chunk-66b27c16.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

76
dist/js/chunk-8bd0d5d4.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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?e76cdbdcf61c14ff4fc9" rel="stylesheet"><link href="dist/css/app.css?e76cdbdcf61c14ff4fc9" 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?78d97b280dca5d10b8bc" rel="stylesheet"><link href="dist/css/app.css?78d97b280dca5d10b8bc" 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?e76cdbdcf61c14ff4fc9"></script><script src="dist/js/app.js?e76cdbdcf61c14ff4fc9"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?78d97b280dca5d10b8bc"></script><script src="dist/js/app.js?78d97b280dca5d10b8bc"></script></body></html>

30
nginx.conf Normal file
View File

@@ -0,0 +1,30 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /app;
index index.html;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}

View File

@@ -15,6 +15,7 @@ import Search from './src/plugins/Search.js'
import Painter from './src/plugins/Painter.js'
import Scrollbar from './src/plugins/Scrollbar.js'
import Formula from './src/plugins/Formula.js'
import RainbowLines from './src/plugins/RainbowLines.js'
import xmind from './src/parse/xmind.js'
import markdown from './src/parse/markdown.js'
import icons from './src/svg/icons.js'
@@ -28,7 +29,7 @@ MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.9.7'
MindMap.version = '0.9.12'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)
@@ -46,5 +47,6 @@ MindMap.usePlugin(MiniMap)
.usePlugin(Painter)
.usePlugin(Scrollbar)
.usePlugin(Formula)
.usePlugin(RainbowLines)
export default MindMap

View File

@@ -10,12 +10,17 @@ import BatchExecution from './src/utils/BatchExecution'
import {
layoutValueList,
CONSTANTS,
commonCaches,
ERROR_TYPES,
cssContent
} from './src/constants/constant'
import { SVG } from '@svgdotjs/svg.js'
import { simpleDeepClone, getType, getObjectChangedProps } from './src/utils'
import {
simpleDeepClone,
getObjectChangedProps,
isUndef,
handleGetSvgDataExtraContent,
getNodeTreeBoundingRect
} from './src/utils'
import defaultTheme, {
checkIsNodeSizeIndependenceConfig
} from './src/themes/default'
@@ -31,6 +36,8 @@ class MindMap {
constructor(opt = {}) {
// 合并选项
this.opt = this.handleOpt(merge(defaultOpt, opt))
// 预处理节点数据
this.opt.data = this.handleData(this.opt.data)
// 容器元素
this.el = this.opt.el
@@ -39,6 +46,10 @@ class MindMap {
// 获取容器尺寸位置信息
this.getElRectInfo()
// 画布初始大小
this.initWidth = this.width
this.initHeight = this.height
// 添加css
this.cssEl = null
this.addCss()
@@ -88,14 +99,12 @@ class MindMap {
// 初始渲染
this.render(this.opt.fit ? () => this.view.fit() : () => {})
setTimeout(() => {
this.command.addHistory()
if (this.opt.data) this.command.addHistory()
}, 0)
}
// 配置参数处理
handleOpt(opt) {
// 深拷贝一份节点数据
opt.data = simpleDeepClone(opt.data || {})
// 检查布局配置
if (!layoutValueList.includes(opt.layout)) {
opt.layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
@@ -105,16 +114,32 @@ class MindMap {
return opt
}
// 预处理节点数据
handleData(data) {
if (isUndef(data) || Object.keys(data).length <= 0) return null
data = simpleDeepClone(data || {})
// 根节点不能收起
if (data.data && !data.data.expand) {
data.data.expand = true
}
return data
}
// 创建容器元素
initContainer() {
const { associativeLineIsAlwaysAboveNode } = this.opt
// 给容器元素添加一个类名
this.el.classList.add('smm-mind-map-container')
// 节点关联线容器
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.svg = SVG()
.addTo(this.el)
.size(this.width, this.height)
// 容器
this.draw = this.svg.group()
this.draw.addClass('smm-container')
@@ -207,19 +232,10 @@ class MindMap {
// 初始化缓存数据
initCache() {
Object.keys(commonCaches).forEach(key => {
let type = getType(commonCaches[key])
let value = ''
switch (type) {
case 'Boolean':
value = false
break
default:
value = null
break
}
commonCaches[key] = value
})
this.commonCaches = {
measureCustomNodeContentSizeEl: null,
measureRichtextNodeTextSizeEl: null
}
}
// 设置主题
@@ -302,9 +318,17 @@ class MindMap {
this.command.exec(...args)
}
// 更新画布数据,如果新的数据是在当前画布节点数据基础上增删改查后形成的,那么可以使用该方法来更新画布数据
updateData(data) {
this.renderer.setData(data)
this.render()
this.command.addHistory()
}
// 动态设置思维导图数据,纯节点数据
setData(data) {
data = simpleDeepClone(data || {})
data = this.handleData(data)
this.opt.data = data
this.execCommand('CLEAR_ACTIVE_NODE')
this.command.clearHistory()
this.command.addHistory()
@@ -386,7 +410,19 @@ class MindMap {
}
// 获取svg数据
getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false } = {}) {
getSvgData({
paddingX = 0,
paddingY = 0,
ignoreWatermark = false,
addContentToHeader,
addContentToFooter,
node
} = {}) {
const { cssTextList, header, headerHeight, footer, footerHeight } =
handleGetSvgDataExtraContent({
addContentToHeader,
addContentToFooter
})
const svg = this.svg
const draw = this.draw
// 保存原始信息
@@ -398,9 +434,21 @@ class MindMap {
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
// 获取变换后的位置尺寸信息其实是getBoundingClientRect方法的包装方法
const rect = draw.rbox()
// 需要裁减的区域
let clipData = null
if (node) {
clipData = getNodeTreeBoundingRect(
node,
rect.x,
rect.y,
paddingX,
paddingY
)
}
// 内边距
const fixHeight = 0
rect.width += paddingX * 2
rect.height += paddingY * 2
rect.height += paddingY * 2 + fixHeight + headerHeight + footerHeight
draw.translate(paddingX, paddingY)
// 将svg设置为实际内容的宽高
svg.size(rect.width, rect.height)
@@ -438,7 +486,21 @@ class MindMap {
this.watermark.isInExport = false
}
// 添加必要的样式
clone.add(SVG(`<style>${cssContent}</style>`))
;[cssContent, ...cssTextList].forEach(s => {
clone.add(SVG(`<style>${s}</style>`))
})
// 附加内容
if (header && headerHeight > 0) {
clone.findOne('.smm-container').translate(0, headerHeight)
header.width(rect.width)
header.y(paddingY)
clone.add(header, 0)
}
if (footer && footerHeight > 0) {
footer.width(rect.width)
footer.y(rect.height - paddingY - footerHeight)
clone.add(footer)
}
// 修正defs里定义的元素的id因为clone时defs里的元素的id会继续递增导致和内容中引用的id对不上
const defs = svg.find('defs')
const defs2 = clone.find('defs')
@@ -462,6 +524,7 @@ class MindMap {
return {
svg: clone, // 思维导图图形的整体svg元素包括svg画布容器、g实际的思维导图组
svgHTML: clone.svg(), // svg字符串
clipData,
rect: {
...rect, // 思维导图图形未缩放时的位置尺寸等信息
ratio: rect.width / rect.height // 思维导图图形的宽高比
@@ -529,6 +592,8 @@ class MindMap {
this.svg.remove()
// 去除给容器元素设置的背景样式
Style.removeBackgroundStyle(this.el)
// 移除给容器元素添加的类名
this.el.classList.remove('smm-mind-map-container')
this.el.innerHTML = ''
this.el = null
this.removeCss()

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.9.7",
"version": "0.9.12",
"description": "一个简单的web在线思维导图",
"authors": [
{
@@ -38,6 +38,7 @@
"quill": "^1.3.6",
"tern": "^0.24.3",
"uuid": "^9.0.0",
"ws": "^7.5.9",
"xml-js": "^1.6.11",
"y-webrtc": "^10.2.5",
"yjs": "^13.6.8"

View File

@@ -312,15 +312,11 @@ export const nodeDataNoStylePropList = [
'associativeLineTargets',
'associativeLineTargetControlOffsets',
'associativeLinePoint',
'associativeLineText'
'associativeLineText',
'attachmentUrl',
'attachmentName'
]
// 数据缓存
export const commonCaches = {
measureCustomNodeContentSizeEl: null,
measureRichtextNodeTextSizeEl: null
}
// 错误类型
export const ERROR_TYPES = {
READ_CLIPBOARD_ERROR: 'read_clipboard_error',
@@ -346,7 +342,7 @@ export const cssContent = `
display: block;
}
.smm-node.active .smm-hover-node{
.smm-node.active .smm-hover-node, .smm-node-highlight .smm-hover-node{
display: block;
opacity: 1;
stroke-width: 2;

View File

@@ -275,5 +275,56 @@ export const defaultOpt = {
customCreateNodePolygon: null,
// 自定义转换节点连线路径的方法
// 接收svg path字符串返回转换后的svg path字符串
customTransformNodeLinePath: null
customTransformNodeLinePath: null,
// 是否仅搜索当前渲染的节点,被收起的节点不会被搜索到
isOnlySearchCurrentRenderNodes: false,
// 协同编辑时,同一个节点不能同时被多人选中
onlyOneEnableActiveNodeOnCooperate: false,
// 协同编辑时,节点操作即将更新到其他客户端前的生命周期函数
// 函数接收一个对象作为参数:
/*
{
type: createOrUpdate创建节点或更新节点、delete删除节点
data: 1.当type=createOrUpdate时代表被创建或被更新的节点数据即将同步到其他客户端所以你可以修改该数据2.当type=delete时代表被删除的节点数据
}
*/
beforeCooperateUpdate: null,
// 快捷键操作即将执行前的生命周期函数返回true可以阻止操作执行
// 函数接收两个参数key快捷键、activeNodeList当前激活的节点列表
beforeShortcutRun: null,
// 彩虹线条配置需要先注册RainbowLines插件
rainbowLinesConfig: {
open: false, // 是否开启彩虹线条
colorsList: [] // 自定义彩虹线条的颜色列表,如果不设置,会使用默认颜色列表
/*
[
'rgb(255, 213, 73)',
'rgb(255, 136, 126)',
'rgb(107, 225, 141)',
'rgb(151, 171, 255)',
'rgb(129, 220, 242)',
'rgb(255, 163, 125)',
'rgb(152, 132, 234)'
]
*/
},
// 导出png、svg、pdf时在头部和尾部添加自定义内容
// 可传递一个函数这个函数可以返回null代表不添加内容也可以返回如下数据
/*
{
el,// 要追加的自定义DOM节点样式可内联
cssText,// 可选如果样式不想内联可以传递该值一个css字符串
height: 50// 返回的DOM节点的高度必须传递
}
*/
addContentToHeader: null,
addContentToFooter: null,
// 演示插件配置
demonstrateConfig: null,
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
resetScaleOnMoveNodeToCenter: false,
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
createNodePrefixContent: null,
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
createNodePostfixContent: null
}

View File

@@ -23,6 +23,18 @@ class Command {
this.mindMap.opt.addHistoryTime,
this
)
// 是否暂停收集历史数据
this.isPause = false
}
// 暂停收集历史数据
pause() {
this.isPause = true
}
// 恢复收集历史数据
recovery() {
this.isPause = false
}
// 清空历史数据
@@ -88,13 +100,14 @@ class Command {
// 添加回退数据
addHistory() {
if (this.mindMap.opt.readonly) {
if (this.mindMap.opt.readonly || this.isPause) {
return
}
const lastData =
this.history.length > 0 ? this.history[this.history.length - 1] : null
const data = this.getCopyData()
// 此次数据和上次一样则不重复添加
if (lastData === data) return
if (lastData && JSON.stringify(lastData) === JSON.stringify(data)) {
return
}
@@ -158,6 +171,7 @@ class Command {
// 获取渲染树数据副本
getCopyData() {
if (!this.mindMap.renderer.renderTree) return null
return copyRenderTree({}, this.mindMap.renderer.renderTree, true)
}

View File

@@ -67,9 +67,10 @@ export default class KeyCommand {
// 按键事件
onKeydown(e) {
const { enableShortcutOnlyWhenMouseInSvg, beforeShortcutRun } = this.mindMap.opt
if (
this.isPause ||
(this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
(enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
) {
return
}
@@ -80,6 +81,10 @@ export default class KeyCommand {
e.stopPropagation()
e.preventDefault()
}
if (typeof beforeShortcutRun === 'function') {
const isStop = beforeShortcutRun(key, [...this.mindMap.renderer.activeNodeList])
if (isStop) return
}
this.shortcutMap[key].forEach(fn => {
fn()
})

View File

@@ -30,7 +30,8 @@ import {
createSmmFormatData,
checkSmmFormatData,
checkIsNodeStyleDataKey,
removeRichTextStyes
removeRichTextStyes,
formatGetNodeGeneralization
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
@@ -65,7 +66,9 @@ class Render {
this.mindMap = opt.mindMap
this.themeConfig = this.mindMap.themeConfig
// 渲染树,操作过程中修改的都是这里的数据
this.renderTree = merge({}, this.mindMap.opt.data || {})
this.renderTree = this.mindMap.opt.data
? merge({}, this.mindMap.opt.data)
: null
// 是否重新渲染
this.reRender = false
// 是否正在渲染中
@@ -117,7 +120,7 @@ class Render {
// 重新设置思维导图数据
setData(data) {
if (this.mindMap.richText) {
this.renderTree = this.mindMap.richText.handleSetData(data)
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
} else {
this.renderTree = data
}
@@ -246,6 +249,9 @@ class Render {
// 设置节点备注
this.setNodeNote = this.setNodeNote.bind(this)
this.mindMap.command.add('SET_NODE_NOTE', this.setNodeNote)
// 设置节点附件
this.setNodeAttachment = this.setNodeAttachment.bind(this)
this.mindMap.command.add('SET_NODE_ATTACHMENT', this.setNodeAttachment)
// 设置节点标签
this.setNodeTag = this.setNodeTag.bind(this)
this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag)
@@ -290,6 +296,10 @@ class Render {
this.mindMap.keyCommand.addShortcut('Tab', () => {
this.mindMap.execCommand('INSERT_CHILD_NODE')
})
// 插入下级节点
this.mindMap.keyCommand.addShortcut('Insert', () => {
this.mindMap.execCommand('INSERT_CHILD_NODE')
})
// 插入同级节点
this.mindMap.keyCommand.addShortcut('Enter', () => {
this.mindMap.execCommand('INSERT_NODE')
@@ -326,7 +336,7 @@ class Render {
})
// 一键整理布局
this.mindMap.keyCommand.addShortcut('Control+l', () => {
this.mindMap.execCommand('RESET_LAYOUT', this.resetLayout)
this.mindMap.execCommand('RESET_LAYOUT')
})
// 上移节点
this.mindMap.keyCommand.addShortcut('Control+Up', () => {
@@ -432,6 +442,12 @@ class Render {
if (this.reRender) {
this.clearActiveNodeList()
}
// 如果没有节点数据
if (!this.renderTree) {
this.isRendering = false
this.mindMap.emit('node_tree_render_end')
return
}
// 计算布局
this.layout.doLayout(root => {
// 删除本次渲染时不再需要的节点
@@ -476,6 +492,7 @@ class Render {
// 给当前被收起来的节点数据添加文本复位标志
resetUnExpandNodeStyle() {
if (!this.renderTree) return
walk(this.renderTree, null, node => {
if (!node.data.expand) {
walk(node, null, node2 => {
@@ -504,9 +521,17 @@ class Render {
}
// 添加节点到激活列表里
addNodeToActiveList(node) {
addNodeToActiveList(node, notEmitBeforeNodeActiveEvent = false) {
if (
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
node.userList.length > 0
)
return
const index = this.findActiveNodeIndex(node)
if (index === -1) {
if (!notEmitBeforeNodeActiveEvent) {
this.mindMap.emit('before_node_active', node, this.activeNodeList)
}
this.mindMap.execCommand('SET_NODE_ACTIVE', node, true)
this.activeNodeList.push(node)
}
@@ -649,7 +674,7 @@ class Render {
uid: createUid(),
...(appointData || {})
},
children: [...createUidForAppointNodes(appointChildren, true)]
children: [...createUidForAppointNodes(appointChildren)]
}
parent.nodeData.children.splice(index + 1, 0, newNodeData)
})
@@ -685,10 +710,7 @@ class Render {
const parent = node.parent
// 计算插入位置
const index = getNodeDataIndex(node)
const newNodeList = createUidForAppointNodes(
simpleDeepClone(nodeList),
true
)
const newNodeList = createUidForAppointNodes(simpleDeepClone(nodeList))
parent.nodeData.children.splice(index + 1, 0, ...newNodeList)
})
if (focusNewNode) {
@@ -748,7 +770,7 @@ class Render {
...params,
...(appointData || {})
},
children: [...createUidForAppointNodes(appointChildren, true)]
children: [...createUidForAppointNodes(appointChildren)]
}
node.nodeData.children.push(newNode)
// 插入子节点时自动展开子节点
@@ -788,7 +810,7 @@ class Render {
if (!node.nodeData.children) {
node.nodeData.children = []
}
childList = createUidForAppointNodes(childList, true)
childList = createUidForAppointNodes(childList)
node.nodeData.children.push(...childList)
// 插入子节点时自动展开子节点
node.setData({
@@ -843,6 +865,9 @@ class Render {
},
children: [node.nodeData]
}
node.setData({
resetRichText: true
})
const parent = node.parent
// 获取当前节点所在位置
const index = getNodeDataIndex(node)
@@ -963,12 +988,14 @@ class Render {
})
} else {
// 否则遍历整棵树
if (!this.renderTree) return
walk(this.renderTree, null, node => {
const _hasCustomStyles = this._handleRemoveCustomStyles(node.data)
if (_hasCustomStyles) hasCustomStyles = true
// 不要忘记概要节点
if (node.data.generalization && node.data.generalization.length > 0) {
node.data.generalization.forEach(generalizationData => {
const generalizationList = formatGetNodeGeneralization(node.data)
if (generalizationList.length > 0) {
generalizationList.forEach(generalizationData => {
const _hasCustomStyles =
this._handleRemoveCustomStyles(generalizationData)
if (_hasCustomStyles) hasCustomStyles = true
@@ -1214,9 +1241,17 @@ class Render {
root.nodeData.children = []
} else {
// 如果只选中了一个节点,删除后激活其兄弟节点或者父节点
needActiveNode = this.getNextActiveNode()
needActiveNode = this.getNextActiveNode(list)
for (let i = 0; i < list.length; i++) {
let node = list[i]
const node = list[i]
const currentEditNode = this.textEdit.getCurrentEditNode()
if (
currentEditNode &&
currentEditNode.getData('uid') === node.getData('uid')
) {
// 如果当前节点正在编辑中,那么先完成编辑
this.textEdit.hideEditTextBox()
}
if (isAppointNodes) list.splice(i, 1)
if (node.isGeneralization) {
this.deleteNodeGeneralization(node)
@@ -1261,13 +1296,13 @@ class Render {
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
return
}
// 删除节点后需要激活的节点,如果只选中了一个节点,删除后激活其兄弟节点或者父节点
let needActiveNode = this.getNextActiveNode()
let isAppointNodes = appointNodes.length > 0
let list = isAppointNodes ? appointNodes : this.activeNodeList
list = list.filter(node => {
return !node.isRoot
})
// 删除节点后需要激活的节点,如果只选中了一个节点,删除后激活其兄弟节点或者父节点
let needActiveNode = this.getNextActiveNode(list)
for (let i = 0; i < list.length; i++) {
let node = list[i]
if (node.isGeneralization) {
@@ -1293,7 +1328,11 @@ class Render {
}
// 计算下一个可激活的节点
getNextActiveNode() {
getNextActiveNode(deleteList) {
// 删除多个节点不自动激活相邻节点
if (deleteList.length !== 1) return null
// 被删除的节点不在当前激活的节点列表里,不激活相邻节点
if (this.findActiveNodeIndex(deleteList[0]) === -1) return null
let needActiveNode = null
if (
this.activeNodeList.length === 1 &&
@@ -1448,6 +1487,7 @@ class Render {
// 展开所有
expandAllNode() {
if (!this.renderTree) return
walk(
this.renderTree,
null,
@@ -1465,7 +1505,8 @@ class Render {
}
// 收起所有
unexpandAllNode() {
unexpandAllNode(isSetRootNodeCenter = true) {
if (!this.renderTree) return
walk(
this.renderTree,
null,
@@ -1480,12 +1521,15 @@ class Render {
0
)
this.mindMap.render(() => {
this.setRootNodeCenter()
if (isSetRootNodeCenter) {
this.setRootNodeCenter()
}
})
}
// 展开到指定层级
expandToLevel(level) {
if (!this.renderTree) return
walk(
this.renderTree,
null,
@@ -1572,6 +1616,14 @@ class Render {
})
}
// 设置节点附件
setNodeAttachment(node, url, name = '') {
this.setNodeDataRender(node, {
attachmentUrl: url,
attachmentName: name
})
}
// 设置节点标签
setNodeTag(node, tag) {
this.setNodeDataRender(node, {
@@ -1591,7 +1643,7 @@ class Render {
}
// 添加节点概要
addGeneralization(data) {
addGeneralization(data, openEdit = true) {
if (this.activeNodeList.length <= 0) {
return
}
@@ -1603,12 +1655,22 @@ class Render {
)
})
const list = parseAddGeneralizationNodeList(nodeList)
const isRichText = !!this.mindMap.richText
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
list.length > 1
)
list.forEach(item => {
const newData = {
inserting,
...(data || {
text: this.mindMap.opt.defaultGeneralizationText
}),
range: item.range || null
range: item.range || null,
uid: createUid(),
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
let generalization = item.node.getData('generalization')
if (generalization) {
@@ -1628,6 +1690,10 @@ class Render {
expand: true
})
})
// 需要清除原来激活的节点
if (focusNewNode) {
this.clearActiveNodeList()
}
this.mindMap.render(() => {
// 修复祖先节点存在概要时位置未更新的问题
// 修复同时给存在上下级关系的节点添加概要时重叠的问题
@@ -1704,7 +1770,7 @@ class Render {
if (targetNode) {
targetNode.active()
this.moveNodeToCenter(targetNode)
callback()
callback(targetNode)
}
})
}
@@ -1734,19 +1800,28 @@ class Render {
// 移动节点到画布中心
moveNodeToCenter(node) {
const { resetScaleOnMoveNodeToCenter } = this.mindMap.opt
let { transform, state } = this.mindMap.view.getTransformData()
let { left, top, width, height } = node
if (!resetScaleOnMoveNodeToCenter) {
left *= transform.scaleX
top *= transform.scaleY
width *= transform.scaleX
height *= transform.scaleY
}
let halfWidth = this.mindMap.width / 2
let halfHeight = this.mindMap.height / 2
let { left, top, width, height } = node
let nodeCenterX = left + width / 2
let nodeCenterY = top + height / 2
let { state } = this.mindMap.view.getTransformData()
let targetX = halfWidth - state.x
let targetY = halfHeight - state.y
let offsetX = targetX - nodeCenterX
let offsetY = targetY - nodeCenterY
this.mindMap.view.translateX(offsetX)
this.mindMap.view.translateY(offsetY)
this.mindMap.view.setScale(1)
if (resetScaleOnMoveNodeToCenter) {
this.mindMap.view.setScale(1)
}
}
// 回到中心主题,即设置根节点到画布中心
@@ -1756,15 +1831,29 @@ class Render {
// 展开到指定uid的节点
expandToNodeUid(uid, callback = () => {}) {
if (!this.renderTree) {
callback()
return
}
let parentsList = []
let isGeneralization = false
const cache = {}
bfsWalk(this.renderTree, (node, parent) => {
if (node.data.uid === uid) {
parentsList = parent ? [...cache[parent.data.uid], parent] : []
return 'stop'
} else {
cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent] : []
}
const generalizationList = formatGetNodeGeneralization(node.data)
generalizationList.forEach(item => {
if (item.uid === uid) {
parentsList = parent ? [...cache[parent.data.uid], parent] : []
isGeneralization = true
}
})
if (isGeneralization) {
return 'stop'
}
cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent] : []
})
let needRender = false
parentsList.forEach(node => {
@@ -1773,6 +1862,18 @@ class Render {
node.data.expand = true
}
})
// 如果是展开到概要节点,那么父节点下的所有节点都需要开
if (isGeneralization) {
const lastNode = parentsList[parentsList.length - 1]
if (lastNode) {
walk(lastNode, null, node => {
if (!node.data.expand) {
needRender = true
node.data.expand = true
}
})
}
}
if (needRender) {
this.mindMap.render(callback)
} else {
@@ -1788,12 +1889,25 @@ class Render {
res = node
return true
}
// 概要节点
let isGeneralization = false
;(node._generalizationList || []).forEach(item => {
if (item.generalizationNode.getData('uid') === uid) {
res = item.generalizationNode
isGeneralization = true
}
})
if (isGeneralization) {
return true
}
})
return res
}
// 高亮节点或子节点
highlightNode(node, range) {
// 如果当前正在渲染,那么不进行高亮,因为节点位置可能不正确
if (this.isRendering) return
const { highlightNodeBoxStyle = {} } = this.mindMap.opt
if (!this.highlightBoxNode) {
this.highlightBoxNode = new Polygon()

View File

@@ -315,4 +315,12 @@ export default class TextEdit {
this.textEditNode.style.transform = 'translateY(0)'
this.showTextEdit = false
}
// 获取当前正在编辑中的节点实例
getCurrentEditNode() {
if (this.mindMap.richText) {
return this.mindMap.richText.node
}
return this.currentNode
}
}

View File

@@ -1,6 +1,6 @@
import Style from './Style'
import Shape from './Shape'
import { G, ForeignObject, Rect } from '@svgdotjs/svg.js'
import { G, Rect } from '@svgdotjs/svg.js'
import nodeGeneralizationMethods from './nodeGeneralization'
import nodeExpandBtnMethods from './nodeExpandBtn'
import nodeCommandWrapsMethods from './nodeCommandWraps'
@@ -8,7 +8,7 @@ import nodeCreateContentsMethods from './nodeCreateContents'
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
import nodeCooperateMethods from './nodeCooperate'
import { CONSTANTS } from '../../../constants/constant'
import { copyNodeTree } from '../../../utils/index'
import { copyNodeTree, createForeignObjectNode } from '../../../utils/index'
// 节点类
class Node {
@@ -75,6 +75,9 @@ class Node {
this._noteData = null
this.noteEl = null
this.noteContentIsShow = false
this._attachmentData = null
this._prefixData = null
this._postfixData = null
this._expandBtn = null
this._lastExpandBtnType = null
this._showExpandBtn = false
@@ -181,7 +184,12 @@ class Node {
// 创建节点的各个内容对象数据
createNodeData() {
// 自定义节点内容
let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt
let {
isUseCustomNodeContent,
customCreateNodeContent,
createNodePrefixContent,
createNodePostfixContent
} = this.mindMap.opt
if (isUseCustomNodeContent && customCreateNodeContent) {
this._customNodeContent = customCreateNodeContent(this)
}
@@ -199,10 +207,19 @@ class Node {
this._hyperlinkData = this.createHyperlinkNode()
this._tagData = this.createTagNode()
this._noteData = this.createNoteNode()
this._attachmentData = this.createAttachmentNode()
this._prefixData = createNodePrefixContent
? createNodePrefixContent(this)
: null
this._postfixData = createNodePostfixContent
? createNodePostfixContent(this)
: null
}
// 计算节点的宽高
getSize() {
this.customLeft = this.getData('customLeft') || undefined
this.customTop = this.getData('customTop') || undefined
this.updateGeneralization()
this.createNodeData()
let { width, height } = this.getNodeRect()
@@ -233,6 +250,11 @@ class Node {
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height
}
// 自定义前置内容
if (this._prefixData) {
textContentWidth += this._prefixData.width
textContentHeight = Math.max(textContentHeight, this._prefixData.height)
}
// 图标
if (this._iconData.length > 0) {
textContentWidth += this._iconData.reduce((sum, cur) => {
@@ -265,6 +287,19 @@ class Node {
textContentWidth += this._noteData.width
textContentHeight = Math.max(textContentHeight, this._noteData.height)
}
// 附件
if (this._attachmentData) {
textContentWidth += this._attachmentData.width
textContentHeight = Math.max(
textContentHeight,
this._attachmentData.height
)
}
// 自定义后置内容
if (this._postfixData) {
textContentWidth += this._postfixData.width
textContentHeight = Math.max(textContentHeight, this._postfixData.height)
}
// 文字内容部分的尺寸
this._rectInfo.textContentWidth = textContentWidth
this._rectInfo.textContentHeight = textContentHeight
@@ -325,10 +360,11 @@ class Node {
}
// 如果存在自定义节点内容,那么使用自定义节点内容
if (this.isUseCustomNodeContent()) {
let foreignObject = new ForeignObject()
foreignObject.width(width)
foreignObject.height(height)
foreignObject.add(this._customNodeContent)
const foreignObject = createForeignObjectNode({
el: this._customNodeContent,
width,
height
})
this.group.add(foreignObject)
addHoverNode()
return
@@ -343,6 +379,19 @@ class Node {
// 内容节点
let textContentNested = new G()
let textContentOffsetX = 0
// 自定义前置内容
if (this._prefixData) {
const foreignObject = createForeignObjectNode({
el: this._prefixData.el,
width: this._prefixData.width,
height: this._prefixData.height
})
foreignObject
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._prefixData.height) / 2)
textContentNested.add(foreignObject)
textContentOffsetX += this._prefixData.width + textContentItemMargin
}
// icon
let iconNested = new G()
if (this._iconData && this._iconData.length > 0) {
@@ -359,9 +408,11 @@ class Node {
}
// 文字
if (this._textData) {
const oldX = this._textData.node.attr('data-offsetx') || 0
this._textData.node.attr('data-offsetx', textContentOffsetX)
// 修复safari浏览器节点存在图标时文字位置不正确的问题
;(this._textData.nodeContent || this._textData.node)
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
.x(textContentOffsetX)
.y(0)
textContentNested.add(this._textData.node)
@@ -397,6 +448,27 @@ class Node {
textContentNested.add(this._noteData.node)
textContentOffsetX += this._noteData.width
}
// 附件
if (this._attachmentData) {
this._attachmentData.node
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._attachmentData.height) / 2)
textContentNested.add(this._attachmentData.node)
textContentOffsetX += this._attachmentData.width
}
// 自定义后置内容
if (this._postfixData) {
const foreignObject = createForeignObjectNode({
el: this._postfixData.el,
width: this._postfixData.width,
height: this._postfixData.height
})
foreignObject
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._postfixData.height) / 2)
textContentNested.add(foreignObject)
textContentOffsetX += this._postfixData.width
}
// 文字内容整体
textContentNested.translate(
width / 2 - textContentNested.bbox().width / 2,
@@ -420,6 +492,12 @@ class Node {
this.isMultipleChoice = false
return
}
if (
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
this.userList.length > 0
) {
return
}
this.active(e)
})
this.group.on('mousedown', e => {
@@ -443,7 +521,7 @@ class Node {
}
}
// 多选和取消多选
if (e.ctrlKey && enableCtrlKeyNodeSelection) {
if ((e.ctrlKey || e.metaKey) && enableCtrlKeyNodeSelection) {
this.isMultipleChoice = true
let isActive = this.getData('isActive')
if (!isActive)
@@ -454,7 +532,7 @@ class Node {
)
this.mindMap.renderer[
isActive ? 'removeNodeFromActiveList' : 'addNodeToActiveList'
](this)
](this, true)
this.renderer.emitNodeActiveEvent(isActive ? null : this)
}
this.mindMap.emit('node_mousedown', this, e)
@@ -486,10 +564,14 @@ class Node {
})
// 双击事件
this.group.on('dblclick', e => {
if (this.mindMap.opt.readonly || e.ctrlKey) {
const { readonly, onlyOneEnableActiveNodeOnCooperate } = this.mindMap.opt
if (readonly || e.ctrlKey || e.metaKey) {
return
}
e.stopPropagation()
if (onlyOneEnableActiveNodeOnCooperate && this.userList.length > 0) {
return
}
this.mindMap.emit('node_dblclick', this, e)
})
// 右键菜单事件
@@ -531,10 +613,16 @@ class Node {
}
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
this.renderer.clearActiveNodeList()
this.renderer.addNodeToActiveList(this)
this.renderer.addNodeToActiveList(this, true)
this.renderer.emitNodeActiveEvent(this)
}
// 取消激活该节点
deactivate() {
this.mindMap.renderer.removeNodeFromActiveList(this)
this.mindMap.renderer.emitNodeActiveEvent()
}
// 更新节点
update() {
if (!this.group) {
@@ -542,9 +630,10 @@ class Node {
}
this.updateNodeActiveClass()
let { alwaysShowExpandBtn } = this.mindMap.opt
const childrenLength = this.nodeData.children.length
if (alwaysShowExpandBtn) {
// 需要移除展开收缩按钮
if (this._expandBtn && this.nodeData.children.length <= 0) {
if (this._expandBtn && childrenLength <= 0) {
this.removeExpandBtn()
} else {
// 更新展开收起按钮
@@ -553,7 +642,9 @@ class Node {
} else {
let { isActive, expand } = this.getData()
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
if (expand && !isActive && !this._isMouseenter) {
if (childrenLength <= 0) {
this.removeExpandBtn()
} else if (expand && !isActive && !this._isMouseenter) {
this.hideExpandBtn()
} else {
this.showExpandBtn()
@@ -705,6 +796,9 @@ class Node {
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
destroy() {
if (!this.group) return
if (this.emptyUser) {
this.emptyUser()
}
this.resetWhenDelete()
this.group.remove()
this.removeGeneralization()
@@ -901,6 +995,7 @@ class Node {
childNode.getStyle('lineWidth', true)
const color =
childNode.getSelfInhertStyle('lineColor') ||
this.getRainbowLineColor(childNode) ||
childNode.getStyle('lineColor', true)
const dasharray =
childNode.getSelfInhertStyle('lineDasharray') ||
@@ -917,6 +1012,13 @@ class Node {
)
}
// 获取彩虹线条颜色
getRainbowLineColor(node) {
return this.mindMap.rainbowLines
? this.mindMap.rainbowLines.getNodeColor(node)
: ''
}
// 移除连线
removeLine() {
this._lines.forEach(line => {
@@ -1024,6 +1126,17 @@ class Node {
return copyNodeTree({}, this, removeActiveState, removeId)
}
// 获取祖先节点列表
getAncestorNodes() {
const list = []
let parent = this.parent
while (parent) {
list.unshift(parent)
parent = parent.parent
}
return list
}
// 是否存在自定义样式
hasCustomStyle() {
return this.style.hasCustomStyle()
@@ -1052,6 +1165,16 @@ class Node {
height: height * scaleY
}
}
// 高亮节点
highlight() {
if (this.group) this.group.addClass('smm-node-highlight')
}
// 取消高亮节点
closeHighlight() {
if (this.group) this.group.removeClass('smm-node-highlight')
}
}
export default Node

View File

@@ -28,6 +28,11 @@ function setNote(note) {
this.mindMap.execCommand('SET_NODE_NOTE', this, note)
}
// 设置附件
function setAttachment(url, name) {
this.mindMap.execCommand('SET_NODE_ATTACHMENT', this, url, name)
}
// 设置标签
function setTag(tag) {
this.mindMap.execCommand('SET_NODE_TAG', this, tag)
@@ -55,6 +60,7 @@ export default {
setIcon,
setHyperlink,
setNote,
setAttachment,
setTag,
setShape,
setStyle,

View File

@@ -67,6 +67,15 @@ function updateUserListNode() {
} else {
node = this.createTextAvatar(item)
}
node.on('click', (e) => {
this.mindMap.emit('node_cooperate_avatar_click', item, this, node, e)
})
node.on('mouseenter', (e) => {
this.mindMap.emit('node_cooperate_avatar_mouseenter', item, this, node, e)
})
node.on('mouseleave', (e) => {
this.mindMap.emit('node_cooperate_avatar_mouseleave', item, this, node, e)
})
node.x(index * avatarSize).cy(-avatarSize / 2)
this._userListGroup.add(node)
})
@@ -94,11 +103,18 @@ function removeUser(userInfo) {
this.updateUserListNode()
}
// 清空用户
function emptyUser() {
this.userList = []
this.updateUserListNode()
}
export default {
createUserListNode,
updateUserListNode,
createTextAvatar,
createImageAvatar,
addUser,
removeUser
removeUser,
emptyUser
}

View File

@@ -4,19 +4,13 @@ import {
removeHtmlStyle,
addHtmlStyle,
checkIsRichText,
isUndef
isUndef,
createForeignObjectNode
} from '../../../utils'
import {
Image as SVGImage,
SVG,
A,
G,
Rect,
Text,
ForeignObject
} from '@svgdotjs/svg.js'
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
import iconsSvg from '../../../svg/icons'
import { CONSTANTS, commonCaches } from '../../../constants/constant'
import { CONSTANTS } from '../../../constants/constant'
import { defenseXSS } from '../../../utils/xss'
// 创建图片节点
function createImgNode() {
@@ -92,7 +86,13 @@ function createIconNode() {
}
node.size(iconSize, iconSize)
node.on('click', e => {
this.mindMap.emit('node_icon_click', this, item, e)
this.mindMap.emit('node_icon_click', this, item, e, node)
})
node.on('mouseenter', e => {
this.mindMap.emit('node_icon_mouseenter', this, item, e, node)
})
node.on('mouseleave', e => {
this.mindMap.emit('node_icon_mouseleave', this, item, e, node)
})
return {
node,
@@ -142,14 +142,19 @@ function createRichTextNode() {
text: text
})
}
let html = `<div>${this.getData('text')}</div>`
if (!commonCaches.measureRichtextNodeTextSizeEl) {
commonCaches.measureRichtextNodeTextSizeEl = document.createElement('div')
commonCaches.measureRichtextNodeTextSizeEl.style.position = 'fixed'
commonCaches.measureRichtextNodeTextSizeEl.style.left = '-999999px'
this.mindMap.el.appendChild(commonCaches.measureRichtextNodeTextSizeEl)
let html = `<div>${defenseXSS(this.getData('text'))}</div>`
if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) {
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl =
document.createElement('div')
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl.style.position =
'fixed'
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl.style.left =
'-999999px'
this.mindMap.el.appendChild(
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl
)
}
let div = commonCaches.measureRichtextNodeTextSizeEl
let div = this.mindMap.commonCaches.measureRichtextNodeTextSizeEl
div.innerHTML = html
let el = div.children[0]
el.classList.add('smm-richtext-node-wrap')
@@ -168,10 +173,11 @@ function createRichTextNode() {
height = Math.ceil(height)
g.attr('data-width', width)
g.attr('data-height', height)
let foreignObject = new ForeignObject()
foreignObject.width(width)
foreignObject.height(height)
foreignObject.add(div.children[0])
const foreignObject = createForeignObjectNode({
el: div.children[0],
width,
height
})
g.add(foreignObject)
return {
node: g,
@@ -256,7 +262,7 @@ function createHyperlinkNode() {
e.stopPropagation()
})
if (hyperlinkTitle) {
a.attr('title', hyperlinkTitle)
node.add(SVG(`<title>${hyperlinkTitle}</title>`))
}
// 添加一个透明的层,作为鼠标区域
a.rect(iconSize, iconSize).fill({ color: 'transparent' })
@@ -281,6 +287,9 @@ function createTagNode() {
let nodes = []
tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => {
let tag = new G()
tag.on('click', () => {
this.mindMap.emit('node_tag_click', this, item)
})
// 标签文本
let text = new Text().text(item).x(8).cy(8)
this.style.tagText(text, index)
@@ -307,7 +316,10 @@ function createNoteNode() {
return null
}
let iconSize = this.mindMap.themeConfig.iconSize
let node = new SVG().attr('cursor', 'pointer').size(iconSize, iconSize)
let node = new SVG()
.attr('cursor', 'pointer')
.addClass('smm-node-note')
.size(iconSize, iconSize)
// 透明的层,用来作为鼠标区域
node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' }))
// 备注图标
@@ -362,6 +374,36 @@ function createNoteNode() {
}
}
// 创建附件节点
function createAttachmentNode() {
const { attachmentUrl, attachmentName } = this.getData()
if (!attachmentUrl) {
return
}
const iconSize = this.mindMap.themeConfig.iconSize
const node = new SVG().attr('cursor', 'pointer').size(iconSize, iconSize)
if (attachmentName) {
node.add(SVG(`<title>${attachmentName}</title>`))
}
// 透明的层,用来作为鼠标区域
node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' }))
// 备注图标
const iconNode = SVG(iconsSvg.attachment).size(iconSize, iconSize)
this.style.iconNode(iconNode)
node.add(iconNode)
node.on('click', e => {
this.mindMap.emit('node_attachmentClick', this, e, node)
})
node.on('contextmenu', e => {
this.mindMap.emit('node_attachmentContextmenu', this, e, node)
})
return {
node,
width: iconSize,
height: iconSize
}
}
// 获取节点备注显示位置
function getNoteContentPosition() {
const iconSize = this.mindMap.themeConfig.iconSize
@@ -377,18 +419,22 @@ function getNoteContentPosition() {
// 测量自定义节点内容元素的宽高
function measureCustomNodeContentSize(content) {
if (!commonCaches.measureCustomNodeContentSizeEl) {
commonCaches.measureCustomNodeContentSizeEl = document.createElement('div')
commonCaches.measureCustomNodeContentSizeEl.style.cssText = `
if (!this.mindMap.commonCaches.measureCustomNodeContentSizeEl) {
this.mindMap.commonCaches.measureCustomNodeContentSizeEl =
document.createElement('div')
this.mindMap.commonCaches.measureCustomNodeContentSizeEl.style.cssText = `
position: fixed;
left: -99999px;
top: -99999px;
`
this.mindMap.el.appendChild(commonCaches.measureCustomNodeContentSizeEl)
this.mindMap.el.appendChild(
this.mindMap.commonCaches.measureCustomNodeContentSizeEl
)
}
commonCaches.measureCustomNodeContentSizeEl.innerHTML = ''
commonCaches.measureCustomNodeContentSizeEl.appendChild(content)
let rect = commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect()
this.mindMap.commonCaches.measureCustomNodeContentSizeEl.innerHTML = ''
this.mindMap.commonCaches.measureCustomNodeContentSizeEl.appendChild(content)
let rect =
this.mindMap.commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect()
return {
width: rect.width,
height: rect.height
@@ -409,6 +455,7 @@ export default {
createHyperlinkNode,
createTagNode,
createNoteNode,
createAttachmentNode,
getNoteContentPosition,
measureCustomNodeContentSize,
isUseCustomNodeContent

View File

@@ -51,6 +51,7 @@ function createGeneralizationNode() {
if (!cur.generalizationNode) {
cur.generalizationNode = new Node({
data: {
inserting: item.inserting,
data: item
},
uid: createUid(),
@@ -59,6 +60,7 @@ function createGeneralizationNode() {
isGeneralization: true
})
}
delete item.inserting
// 关联所属节点
cur.generalizationNode.generalizationBelongNode = this
// 大小

View File

@@ -37,7 +37,7 @@ class View {
this.mindMap.event.on('drag', (e, event) => {
// 按住ctrl键拖动为多选
// 禁用拖拽
if (e.ctrlKey || this.mindMap.opt.isDisableDrag) {
if (e.ctrlKey || e.metaKey || this.mindMap.opt.isDisableDrag) {
return
}
if (this.firstDrag) {
@@ -72,7 +72,11 @@ class View {
return customHandleMousewheel(e)
}
// 1.鼠标滚轮事件控制缩放
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM || e.ctrlKey) {
if (
mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM ||
e.ctrlKey ||
e.metaKey
) {
if (disableMouseWheelZoom) return
const { x: clientX, y: clientY } = this.mindMap.toPos(
e.clientX,
@@ -128,6 +132,10 @@ class View {
this.translateXY(mx, my)
}
})
this.mindMap.on('resize', () => {
if (!this.checkNeedMindMapInCanvas()) return
this.transform()
})
}
// 获取当前变换状态数据
@@ -154,7 +162,8 @@ class View {
...viewData.transform
})
this.mindMap.emit('view_data_change', this.getTransformData())
this.mindMap.emit('scale', this.scale)
this.emitEvent('scale')
this.emitEvent('translate')
}
}
@@ -164,6 +173,7 @@ class View {
this.x += x
this.y += y
this.transform()
this.emitEvent('translate')
}
// 平移x方向
@@ -171,12 +181,14 @@ class View {
if (step === 0) return
this.x += step
this.transform()
this.emitEvent('translate')
}
// 平移x方式到
translateXTo(x) {
this.x = x
this.transform()
this.emitEvent('translate')
}
// 平移y方向
@@ -184,12 +196,14 @@ class View {
if (step === 0) return
this.y += step
this.transform()
this.emitEvent('translate')
}
// 平移y方向到
translateYTo(y) {
this.y = y
this.transform()
this.emitEvent('translate')
}
// 应用变换
@@ -207,13 +221,17 @@ class View {
// 恢复
reset() {
let scaleChange = this.scale !== 1
const scaleChange = this.scale !== 1
const translateChange = this.x !== 0 || this.y !== 0
this.scale = 1
this.x = 0
this.y = 0
this.transform()
if (scaleChange) {
this.mindMap.emit('scale', this.scale)
this.emitEvent('scale')
}
if (translateChange) {
this.emitEvent('translate')
}
}
@@ -223,7 +241,7 @@ class View {
const scale = Math.max(this.scale - scaleRatio, 0.1)
this.scaleInCenter(scale, cx, cy)
this.transform()
this.mindMap.emit('scale', this.scale)
this.emitEvent('scale')
}
// 放大
@@ -232,7 +250,7 @@ class View {
const scale = this.scale + scaleRatio
this.scaleInCenter(scale, cx, cy)
this.transform()
this.mindMap.emit('scale', this.scale)
this.emitEvent('scale')
}
// 基于指定中心进行缩放cxcy 可不指定,此时会使用画布中心点
@@ -258,15 +276,16 @@ class View {
this.scale = scale
}
this.transform()
this.mindMap.emit('scale', this.scale)
this.emitEvent('scale')
}
// 适应画布大小
fit() {
const { fitPadding } = this.mindMap.opt
fit(getRbox = () => {}, enlarge = false, fitPadding) {
fitPadding =
fitPadding === undefined ? this.mindMap.opt.fitPadding : fitPadding
const draw = this.mindMap.draw
const origTransform = draw.transform()
const rect = draw.rbox()
const rect = getRbox() || draw.rbox()
const drawWidth = rect.width / origTransform.scaleX
const drawHeight = rect.height / origTransform.scaleY
const drawRatio = drawWidth / drawHeight
@@ -276,7 +295,7 @@ class View {
const elRatio = elWidth / elHeight
let newScale = 0
let flag = ''
if (drawWidth <= elWidth && drawHeight <= elHeight) {
if (drawWidth <= elWidth && drawHeight <= elHeight && !enlarge) {
newScale = 1
flag = 1
} else {
@@ -294,7 +313,7 @@ class View {
newScale = newWidth / drawWidth
}
this.setScale(newScale)
const newRect = draw.rbox()
const newRect = getRbox() || draw.rbox()
// 需要考虑画布容器距浏览器窗口左上角的距离
newRect.x -= this.mindMap.elRect.left
newRect.y -= this.mindMap.elRect.top
@@ -313,20 +332,31 @@ class View {
this.translateXY(newX, newY)
}
// 将思维导图限制在画布内
limitMindMapInCanvas() {
// 判断是否需要将思维导图限制在画布内
checkNeedMindMapInCanvas() {
const { isLimitMindMapInCanvasWhenHasScrollbar, isLimitMindMapInCanvas } =
this.mindMap.opt
// 如果注册了滚动条插件那么使用isLimitMindMapInCanvasWhenHasScrollbar配置
if (this.mindMap.scrollbar) {
if (!isLimitMindMapInCanvasWhenHasScrollbar) return
return isLimitMindMapInCanvasWhenHasScrollbar
} else {
// 否则使用isLimitMindMapInCanvas配置
if (!isLimitMindMapInCanvas) return
return isLimitMindMapInCanvas
}
}
// 将思维导图限制在画布内
limitMindMapInCanvas() {
if (!this.checkNeedMindMapInCanvas()) return
let { scale, left, top, right, bottom } = this.getPositionLimit()
// 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
const centerXChange =
((this.mindMap.width - this.mindMap.initWidth) / 2) * scale
const centerYChange =
((this.mindMap.height - this.mindMap.initHeight) / 2) * scale
// 如果缩放值改变了
const scaleRatio = this.scale / scale
left *= scaleRatio
@@ -338,10 +368,10 @@ class View {
const centerX = this.mindMap.width / 2
const centerY = this.mindMap.height / 2
const scaleOffset = this.scale - 1
left -= scaleOffset * centerX
right -= scaleOffset * centerX
top -= scaleOffset * centerY
bottom -= scaleOffset * centerY
left -= scaleOffset * centerX - centerXChange
right -= scaleOffset * centerX - centerXChange
top -= scaleOffset * centerY - centerYChange
bottom -= scaleOffset * centerY - centerYChange
// 判断是否超出边界
if (this.x > left) {
@@ -379,6 +409,16 @@ class View {
bottom
}
}
// 派发事件
emitEvent(type) {
switch (type) {
case 'scale':
this.mindMap.emit('scale', this.scale)
case 'translate':
this.mindMap.emit('translate', this.x, this.y)
}
}
}
export default View

View File

@@ -144,9 +144,10 @@ class Base {
this.cacheNode(newUid, newNode)
// 数据关联实际节点
data._node = newNode
if (data.data.isActive) {
this.renderer.addNodeToActiveList(newNode)
}
}
// 如果该节点数据是已激活状态,那么添加到激活节点列表里
if (data.data.isActive) {
this.renderer.addNodeToActiveList(newNode)
}
// 如果当前节点在激活节点列表里,那么添加上激活的状态
if (this.mindMap.renderer.findActiveNodeIndex(newNode) !== -1) {

View File

@@ -1,5 +1,21 @@
import { fromMarkdown } from 'mdast-util-from-markdown'
const getNodeText = node => {
// 优先找出其中的text类型的子节点
let textChild = (node.children || []).find(item => {
return item.type === 'text'
})
// 没有找到,那么直接使用第一个子节点
textChild = textChild || node.children[0]
if (textChild) {
if (textChild.value !== undefined) {
return textChild.value
}
return getNodeText(textChild)
}
return ''
}
// 处理list的情况
const handleList = node => {
let list = []
@@ -9,7 +25,7 @@ const handleList = node => {
let node = {}
node.data = {
// 节点内容
text: cur.children[0].children[0].value
text: getNodeText(cur)
}
node.children = []
newArr.push(node)
@@ -45,7 +61,7 @@ export const transformMarkdownTo = md => {
let node = {}
node.data = {
// 节点内容
text: cur.children[0].value
text: getNodeText(cur)
}
node.children = []
// 如果当前的层级大于上一个节点的层级,那么是其子节点

View File

@@ -1,12 +1,7 @@
import { walk } from '../utils'
import { walk, nodeRichTextToTextWithWrap } from '../utils'
let el = null
const getText = str => {
if (!el) {
el = document.createElement('div')
}
el.innerHTML = str
return el.textContent
const getNodeText = data => {
return data.richText ? nodeRichTextToTextWithWrap(data.text) : data.text
}
const getTitleMark = level => {
@@ -24,21 +19,22 @@ export const transformToMarkdown = root => {
root,
null,
(node, parent, isRoot, layerIndex) => {
let level = layerIndex + 1
let text = node.data.richText ? getText(node.data.text) : node.data.text
const level = layerIndex + 1
if (level <= 6) {
content += getTitleMark(level)
} else {
content += getIndentMark(level)
}
content += ' ' + text
content += ' ' + getNodeText(node.data)
// 概要
let generalization = node.data.generalization
if (generalization && generalization.text) {
let generalizationText = generalization.richText
? getText(generalization.text)
: generalization.text
content += `[${generalizationText}]`
const generalization = node.data.generalization
if (Array.isArray(generalization)) {
content += generalization.map(item => {
return ` [${getNodeText(item)}]`
})
} else if (generalization && generalization.text) {
const generalizationText = getNodeText(generalization)
content += ` [${generalizationText}]`
}
content += '\n\n'
// 备注

View File

@@ -0,0 +1,35 @@
import { walk, nodeRichTextToTextWithWrap } from '../utils'
const getNodeText = data => {
return data.richText ? nodeRichTextToTextWithWrap(data.text) : data.text
}
const getIndent = level => {
return new Array(level).fill(' ').join('')
}
// 转换成txt格式
export const transformToTxt = root => {
let content = ''
walk(
root,
null,
(node, parent, isRoot, layerIndex) => {
content += getIndent(layerIndex)
content += ' ' + getNodeText(node.data)
// 概要
const generalization = node.data.generalization
if (Array.isArray(generalization)) {
content += generalization.map(item => {
return ` [${getNodeText(item)}]`
})
} else if (generalization && generalization.text) {
content += ` [${getNodeText(generalization)}]`
}
content += '\n\n'
},
() => {},
true
)
return content
}

View File

@@ -198,7 +198,7 @@ const transformOldXmind = content => {
childrenItem.elements.length > 0
) {
const children = getElementsByType(childrenItem.elements, 'attached')
children.forEach((item, index) => {
;(children || []).forEach((item, index) => {
const newChild = {}
newNode.children.push(newChild)
if (childrenSummary[index]) {

View File

@@ -339,6 +339,10 @@ class AssociativeLine {
dasharray: [6, 4]
})
.fill({ color: 'none' })
// 箭头
this.markerPath
.stroke({ color: associativeLineColor })
.fill({ color: associativeLineColor })
this.creatingLine.marker('end', this.marker)
}

View File

@@ -28,6 +28,8 @@ class Cooperate {
this.currentData = null
// 用户信息
this.userInfo = null
// 是否正在重新设置思维导图数据
this.isSetData = false
// 绑定事件
this.bindEvent()
// 处理实例化时传入的思维导图数据
@@ -92,8 +94,8 @@ class Cooperate {
this.mindMap.on('node_tree_render_end', this.onNodeTreeRenderEnd)
// 监听设置思维导图数据事件
this.initData = this.initData.bind(this)
this.mindMap.on('set_data', this.initData)
this.onSetData = this.onSetData.bind(this)
this.mindMap.on('set_data', this.onSetData)
}
// 解绑事件
@@ -104,7 +106,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.mindMap.off('set_data', this.onSetData)
this.ydoc.destroy()
}
@@ -118,35 +120,63 @@ class Cooperate {
const res = transformObjectToTreeData(data)
if (!res) return
// 更新思维导图画布
this.mindMap.renderer.setData(res)
this.mindMap.render()
this.mindMap.command.addHistory()
this.mindMap.updateData(res)
}
// 当前思维导图改变后的处理,触发同步
onDataChange(data) {
if (this.isSetData) {
this.isSetData = false
return
}
const res = transformTreeDataToObject(data)
this.updateChanges(res)
}
// 找出更新点
updateChanges(data) {
const { beforeCooperateUpdate } = this.mindMap.opt
const oldData = this.currentData
this.currentData = data
this.ydoc.transact(() => {
// 找出新增的或修改的
const createOrUpdateList = []
Object.keys(data).forEach(uid => {
// 新增的或已经存在的,如果数据发生了改变
if (!oldData[uid] || !isSameObject(oldData[uid], data[uid])) {
this.ymap.set(uid, data[uid])
createOrUpdateList.push({
uid,
data: data[uid],
oldData: oldData[uid]
})
}
})
if (beforeCooperateUpdate && createOrUpdateList.length > 0) {
beforeCooperateUpdate({
type: 'createOrUpdate',
list: createOrUpdateList,
data
})
}
createOrUpdateList.forEach(item => {
this.ymap.set(item.uid, item.data)
})
// 找出删除的
const deleteList = []
Object.keys(oldData).forEach(uid => {
if (!data[uid]) {
this.ymap.delete(uid)
deleteList.push({ uid, data: oldData[uid] })
}
})
if (beforeCooperateUpdate && deleteList.length > 0) {
beforeCooperateUpdate({
type: 'delete',
list: deleteList
})
}
deleteList.forEach(item => {
this.ymap.delete(item.uid)
})
})
}
@@ -177,6 +207,12 @@ class Cooperate {
this.waitNodeUidMap = {}
}
// 监听思维导图数据的重新设置事件
onSetData(data) {
this.isSetData = true
this.initData(data)
}
// 设置用户信息
/**
* {
@@ -220,6 +256,7 @@ class Cooperate {
// 设置当前数据
const data = Array.from(this.awareness.getStates().values())
this.currentAwarenessData = data
this.waitNodeUidMap = {}
walk(data, (uid, node, userInfo) => {
// 不显示自己
if (userInfo.id === this.userInfo.id) return

View File

@@ -0,0 +1,397 @@
import {
walk,
getNodeTreeBoundingRect,
fullscrrenEvent,
fullScreen,
exitFullScreen,
formatGetNodeGeneralization
} from '../utils/index'
import { keyMap } from '../core/command/keyMap'
const defaultConfig = {
boxShadowColor: 'rgba(0, 0, 0, 0.8)', // 高亮框四周的区域颜色
borderRadius: '5px', // 高亮框的圆角大小
transition: 'all 0.3s ease-out', // 高亮框动画的过渡
zIndex: 9999, // 高亮框元素的层级
padding: 20, // 高亮框的内边距
margin: 50, // 高亮框的外边距
openBlankMode: true // 是否开启填空模式,即带下划线的文本默认不显示,按回车键才依次显示
}
// 演示插件
class Demonstrate {
constructor(opt) {
this.mindMap = opt.mindMap
// 演示的步骤列表
this.stepList = []
// 当前所在步骤
this.currentStepIndex = 0
// 当前所在步骤对应的节点实例
this.currentStepNode = null
// 当前所在步骤节点的下划线文本数据
this.currentUnderlineTextData = null
// 临时的样式剩余
this.tmpStyleEl = null
// 高亮样式元素
this.highlightEl = null
this.transformState = null
this.renderTree = null
this.config = Object.assign(
{ ...defaultConfig },
this.mindMap.opt.demonstrateConfig || {}
)
}
// 进入演示模式
enter() {
// 全屏
this.bindFullscreenEvent()
// 如果已经全屏了
if (document.fullscreenElement === this.mindMap.el) {
this._enter()
} else {
// 否则申请全屏
fullScreen(this.mindMap.el)
}
}
_enter() {
// 添加演示用的临时的样式
this.addTmpStyles()
// 记录演示前的画布状态
this.transformState = this.mindMap.view.getTransformData()
// 记录演示前的画布数据
this.renderTree = this.mindMap.getData()
// 暂停收集历史记录
this.mindMap.command.pause()
// 暂停思维导图快捷键响应
this.mindMap.keyCommand.pause()
// 创建高亮元素
this.createHighlightEl()
// 计算步骤数据
this.getStepList()
// 收起所有节点
this.mindMap.execCommand('UNEXPAND_ALL', false)
const onRenderEnd = () => {
this.mindMap.off('node_tree_render_end', onRenderEnd)
// 聚焦到第一步
this.jump(this.currentStepIndex)
this.bindEvent()
}
this.mindMap.on('node_tree_render_end', onRenderEnd)
}
// 退出演示模式
exit() {
exitFullScreen(this.mindMap.el)
this.mindMap.updateData(this.renderTree)
this.mindMap.view.setTransformData(this.transformState)
this.renderTree = null
this.transformState = null
this.stepList = []
this.currentStepIndex = 0
this.currentStepNode = null
this.currentUnderlineTextData = null
this.unBindEvent()
this.removeTmpStyles()
this.removeHighlightEl()
this.mindMap.command.recovery()
this.mindMap.keyCommand.recovery()
this.mindMap.emit('exit_demonstrate')
}
// 添加临时的样式
addTmpStyles() {
this.tmpStyleEl = document.createElement('style')
let cssText = `
/* 画布所有元素禁止响应鼠标事件 */
.smm-mind-map-container {
pointer-events: none;
}
/* 超链接图标允许响应鼠标事件 */
.smm-node a {
pointer-events: all;
}
/* 备注图标允许响应鼠标事件 */
.smm-node .smm-node-note {
pointer-events: all;
}
`
if (this.config.openBlankMode) {
cssText += `
/* 带下划线的文本内容全部隐藏 */
.smm-richtext-node-wrap u {
opacity: 0;
}
`
}
this.tmpStyleEl.innerText = cssText
document.head.appendChild(this.tmpStyleEl)
}
// 移除临时的样式
removeTmpStyles() {
if (this.tmpStyleEl) document.head.removeChild(this.tmpStyleEl)
}
// 创建高亮元素
createHighlightEl() {
if (!this.highlightEl) {
// 高亮元素
this.highlightEl = document.createElement('div')
this.highlightEl.style.cssText = `
position: absolute;
box-shadow: 0 0 0 5000px ${this.config.boxShadowColor};
border-radius: ${this.config.borderRadius};
transition: ${this.config.transition};
z-index: ${this.config.zIndex + 1};
pointer-events: none;
`
this.mindMap.el.appendChild(this.highlightEl)
}
}
// 移除高亮元素
removeHighlightEl() {
if (this.highlightEl) {
this.mindMap.el.removeChild(this.highlightEl)
this.highlightEl = null
}
}
// 更新高亮元素的位置和大小
updateHighlightEl({ left, top, width, height }) {
const padding = this.config.padding
if (left) {
this.highlightEl.style.left = left - padding + 'px'
}
if (top) {
this.highlightEl.style.top = top - padding + 'px'
}
if (width) {
this.highlightEl.style.width = width + padding * 2 + 'px'
}
if (height) {
this.highlightEl.style.height = height + padding * 2 + 'px'
}
}
// 绑定事件
bindEvent() {
this.onKeydown = this.onKeydown.bind(this)
window.addEventListener('keydown', this.onKeydown)
}
// 绑定全屏事件
bindFullscreenEvent() {
this.onFullscreenChange = this.onFullscreenChange.bind(this)
document.addEventListener(fullscrrenEvent, this.onFullscreenChange)
}
// 解绑事件
unBindEvent() {
window.removeEventListener('keydown', this.onKeydown)
document.removeEventListener(fullscrrenEvent, this.onFullscreenChange)
}
// 全屏状态改变
onFullscreenChange() {
if (!document.fullscreenElement) {
this.exit()
} else if (document.fullscreenElement === this.mindMap.el) {
this._enter()
}
}
// 按键事件
onKeydown(e) {
// 上一个
if (e.keyCode === keyMap.Left) {
this.prev()
} else if (e.keyCode === keyMap.Right) {
// 下一个
this.next()
} else if (e.keyCode === keyMap.Esc) {
// 退出演示
this.exit()
} else if (e.keyCode === keyMap.Enter) {
// 回车键显示隐藏的下划线文本
this.showNextUnderlineText()
}
}
// 上一张
prev() {
if (this.currentStepIndex > 0) {
this.jump(this.currentStepIndex - 1)
}
}
// 下一张
next() {
const stepLength = this.stepList.length
if (this.currentStepIndex < stepLength - 1) {
this.jump(this.currentStepIndex + 1)
}
}
// 显示隐藏的下划线文本
showNextUnderlineText() {
if (
!this.config.openBlankMode ||
!this.currentStepNode ||
!this.currentUnderlineTextData
)
return
const { index, list, length } = this.currentUnderlineTextData
if (index >= length) return
const node = list[index]
this.currentUnderlineTextData.index++
node.node.style.opacity = 1
}
// 跳转到某一张
jump(index) {
// 移除该当前下划线元素设置的样式
if (this.currentUnderlineTextData) {
this.currentUnderlineTextData.list.forEach(item => {
item.node.style.opacity = ''
})
this.currentUnderlineTextData = null
}
this.currentStepNode = null
this.currentStepIndex = index
this.mindMap.emit(
'demonstrate_jump',
this.currentStepIndex,
this.stepList.length
)
const step = this.stepList[index]
// 这一步的节点数据
const nodeData = step.node
// 该节点的uid
const uid = nodeData.data.uid
// 根据uid在画布上找到该节点实例
const node = this.mindMap.renderer.findNodeByUid(uid)
// 如果该节点实例不存在,那么先展开到该节点
if (!node) {
this.mindMap.renderer.expandToNodeUid(uid, () => {
const node = this.mindMap.renderer.findNodeByUid(uid)
// 展开后还是没找到,那么就别进入了,否则会死循环
if (node) {
this.jump(index)
}
})
return
}
// 1.聚焦到某个节点
if (step.type === 'node') {
this.currentStepNode = node
// 当前节点存在带下划线的文本内容
const uNodeList = this.config.openBlankMode ? node.group.find('u') : null
if (uNodeList && uNodeList.length > 0) {
this.currentUnderlineTextData = {
index: 0,
list: uNodeList,
length: uNodeList.length
}
}
// 适应画布大小
this.mindMap.view.fit(
() => {
return node.group.rbox()
},
true,
this.config.padding + this.config.margin
)
const rect = node.group.rbox()
this.updateHighlightEl({
left: rect.x,
top: rect.y,
width: rect.width,
height: rect.height
})
} else {
// 2.聚焦到某个节点的所有子节点
// 聚焦该节点的所有子节点
const task = () => {
// 先收起该节点所有子节点的子节点
nodeData.children.forEach(item => {
item.data.expand = false
})
this.mindMap.render(() => {
// 适应画布大小
this.mindMap.view.fit(
() => {
const res = getNodeTreeBoundingRect(node, 0, 0, 0, 0, true)
return {
...res,
x: res.left,
y: res.top
}
},
true,
this.config.padding + this.config.margin
)
const res = getNodeTreeBoundingRect(node, 0, 0, 0, 0, true)
this.updateHighlightEl(res)
})
}
// 如果该节点是收起状态,那么需要先展开
if (!nodeData.data.expand) {
this.mindMap.execCommand('SET_NODE_EXPAND', node, true)
const onRenderEnd = () => {
this.mindMap.off('node_tree_render_end', onRenderEnd)
task()
}
this.mindMap.on('node_tree_render_end', onRenderEnd)
} else {
// 否则直接聚焦
task()
}
}
}
// 深度度优先遍历所有节点,返回步骤列表
getStepList() {
walk(this.mindMap.renderer.renderTree, null, node => {
this.stepList.push({
type: 'node',
node
})
// 添加概要步骤
const generalizationList = formatGetNodeGeneralization(node.data)
generalizationList.forEach(item => {
// 没有uid的直接过滤掉否则会死循环
if (item.uid) {
this.stepList.push({
type: 'node',
node: {
data: item
}
})
}
})
if (node.children.length > 1) {
this.stepList.push({
type: 'children',
node
})
}
})
}
// 插件被移除前做的事情
beforePluginRemove() {
this.unBindEvent()
}
// 插件被卸载前做的事情
beforePluginDestroy() {
this.unBindEvent()
}
}
Demonstrate.instanceName = 'demonstrate'
export default Demonstrate

View File

@@ -122,6 +122,10 @@ class Drag extends Base {
if (!this.isMousedown) {
return
}
// 停止自动移动
if (this.mindMap.opt.autoMoveWhenMouseInEdgeOnDrag && this.mindMap.select) {
this.mindMap.select.clearAutoMoveTimer()
}
this.isMousedown = false
// 恢复被拖拽节点的临时设置
this.beingDragNodeList.forEach(node => {

View File

@@ -10,6 +10,7 @@ import { SVG } from '@svgdotjs/svg.js'
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
import { transformToMarkdown } from '../parse/toMarkdown'
import { ERROR_TYPES } from '../constants/constant'
import { transformToTxt } from '../parse/toTxt'
// 导出插件
class Export {
@@ -46,13 +47,26 @@ class Export {
}
// 获取svg数据
async getSvgData() {
let { exportPaddingX, exportPaddingY, errorHandler, resetCss } =
this.mindMap.opt
let { svg, svgHTML } = this.mindMap.getSvgData({
async getSvgData(node) {
let {
exportPaddingX,
exportPaddingY,
errorHandler,
resetCss,
addContentToHeader,
addContentToFooter
} = this.mindMap.opt
let { svg, svgHTML, clipData } = this.mindMap.getSvgData({
paddingX: exportPaddingX,
paddingY: exportPaddingY
paddingY: exportPaddingY,
addContentToHeader,
addContentToFooter,
node
})
if (clipData) {
clipData.paddingX = exportPaddingX
clipData.paddingY = exportPaddingY
}
// svg的image标签把图片的url转换成data:url类型否则导出会丢失图片
const task1 = this.createTransformImgTaskList(
svg,
@@ -87,12 +101,13 @@ class Export {
}
return {
node: svg,
str: svgHTML
str: svgHTML,
clipData
}
}
// svg转png
svgToPng(svgSrc, transparent) {
svgToPng(svgSrc, transparent, clipData = null) {
return new Promise((resolve, reject) => {
const img = new Image()
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
@@ -106,6 +121,15 @@ class Export {
)
let imgWidth = img.width
let imgHeight = img.height
// 如果是裁减操作的话,那么需要手动添加内边距,及调整图片大小为实际的裁减区域的大小,不要忘了内边距哦
let paddingX = 0
let paddingY = 0
if (clipData) {
paddingX = clipData.paddingX
paddingY = clipData.paddingY
imgWidth = clipData.width + paddingX * 2
imgHeight = clipData.height + paddingY * 2
}
// 检查是否超出canvas支持的像素上限
const maxSize = 16384 / dpr
const maxArea = maxSize * maxSize
@@ -132,7 +156,22 @@ class Export {
await this.drawBackgroundToCanvas(ctx, imgWidth, imgHeight)
}
// 图片绘制到canvas里
ctx.drawImage(img, 0, 0, imgWidth, imgHeight)
// 如果有裁减数据,那么需要进行裁减
if (clipData) {
ctx.drawImage(
img,
clipData.left,
clipData.top,
clipData.width,
clipData.height,
paddingX,
paddingY,
clipData.width,
clipData.height
)
} else {
ctx.drawImage(img, 0, 0, imgWidth, imgHeight)
}
resolve(canvas.toDataURL())
} catch (error) {
reject(error)
@@ -216,13 +255,24 @@ class Export {
* 方法1.把svg的图片都转化成data:url格式再转换
* 方法2.把svg的图片提取出来再挨个绘制到canvas里最后一起转换
*/
async png(name, transparent = false) {
const { str } = await this.getSvgData()
async png(name, transparent = false, node = null) {
this.handleNodeExport(node)
const { str, clipData } = await this.getSvgData(node)
const svgUrl = await this.fixSvgStrAndToBlob(str)
const res = await this.svgToPng(svgUrl, transparent)
const res = await this.svgToPng(svgUrl, transparent, clipData)
return res
}
// 导出指定节点,如果该节点是激活状态,那么取消激活和隐藏展开收起按钮
handleNodeExport(node) {
if (node && node.getData('isActive')) {
node.deactivate()
if (!this.mindMap.opt.alwaysShowExpandBtn && node.getData('expand')) {
node.removeExpandBtn()
}
}
}
// 导出为pdf
async pdf(name, transparent = false) {
if (!this.mindMap.doExportPDF) {
@@ -294,6 +344,15 @@ class Export {
const res = await readBlob(blob)
return res
}
// txt文件
async txt() {
const data = this.mindMap.getData()
const content = transformToTxt(data)
const blob = new Blob([content])
const res = await readBlob(blob)
return res
}
}
Export.instanceName = 'doExport'

View File

@@ -93,10 +93,7 @@ class MiniMap {
return {
getImgUrl: async callback => {
const blob = new Blob([svgStr], {
type: 'image/svg+xml'
})
const res = await readBlob(blob)
const res = await this.mindMap.doExport.fixSvgStrAndToBlob(svgStr)
callback(res)
},
svgHTML: svgStr, // 小地图html

View File

@@ -0,0 +1,92 @@
import { walk, getNodeDataIndex } from '../utils/index'
const defaultColorsList = [
'rgb(255, 213, 73)',
'rgb(255, 136, 126)',
'rgb(107, 225, 141)',
'rgb(151, 171, 255)',
'rgb(129, 220, 242)',
'rgb(255, 163, 125)',
'rgb(152, 132, 234)'
]
// 彩虹线条插件
class RainbowLines {
constructor({ mindMap }) {
this.mindMap = mindMap
}
// 更新彩虹线条配置
updateRainLinesConfig(config = {}) {
const newConfig = this.mindMap.opt.rainbowLinesConfig || {}
newConfig.open = !!config.open
newConfig.colorsList = Array.isArray(config.colorsList)
? config.colorsList
: []
// 如果开启彩虹线条,那么先移除所有节点的自定义连线颜色配置
if (this.mindMap.opt.rainbowLinesConfig.open) {
this.removeNodeLineColor()
}
this.mindMap.render()
}
// 删除所有节点的连线颜色
removeNodeLineColor() {
const tree = this.mindMap.renderer.renderTree
if (!tree) return
walk(
tree,
null,
cur => {
delete cur.data.lineColor
},
null,
true
)
this.mindMap.command.addHistory()
}
// 获取一个节点的第二层级的祖先节点
getSecondLayerAncestor(node) {
if (node.layerIndex === 0) {
return null
} else if (node.layerIndex === 1) {
return node
} else {
let res = null
let parent = node.parent
while (parent) {
if (parent.layerIndex === 1) {
return parent
}
parent = parent.parent
}
return res
}
}
// 获取颜色列表
getColorsList() {
const { rainbowLinesConfig } = this.mindMap.opt
return rainbowLinesConfig &&
Array.isArray(rainbowLinesConfig.colorsList) &&
rainbowLinesConfig.colorsList.length > 0
? rainbowLinesConfig.colorsList
: [...defaultColorsList]
}
// 获取一个节点的彩虹线条颜色
getNodeColor(node) {
const { rainbowLinesConfig } = this.mindMap.opt
if (!rainbowLinesConfig || !rainbowLinesConfig.open) return ''
const ancestor = this.getSecondLayerAncestor(node)
if (!ancestor) return
const index = getNodeDataIndex(ancestor)
const colorsList = this.getColorsList()
return colorsList[index % colorsList.length]
}
}
RainbowLines.instanceName = 'rainbowLines'
export default RainbowLines

View File

@@ -8,7 +8,8 @@ import {
getVisibleColorFromTheme,
isUndef,
checkSmmFormatData,
removeHtmlNodeByClass
removeHtmlNodeByClass,
formatGetNodeGeneralization
} from '../utils'
import { CONSTANTS } from '../constants/constant'
@@ -327,8 +328,8 @@ class RichText {
list.forEach(node => {
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
// if (node.isGeneralization) {
// 概要节点
// node.generalizationBelongNode.updateGeneralization()
// 概要节点
// node.generalizationBelongNode.updateGeneralization()
// }
this.mindMap.render()
})
@@ -641,6 +642,7 @@ class RichText {
// 将所有节点转换成非富文本节点
transformAllNodesToNormalNode() {
if (!this.mindMap.renderer.renderTree) return
walk(
this.mindMap.renderer.renderTree,
null,
@@ -648,7 +650,14 @@ class RichText {
if (node.data.richText) {
node.data.richText = false
node.data.text = getTextFromHtml(node.data.text)
// delete node.data.uid
}
// 概要
if (node.data) {
const generalizationList = formatGetNodeGeneralization(node.data)
generalizationList.forEach(item => {
item.richText = false
item.text = getTextFromHtml(item.text)
})
}
},
null,
@@ -669,6 +678,14 @@ class RichText {
root.data.richText = true
root.data.resetRichText = true
}
// 概要
if (root.data) {
const generalizationList = formatGetNodeGeneralization(root.data)
generalizationList.forEach(item => {
item.richText = true
item.resetRichText = true
})
}
if (root.children && root.children.length > 0) {
Array.from(root.children).forEach(item => {
walk(item)

View File

@@ -40,6 +40,7 @@ class Scrollbar {
this.mindMap.on('mouseup', this.onMouseup)
this.mindMap.on('node_tree_render_end', this.updateScrollbar)
this.mindMap.on('view_data_change', this.updateScrollbar)
this.mindMap.on('resize', this.updateScrollbar)
}
// 解绑事件
@@ -48,6 +49,7 @@ class Scrollbar {
this.mindMap.off('mouseup', this.onMouseup)
this.mindMap.off('node_tree_render_end', this.updateScrollbar)
this.mindMap.off('view_data_change', this.updateScrollbar)
this.mindMap.off('resize', this.updateScrollbar)
}
// 渲染后、数据改变需要更新滚动条
@@ -202,7 +204,8 @@ class Scrollbar {
yOffset -
paddingY * t.scaleY +
paddingY -
rootCenterOffset.y * t.scaleY
rootCenterOffset.y * t.scaleY +
((this.mindMap.height - this.mindMap.initHeight) / 2) * t.scaleY // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
this.mindMap.view.translateYTo(chartTop)
this.emitEvent({
horizontal: scrollbarData.horizontal,
@@ -238,7 +241,8 @@ class Scrollbar {
xOffset -
paddingX * t.scaleX +
paddingX -
rootCenterOffset.x * t.scaleX
rootCenterOffset.x * t.scaleX +
((this.mindMap.width - this.mindMap.initWidth) / 2) * t.scaleX // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
this.mindMap.view.translateXTo(chartLeft)
this.emitEvent({
vertical: scrollbarData.vertical,

View File

@@ -4,6 +4,7 @@ import {
isUndef,
replaceHtmlText
} from '../utils/index'
import Node from '../core/render/node/Node'
// 搜索插件
class Search {
@@ -69,14 +70,14 @@ class Search {
// 结束搜索
endSearch() {
if (!this.isSearching) return
if (this.mindMap.opt.readonly && this.matchNodeList[this.currentIndex]) {
this.matchNodeList[this.currentIndex].closeHighlight()
}
this.searchText = ''
this.matchNodeList = []
this.currentIndex = -1
this.notResetSearchText = false
this.isSearching = false
if (this.mindMap.opt.readonly) {
this.mindMap.renderer.closeHighlightNode()
}
this.emitEvent()
}
@@ -84,8 +85,16 @@ class Search {
doSearch() {
this.matchNodeList = []
this.currentIndex = -1
bfsWalk(this.mindMap.renderer.root, node => {
let { richText, text } = node.getData()
const { isOnlySearchCurrentRenderNodes } = this.mindMap.opt
// 如果要搜索收起来的节点,那么要遍历渲染树而不是节点树
const tree = isOnlySearchCurrentRenderNodes
? this.mindMap.renderer.root
: this.mindMap.renderer.renderTree
if (!tree) return
bfsWalk(tree, node => {
let { richText, text } = isOnlySearchCurrentRenderNodes
? node.getData()
: node.data
if (richText) {
text = getTextFromHtml(text)
}
@@ -95,6 +104,11 @@ class Search {
})
}
// 判断对象是否是节点实例
isNodeInstance(node) {
return node instanceof Node
}
// 搜索下一个,定位到下一个匹配节点
searchNext(callback) {
if (!this.isSearching || this.matchNodeList.length <= 0) return
@@ -103,14 +117,24 @@ class Search {
} else {
this.currentIndex = 0
}
let currentNode = this.matchNodeList[this.currentIndex]
const currentNode = this.matchNodeList[this.currentIndex]
this.notResetSearchText = true
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
this.notResetSearchText = false
const uid = this.isNodeInstance(currentNode)
? currentNode.getData('uid')
: currentNode.data.uid
const targetNode = this.mindMap.renderer.findNodeByUid(uid)
this.mindMap.execCommand('GO_TARGET_NODE', uid, node => {
if (!this.isNodeInstance(currentNode)) {
this.matchNodeList[this.currentIndex] = node
}
callback()
// 只读模式下节点无法激活,所以通过高亮的方式
if (this.mindMap.opt.readonly) {
this.mindMap.renderer.highlightNode(currentNode)
node.highlight()
}
// 如果当前节点实例已经存在则不会触发data_change事件那么需要手动把标志复位
if (targetNode) {
this.notResetSearchText = false
}
})
}
@@ -154,15 +178,20 @@ class Search {
return
replaceText = String(replaceText)
this.matchNodeList.forEach(node => {
let text = this.getReplacedText(node, this.searchText, replaceText)
this.mindMap.renderer.setNodeDataRender(
node,
{
text,
resetRichText: !!node.getData('richText')
},
true
)
const text = this.getReplacedText(node, this.searchText, replaceText)
if (this.isNodeInstance(node)) {
this.mindMap.renderer.setNodeDataRender(
node,
{
text,
resetRichText: !!node.getData('richText')
},
true
)
} else {
node.data.text = text
node.data.resetRichText = !!node.data.richText
}
})
this.mindMap.render()
this.mindMap.command.addHistory()
@@ -171,7 +200,9 @@ class Search {
// 获取某个节点替换后的文本
getReplacedText(node, searchText, replaceText) {
let { richText, text } = node.getData()
let { richText, text } = this.isNodeInstance(node)
? node.getData()
: node.data
if (richText) {
return replaceHtmlText(text, searchText, replaceText)
} else {

View File

@@ -44,7 +44,7 @@ class Select {
}
let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
if (
!e.ctrlKey &&
!(e.ctrlKey || e.metaKey) &&
(useLeftKeySelectionRightKeyDrag ? e.which !== 1 : e.which !== 3)
) {
return
@@ -237,11 +237,13 @@ class Select {
return
}
this.mindMap.renderer.addNodeToActiveList(node)
this.mindMap.renderer.emitNodeActiveEvent()
} else if (node.getData('isActive')) {
if (!node.getData('isActive')) {
return
}
this.mindMap.renderer.removeNodeFromActiveList(node)
this.mindMap.renderer.emitNodeActiveEvent()
}
})
}

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