Compare commits

...

84 Commits
0.9.4 ... 0.9.8

Author SHA1 Message Date
街角小林
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
街角小林
4a5980f993 打包0.9.7 2024-02-02 17:08:03 +08:00
街角小林
fa8ad5c0d0 Doc: update 2024-02-02 17:02:36 +08:00
街角小林
a37fe66e60 Fix:修复激活概要节点,右侧设置文本样式,概要节点会失去焦点的问题 2024-01-31 18:10:22 +08:00
街角小林
af622793d8 Demo:优化主题设置弹窗 2024-01-31 17:50:28 +08:00
街角小林
679330663a Demo:新增去除节点自定义样式的右键菜单 2024-01-31 17:13:09 +08:00
街角小林
32e027529f Feat:新增两个去除节点自定义样式的命令 2024-01-31 17:12:38 +08:00
wanglin2
5be2f561e7 打包demo 2024-01-29 22:14:24 +08:00
街角小林
3da8070820 Fix:修复二级以下节点拖拽到根节点变成二级节点时样式没有更新的问题;修复上移一个层级命令移动节点时样式没有更新的问题 2024-01-26 13:51:44 +08:00
街角小林
12c89e6d37 打包demo 2024-01-26 12:00:32 +08:00
街角小林
fdb292d9b1 '打包' 2024-01-26 11:52:24 +08:00
街角小林
1083138d8c Doc: update 2024-01-26 11:41:34 +08:00
街角小林
6a4e87af7b 打包0.9.6 2024-01-26 11:33:26 +08:00
街角小林
7354bec8fd Doc: update 2024-01-26 11:19:27 +08:00
街角小林
3405fb7e8a Fix:修复添加了数学公式的节点,切换主题时文本样式没有改变的问题 2024-01-26 11:09:17 +08:00
街角小林
138cc4b3e8 Fix:修复切换主题时,换行的文本样式没有改变的问题 2024-01-26 09:44:09 +08:00
街角小林
e6ede72169 Fix:修复切换主题后,第一次进行文本换行后,新换行的文本样式会丢失的问题 2024-01-26 09:43:17 +08:00
街角小林
edddbbd1d6 Demo:修复大纲中文本换行不生效,显示br标签的问题 2024-01-26 09:05:34 +08:00
街角小林
304e76e4af 删除无用文件 2024-01-26 08:49:26 +08:00
街角小林
b4ceb88d18 Doc: update 2024-01-26 08:43:14 +08:00
街角小林
635fdf4806 Demo:新增两个主题 2024-01-26 08:40:31 +08:00
街角小林
77d376210e Doc: update 2024-01-25 18:25:36 +08:00
街角小林
12f9e03f63 Demo:大纲支持按Shift+Tab将节点上移一个层级 2024-01-25 15:24:41 +08:00
街角小林
a4f83437c9 Feat:新增MOVE_UP_ONE_LEVEL命令将节点上移一个层级 2024-01-25 15:19:11 +08:00
街角小林
a5b3efd272 Doc: update 2024-01-25 14:11:43 +08:00
街角小林
7bd467a330 Fix:修复切换主题时,被收起的节点样式没有变化的问题 2024-01-25 13:52:23 +08:00
街角小林
59b2506884 Feat:Mac双指触摸事件移动画布增加一点灵敏度 2024-01-25 08:58:33 +08:00
街角小林
cf87333910 Feat:直连风格根节点的连线起点统一为节点的边界 2024-01-24 18:51:39 +08:00
街角小林
95b957d37e Feat:主题新增字段用于设置曲线连接下根节点连线的起始位置 2024-01-24 18:28:04 +08:00
街角小林
7d18f98a33 Feat:支持设置节点连线箭头的显示位置,头部或者尾部 2024-01-24 17:52:48 +08:00
街角小林
6baf388d95 update 2024-01-22 19:59:17 +08:00
街角小林
d63d01647c 打包0.9.5 2024-01-22 19:56:20 +08:00
街角小林
70c6a26de0 Doc: update 2024-01-22 19:53:38 +08:00
街角小林
8241bcbbb4 Demo:优化代码 2024-01-19 17:45:48 +08:00
街角小林
89fd59adec Demo:支持开启手绘风格 2024-01-19 16:29:50 +08:00
街角小林
9b1f26f6e9 Feat:支持自定义创建节点形状的方法 2024-01-19 15:50:32 +08:00
街角小林
e590161f0a Doc: update 2024-01-17 18:19:48 +08:00
街角小林
2fe804880f Demo:优化移动端的使用体验 2024-01-16 18:30:41 +08:00
街角小林
bbb21d4e76 Feat:优化创建关联线时初始端点位置的计算逻辑 2024-01-16 10:10:32 +08:00
街角小林
3f9c3e9fb1 Doc: update 2024-01-16 08:41:25 +08:00
街角小林
925c5d6d3c Feat:增加禁止调整关联线端点位置的配置 2024-01-15 18:34:19 +08:00
街角小林
bb223b080c Feat:增加设置关联线创建时两个端点初始位置的配置 2024-01-15 18:31:40 +08:00
街角小林
c3652331ea Fix:修复新创建的关联线位置始终在节点的右侧没有根据相对位置调整的问题 2024-01-15 17:50:24 +08:00
街角小林
62c61b6e53 Fix:修复展开收起节点时区间概要会丢失的问题 2024-01-15 17:11:16 +08:00
170 changed files with 3396 additions and 28651 deletions

View File

@@ -2,12 +2,12 @@
[![npm-version](https://img.shields.io/npm/v/simple-mind-map)](https://www.npmjs.com/package/simple-mind-map)
![npm download](https://img.shields.io/npm/dm/simple-mind-map)
[![GitHub stars](https://img.shields.io/github/stars/wanglin2/mind-map)](https://github.com/wanglin2/mind-map/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/wanglin2/mind-map)](https://github.com/wanglin2/mind-map/issues)
[![GitHub forks](https://img.shields.io/github/forks/wanglin2/mind-map)](https://github.com/wanglin2/mind-map/network/members)
![license](https://img.shields.io/npm/l/express.svg)
[![GitHub stars](https://img.shields.io/github/stars/wanglin2/mind-map)](https://github.com/wanglin2/mind-map/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/wanglin2/mind-map)](https://github.com/wanglin2/mind-map/network/members)
> 一个简单&强大的 Web 思维导图
> 中文名:思绪思维导图。一个简单&强大的 Web 思维导图
本项目包含两部分:
@@ -27,8 +27,6 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。
> 客户端版本会落后于在线版本,尝试最新功能请优先使用在线版。
> 如果访问 github 比较慢可以使用【更新可能会滞后】http://lxqnsys.com/simple-mind-map/#/index
# 特性
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
@@ -90,10 +88,18 @@ const mindMap = new MindMap({
[MIT](./LICENSE)
保留`mind-map`版权声明的情况下可随意商用。
# 微信交流群
群聊人数较多,无法通过二维码入群,可以微信添加`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)
# 请作者喝杯咖啡
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~
@@ -260,4 +266,36 @@ const mindMap = new MindMap({
<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/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/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>
</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

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/img/classic6.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
dist/img/classic7.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 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: 148 KiB

BIN
dist/img/手绘风格.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 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: 347 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-1c3bec15.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/chunk-2bf818d4.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

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

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

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-554d686c.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-9d289278.js vendored Normal file

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

View File

@@ -28,7 +28,7 @@ MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.9.4'
MindMap.version = '0.9.8'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)

View File

@@ -31,6 +31,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 +41,10 @@ class MindMap {
// 获取容器尺寸位置信息
this.getElRectInfo()
// 画布初始大小
this.initWidth = this.width
this.initHeight = this.height
// 添加css
this.cssEl = null
this.addCss()
@@ -94,8 +100,6 @@ class MindMap {
// 配置参数处理
handleOpt(opt) {
// 深拷贝一份节点数据
opt.data = simpleDeepClone(opt.data || {})
// 检查布局配置
if (!layoutValueList.includes(opt.layout)) {
opt.layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
@@ -105,6 +109,16 @@ class MindMap {
return opt
}
// 预处理节点数据
handleData(data) {
data = simpleDeepClone(data || {})
// 根节点不能收起
if (data.data && !data.data.expand) {
data.data.expand = true
}
return data
}
// 创建容器元素
initContainer() {
const { associativeLineIsAlwaysAboveNode } = this.opt
@@ -304,7 +318,8 @@ class MindMap {
// 动态设置思维导图数据,纯节点数据
setData(data) {
data = simpleDeepClone(data || {})
data = this.handleData(data)
this.opt.data = data
this.execCommand('CLEAR_ACTIVE_NODE')
this.command.clearHistory()
this.command.addHistory()

View File

@@ -1,11 +1,11 @@
{
"name": "simple-mind-map",
"version": "0.9.2",
"version": "0.9.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.9.2",
"version": "0.9.4",
"license": "MIT",
"dependencies": {
"@svgdotjs/svg.js": "^3.0.16",

View File

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

View File

@@ -346,7 +346,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

@@ -40,7 +40,7 @@ export const defaultOpt = {
enableFreeDrag: false,
// 水印配置
watermarkConfig: {
onlyExport: false,// 是否仅在导出时添加水印
onlyExport: false, // 是否仅在导出时添加水印
text: '',
lineSpacing: 100,
textSpacing: 100,
@@ -256,5 +256,37 @@ export const defaultOpt = {
}
}
*/
handleNodePasteImg: null
handleNodePasteImg: null,
// 默认情况下,新创建的关联线两个端点的位置是根据两个节点中心点的相对位置来计算的,如果你想固定位置,可以通过这个属性来配置
// from和to都不传则都自动计算如果只传一个另一个则会自动计算
associativeLineInitPointsPosition: {
// from和to可选值left、top、bottom、right
from: '', // 关联线起始节点上端点的位置
to: '' // 关联线目标节点上端点的位置
},
// 是否允许调整关联线两个端点的位置
enableAdjustAssociativeLinePoints: true,
// 自定义创建节点形状的方法,可以传一个函数,均接收一个参数
// 矩形、圆角矩形、椭圆、圆等形状会调用该方法
// 接收svg path字符串返回svg节点
customCreateNodePath: null,
// 菱形、平行四边形、八角矩形、外三角矩形、内三角矩形等形状会调用该方法
// 接收points数组点位返回svg节点
customCreateNodePolygon: null,
// 自定义转换节点连线路径的方法
// 接收svg path字符串返回转换后的svg path字符串
customTransformNodeLinePath: null,
// 是否仅搜索当前渲染的节点,被收起的节点不会被搜索到
isOnlySearchCurrentRenderNodes: false,
// 协同编辑时,同一个节点不能同时被多人选中
onlyOneEnableActiveNodeOnCooperate: false,
// 协同编辑时,节点操作即将更新到其他客户端前的生命周期函数
// 函数接收一个对象作为参数:
/*
{
type: createOrUpdate创建节点或更新节点、delete删除节点
data: 1.当type=createOrUpdate时代表被创建或被更新的节点数据即将同步到其他客户端所以你可以修改该数据2.当type=delete时代表被删除的节点数据
}
*/
beforeCooperateUpdate: null
}

View File

@@ -28,7 +28,9 @@ import {
parseAddGeneralizationNodeList,
checkNodeListIsEqual,
createSmmFormatData,
checkSmmFormatData
checkSmmFormatData,
checkIsNodeStyleDataKey,
removeRichTextStyes
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
@@ -180,6 +182,9 @@ class Render {
// 下移节点
this.downNode = this.downNode.bind(this)
this.mindMap.command.add('DOWN_NODE', this.downNode)
// 将一个节点上移一个层级
this.moveUpOneLevel = this.moveUpOneLevel.bind(this)
this.mindMap.command.add('MOVE_UP_ONE_LEVEL', this.moveUpOneLevel)
// 移动节点
this.insertAfter = this.insertAfter.bind(this)
this.mindMap.command.add('INSERT_AFTER', this.insertAfter)
@@ -268,6 +273,15 @@ class Render {
// 定位节点
this.goTargetNode = this.goTargetNode.bind(this)
this.mindMap.command.add('GO_TARGET_NODE', this.goTargetNode)
// 一键去除节点自定义样式
this.removeCustomStyles = this.removeCustomStyles.bind(this)
this.mindMap.command.add('REMOVE_CUSTOM_STYLES', this.removeCustomStyles)
// 一键去除所有节点自定义样式
this.removeAllNodeCustomStyles = this.removeAllNodeCustomStyles.bind(this)
this.mindMap.command.add(
'REMOVE_ALL_NODE_CUSTOM_STYLES',
this.removeAllNodeCustomStyles
)
}
// 注册快捷键
@@ -397,6 +411,10 @@ class Render {
// 渲染
render(callback = () => {}, source) {
// 切换主题时,被收起的节点需要添加样式复位的标注
if (source === CONSTANTS.CHANGE_THEME) {
this.resetUnExpandNodeStyle()
}
// 如果当前还没有渲染完毕,不再触发渲染
if (this.isRendering) {
// 等待当前渲染完毕后再进行一次渲染
@@ -439,6 +457,7 @@ class Render {
this.waitRenderingParams = []
this.render(...params)
} else {
this.renderSource = ''
if (this.reRender) {
this.reRender = false
}
@@ -455,6 +474,18 @@ class Render {
this.emitNodeActiveEvent()
}
// 给当前被收起来的节点数据添加文本复位标志
resetUnExpandNodeStyle() {
walk(this.renderTree, null, node => {
if (!node.data.expand) {
walk(node, null, node2 => {
node2.data.resetRichText = true
})
return true
}
})
}
// 清除当前所有激活节点,并会触发事件
clearActiveNode() {
if (this.activeNodeList.length <= 0) {
@@ -474,6 +505,11 @@ class Render {
// 添加节点到激活列表里
addNodeToActiveList(node) {
if (
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
node.userList.length > 0
)
return
const index = this.findActiveNodeIndex(node)
if (index === -1) {
this.mindMap.execCommand('SET_NODE_ACTIVE', node, true)
@@ -618,7 +654,7 @@ class Render {
uid: createUid(),
...(appointData || {})
},
children: [...createUidForAppointNodes(appointChildren, true)]
children: [...createUidForAppointNodes(appointChildren)]
}
parent.nodeData.children.splice(index + 1, 0, newNodeData)
})
@@ -654,10 +690,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) {
@@ -717,7 +750,7 @@ class Render {
...params,
...(appointData || {})
},
children: [...createUidForAppointNodes(appointChildren, true)]
children: [...createUidForAppointNodes(appointChildren)]
}
node.nodeData.children.push(newNode)
// 插入子节点时自动展开子节点
@@ -757,7 +790,7 @@ class Render {
if (!node.nodeData.children) {
node.nodeData.children = []
}
childList = createUidForAppointNodes(childList, true)
childList = createUidForAppointNodes(childList)
node.nodeData.children.push(...childList)
// 插入子节点时自动展开子节点
node.setData({
@@ -874,6 +907,82 @@ class Render {
this.mindMap.render()
}
// 将节点上移一个层级,多个节点只会操作第一个节点
moveUpOneLevel(node) {
node = node || this.activeNodeList[0]
if (!node || node.isRoot || node.layerIndex <= 1) {
return
}
const parent = node.parent
const grandpa = parent.parent
const index = getNodeIndexInNodeList(node, parent.children)
const parentIndex = getNodeIndexInNodeList(parent, grandpa.children)
// 节点数据
this.checkNodeLayerChange(node, parent)
parent.nodeData.children.splice(index, 1)
grandpa.nodeData.children.splice(parentIndex + 1, 0, node.nodeData)
this.mindMap.render()
}
// 移除节点数据的自定义样式的内部方法
_handleRemoveCustomStyles(nodeData) {
let hasCustomStyles = false
Object.keys(nodeData).forEach(key => {
if (checkIsNodeStyleDataKey(key)) {
hasCustomStyles = true
delete nodeData[key]
}
})
// 如果是富文本,那么还要处理富文本内容
if (hasCustomStyles && this.mindMap.richText) {
nodeData.resetRichText = true
nodeData.text = removeRichTextStyes(nodeData.text)
}
return hasCustomStyles
}
// 一键去除自定义样式
removeCustomStyles(node) {
node = node || this.activeNodeList[0]
if (!node) {
return
}
const hasCustomStyles = this._handleRemoveCustomStyles(node.getData())
if (hasCustomStyles) {
this.reRenderNodeCheckChange(node)
}
}
// 一键去除所有节点自定义样式
removeAllNodeCustomStyles(appointNodes) {
appointNodes = formatDataToArray(appointNodes)
let hasCustomStyles = false
// 指定了节点列表,那么遍历该节点列表
if (appointNodes.length > 0) {
appointNodes.forEach(node => {
const _hasCustomStyles = this._handleRemoveCustomStyles(node.getData())
if (_hasCustomStyles) hasCustomStyles = true
})
} else {
// 否则遍历整棵树
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 _hasCustomStyles =
this._handleRemoveCustomStyles(generalizationData)
if (_hasCustomStyles) hasCustomStyles = true
})
}
})
}
if (hasCustomStyles) {
this.mindMap.reRender()
}
}
// 复制节点
copy() {
this.beingCopyData = this.copyNode()
@@ -1074,11 +1183,12 @@ class Render {
}
// 如果是富文本模式,那么某些层级变化需要更新样式
checkNodeLayerChange(node, toNode) {
checkNodeLayerChange(node, toNode, toNodeIsParent = false) {
if (this.mindMap.richText) {
const toIndex = toNodeIsParent ? toNode.layerIndex + 1 : toNode.layerIndex
let nodeLayerChanged =
(node.layerIndex === 1 && toNode.layerIndex !== 1) ||
(node.layerIndex !== 1 && toNode.layerIndex === 1)
(node.layerIndex === 1 && toIndex !== 1) ||
(node.layerIndex !== 1 && toIndex === 1)
if (nodeLayerChanged) {
node.setData({
resetRichText: true
@@ -1108,7 +1218,15 @@ class Render {
// 如果只选中了一个节点,删除后激活其兄弟节点或者父节点
needActiveNode = this.getNextActiveNode()
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)
@@ -1256,7 +1374,7 @@ class Render {
return !item.isRoot
})
nodeList.forEach(item => {
this.checkNodeLayerChange(item, toNode)
this.checkNodeLayerChange(item, toNode, true)
this.removeNodeFromActiveList(item)
removeFromParentNodeData(item)
toNode.nodeData.children.push(item.nodeData)
@@ -1500,7 +1618,8 @@ class Render {
...(data || {
text: this.mindMap.opt.defaultGeneralizationText
}),
range: item.range || null
range: item.range || null,
uid: createUid()
}
let generalization = item.node.getData('generalization')
if (generalization) {
@@ -1596,7 +1715,7 @@ class Render {
if (targetNode) {
targetNode.active()
this.moveNodeToCenter(targetNode)
callback()
callback(targetNode)
}
})
}
@@ -1611,6 +1730,11 @@ class Render {
// 设置节点数据,并判断是否渲染
setNodeDataRender(node, data, notRender = false) {
this.mindMap.execCommand('SET_NODE_DATA', node, data)
this.reRenderNodeCheckChange(node, notRender)
}
// 重新节点某个节点,判断节点大小是否发生了改变,是的话触发重绘
reRenderNodeCheckChange(node, notRender) {
let changed = node.reRender()
if (changed) {
if (!notRender) this.mindMap.render()

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

@@ -203,6 +203,8 @@ class Node {
// 计算节点的宽高
getSize() {
this.customLeft = this.getData('customLeft') || undefined
this.customTop = this.getData('customTop') || undefined
this.updateGeneralization()
this.createNodeData()
let { width, height } = this.getNodeRect()
@@ -420,6 +422,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 => {
@@ -486,10 +494,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) {
return
}
e.stopPropagation()
if (onlyOneEnableActiveNodeOnCooperate && this.userList.length > 0) {
return
}
this.mindMap.emit('node_dblclick', this, e)
})
// 右键菜单事件
@@ -705,6 +717,9 @@ class Node {
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
destroy() {
if (!this.group) return
if (this.emptyUser) {
this.emptyUser()
}
this.resetWhenDelete()
this.group.remove()
this.removeGeneralization()
@@ -1052,6 +1067,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

@@ -1,10 +1,11 @@
import { Rect, Polygon, Path } from '@svgdotjs/svg.js'
import { Polygon, Path, SVG } from '@svgdotjs/svg.js'
import { CONSTANTS } from '../../../constants/constant'
// 节点形状类
export default class Shape {
constructor(node) {
this.node = node
this.mindMap = node.mindMap
}
// 形状需要的padding
@@ -106,11 +107,29 @@ export default class Shape {
}
}
// 创建路径节点
createPath(pathStr) {
const { customCreateNodePath } = this.mindMap.opt
if (customCreateNodePath) {
return SVG(customCreateNodePath(pathStr))
}
return new Path().plot(pathStr)
}
// 创建多边形节点
createPolygon(points) {
const { customCreateNodePolygon } = this.mindMap.opt
if (customCreateNodePolygon) {
return SVG(customCreateNodePolygon(points))
}
return new Polygon().plot(points)
}
// 创建矩形
createRect() {
let { width, height } = this.getNodeSize()
let borderRadius = this.node.style.merge('borderRadius')
return new Path().plot(`
const pathStr = `
M${borderRadius},0
L${width - borderRadius},0
C${width - borderRadius},0 ${width},${0} ${width},${borderRadius}
@@ -123,7 +142,8 @@ export default class Shape {
L${0},${borderRadius}
C${0},${borderRadius} ${0},${0} ${borderRadius},${0}
Z
`)
`
return this.createPath(pathStr)
}
// 创建菱形
@@ -139,12 +159,13 @@ export default class Shape {
let bottomY = height
let leftX = 0
let leftY = halfHeight
return new Polygon().plot([
const points = [
[topX, topY],
[rightX, rightY],
[bottomX, bottomY],
[leftX, leftY]
])
]
return this.createPolygon(points)
}
// 创建平行四边形
@@ -152,32 +173,34 @@ export default class Shape {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.getNodeSize()
return new Polygon().plot([
const points = [
[paddingX, 0],
[width, 0],
[width - paddingX, height],
[0, height]
])
]
return this.createPolygon(points)
}
// 创建圆角矩形
createRoundedRectangle() {
let { width, height } = this.getNodeSize()
let halfHeight = height / 2
return new Path().plot(`
const pathStr = `
M${halfHeight},0
L${width - halfHeight},0
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
L${halfHeight},${height}
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
`)
`
return this.createPath(pathStr)
}
// 创建八角矩形
createOctagonalRectangle() {
let w = 5
let { width, height } = this.getNodeSize()
return new Polygon().plot([
const points = [
[0, w],
[w, 0],
[width - w, 0],
@@ -186,7 +209,8 @@ export default class Shape {
[width - w, height],
[w, height],
[0, height - w]
])
]
return this.createPolygon(points)
}
// 创建外三角矩形
@@ -194,14 +218,15 @@ export default class Shape {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.getNodeSize()
return new Polygon().plot([
const points = [
[paddingX, 0],
[width - paddingX, 0],
[width, height / 2],
[width - paddingX, height],
[paddingX, height],
[0, height / 2]
])
]
return this.createPolygon(points)
}
// 创建内三角矩形
@@ -209,14 +234,15 @@ export default class Shape {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.getNodeSize()
return new Polygon().plot([
const points = [
[0, 0],
[width, 0],
[width - paddingX / 2, height / 2],
[width, height],
[0, height],
[paddingX / 2, height / 2]
])
]
return this.createPolygon(points)
}
// 创建椭圆
@@ -224,12 +250,13 @@ export default class Shape {
let { width, height } = this.getNodeSize()
let halfWidth = width / 2
let halfHeight = height / 2
return new Path().plot(`
const pathStr = `
M${halfWidth},0
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
M${halfWidth},${height}
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
`)
`
return this.createPath(pathStr)
}
// 创建圆
@@ -237,12 +264,13 @@ export default class Shape {
let { width, height } = this.getNodeSize()
let halfWidth = width / 2
let halfHeight = height / 2
return new Path().plot(`
const pathStr = `
M${halfWidth},0
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
M${halfWidth},${height}
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
`)
`
return this.createPath(pathStr)
}
}

View File

@@ -220,9 +220,14 @@ class Style {
childNodeStyle._marker || childNodeStyle.createMarker()
// 设置样式
childNodeStyle._markerPath.stroke({ color }).fill({ color })
line.marker('end', childNodeStyle._marker)
// 箭头位置可能会发生改变,所以需要先删除
line.attr('marker-start', '')
line.attr('marker-end', '')
const dir = childNodeStyle.merge('lineMarkerDir')
line.marker(dir, childNodeStyle._marker)
} else if (childNodeStyle._marker) {
// 不显示箭头,则删除该子节点的箭头标记
line.attr('marker-start', '')
line.attr('marker-end', '')
childNodeStyle._marker.remove()
childNodeStyle._marker = null

View File

@@ -94,11 +94,18 @@ function removeUser(userInfo) {
this.updateUserListNode()
}
// 清空用户
function emptyUser() {
this.userList = []
this.updateUserListNode()
}
export default {
createUserListNode,
updateUserListNode,
createTextAvatar,
createImageAvatar,
addUser,
removeUser
removeUser,
emptyUser
}

View File

@@ -186,6 +186,9 @@ function createTextNode() {
if (this.getData('richText')) {
return this.createRichTextNode()
}
if (this.getData('resetRichText')) {
delete this.nodeData.data.resetRichText
}
let g = new G()
let fontSize = this.getStyle('fontSize', false)
let lineHeight = this.getStyle('lineHeight', false)

View File

@@ -104,7 +104,7 @@ function renderGeneralization() {
// 更新节点概要数据
function updateGeneralizationData() {
const childrenLength = this.children.length
const childrenLength = this.nodeData.children.length
const list = this.formatGetGeneralization()
const newList = []
list.forEach(item => {

View File

@@ -106,7 +106,7 @@ class View {
}
} else {
// 2.鼠标滚轮事件控制画布移动
const step = isTouchPad ? 5 : mousewheelMoveStep
const step = isTouchPad ? 10 : mousewheelMoveStep
let mx = 0
let my = 0
// 上移
@@ -128,6 +128,10 @@ class View {
this.translateXY(mx, my)
}
})
this.mindMap.on('resize', () => {
if (!this.checkNeedMindMapInCanvas()) return
this.transform()
})
}
// 获取当前变换状态数据
@@ -313,20 +317,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 +353,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) {

View File

@@ -85,8 +85,12 @@ class Base {
newNode.layerIndex = layerIndex
this.cacheNode(data._node.uid, newNode)
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
// 主题或主题配置改变了需要重新计算节点大小和布局
if (this.checkIsNeedResizeSources() || isLayerTypeChange) {
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
if (
this.checkIsNeedResizeSources() ||
isLayerTypeChange ||
newNode.getData('resetRichText')
) {
newNode.getSize()
newNode.needLayout = true
}
@@ -113,9 +117,14 @@ class Base {
data._node = newNode
// 主题或主题配置改变了需要重新计算节点大小和布局
const isResizeSource = this.checkIsNeedResizeSources()
// 节点数据改变了需要重新计算节点大小和布局
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本,节点数据改变了等情况需要重新计算节点大小和布局
const isNodeDataChange = lastData !== JSON.stringify(data.data)
if (isResizeSource || isNodeDataChange || isLayerTypeChange) {
if (
isResizeSource ||
isNodeDataChange ||
isLayerTypeChange ||
newNode.getData('resetRichText')
) {
newNode.getSize()
newNode.needLayout = true
}
@@ -505,9 +514,19 @@ class Base {
// 设置连线样式
setLineStyle(style, line, path, childNode) {
line.plot(path)
line.plot(this.transformPath(path))
style && style(line, childNode, true)
}
// 转换路径,可以转换成特殊风格的线条样式
transformPath(path) {
const { customTransformNodeLinePath } = this.mindMap.opt
if (customTransformNodeLinePath) {
return customTransformNodeLinePath(path)
} else {
return path
}
}
}
export default Base

View File

@@ -240,14 +240,14 @@ class CatalogOrganization extends Base {
// 父节点的竖线
let line1 = this.lineDraw.path()
node.style.line(line1)
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
line1.plot(this.transformPath(`M ${x1},${y1} L ${x1},${y1 + s1}`))
node._lines.push(line1)
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.lineDraw.path()
node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
lin2.plot(this.transformPath(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`))
node._lines.push(lin2)
style && style(lin2, node)
}
@@ -311,7 +311,9 @@ class CatalogOrganization extends Base {
if (maxy < y1 + expandBtnSize) {
lin2.hide()
} else {
lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
lin2.plot(
this.transformPath(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
)
lin2.show()
}
node._lines.push(lin2)
@@ -349,7 +351,7 @@ class CatalogOrganization extends Base {
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2

View File

@@ -253,15 +253,19 @@ class Fishbone extends Base {
let line = this.lineDraw.path()
if (this.checkIsTop(item)) {
line.plot(
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
item.left
},${item.top + item.height}`
this.transformPath(
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
item.left
},${item.top + item.height}`
)
)
} else {
line.plot(
`M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${
item.top
}`
this.transformPath(
`M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${
item.top
}`
)
)
}
node.style.line(line)
@@ -273,9 +277,11 @@ class Fishbone extends Base {
let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1)
let line = this.lineDraw.path()
line.plot(
`M ${node.left + node.width},${nodeHalfTop} L ${
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
},${nodeHalfTop}`
this.transformPath(
`M ${node.left + node.width},${nodeHalfTop} L ${
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
},${nodeHalfTop}`
)
)
node.style.line(line)
node._lines.push(line)
@@ -372,7 +378,7 @@ class Fishbone extends Base {
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2

View File

@@ -197,10 +197,12 @@ class LogicalStructure extends Base {
if (!this.mindMap.opt.alwaysShowExpandBtn) {
expandBtnSize = 0
}
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
const { nodeUseLineStyle } = this.mindMap.themeConfig
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
if (node.layerIndex === 0) {
expandBtnSize = 0
}
let x1 = left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
@@ -224,10 +226,19 @@ class LogicalStructure extends Base {
if (!this.mindMap.opt.alwaysShowExpandBtn) {
expandBtnSize = 0
}
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
const {
nodeUseLineStyle,
rootLineStartPositionKeepSameInCurve,
rootLineKeepSameInCurve
} = this.mindMap.themeConfig
node.children.forEach((item, index) => {
if (node.layerIndex === 0) {
expandBtnSize = 0
}
let x1 =
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
? left + width / 2
: left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
@@ -238,7 +249,7 @@ class LogicalStructure extends Base {
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left + item.width},${y2}`
: ''
if (node.isRoot && !this.mindMap.themeConfig.rootLineKeepSameInCurve) {
if (node.isRoot && !rootLineKeepSameInCurve) {
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath

View File

@@ -259,12 +259,13 @@ class MindMap extends Base {
if (!this.mindMap.opt.alwaysShowExpandBtn) {
expandBtnSize = 0
}
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
const { nodeUseLineStyle } = this.mindMap.themeConfig
node.children.forEach((item, index) => {
if (node.layerIndex === 0) {
expandBtnSize = 0
}
let x1 =
node.layerIndex === 0
? left + width / 2
: item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
? left - expandBtnSize
: left + width + expandBtnSize
let y1 = top + height / 2
@@ -298,10 +299,17 @@ class MindMap extends Base {
if (!this.mindMap.opt.alwaysShowExpandBtn) {
expandBtnSize = 0
}
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
const {
nodeUseLineStyle,
rootLineKeepSameInCurve,
rootLineStartPositionKeepSameInCurve
} = this.mindMap.themeConfig
node.children.forEach((item, index) => {
if (node.layerIndex === 0) {
expandBtnSize = 0
}
let x1 =
node.layerIndex === 0
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
? left + width / 2
: item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
? left - expandBtnSize
@@ -317,14 +325,14 @@ class MindMap extends Base {
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = ''
if (this.mindMap.themeConfig.nodeUseLineStyle) {
if (nodeUseLineStyle) {
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
nodeUseLineStylePath = ` L ${item.left},${y2}`
} else {
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
}
}
if (node.isRoot && !this.mindMap.themeConfig.rootLineKeepSameInCurve) {
if (node.isRoot && !rootLineKeepSameInCurve) {
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath

View File

@@ -161,13 +161,14 @@ class OrganizationStructure extends Base {
return []
}
let { left, top, width, height } = node
const { nodeUseLineStyle } = this.mindMap.themeConfig
let x1 = left + width / 2
let y1 = top + height
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = item.top
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
@@ -213,14 +214,16 @@ class OrganizationStructure extends Base {
let line1 = this.lineDraw.path()
node.style.line(line1)
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
line1.plot(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
line1.plot(
this.transformPath(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
)
node._lines.push(line1)
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.lineDraw.path()
node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
lin2.plot(this.transformPath(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`))
node._lines.push(lin2)
style && style(lin2, node)
}
@@ -253,7 +256,7 @@ class OrganizationStructure extends Base {
let cx = x1 + (x2 - x1) / 2
let cy = y1 + 20
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.top = bottom + generalizationNodeMargin
item.generalizationNode.left =
left + (right - left - item.generalizationNode.width) / 2

View File

@@ -274,9 +274,13 @@ class Timeline extends Base {
node.parent.isRoot &&
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
) {
line.plot(`M ${x},${top} L ${x},${miny}`)
line.plot(this.transformPath(`M ${x},${top} L ${x},${miny}`))
} else {
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
line.plot(
this.transformPath(
`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`
)
)
}
node.style.line(line)
node._lines.push(line)
@@ -325,9 +329,10 @@ class Timeline extends Base {
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.left = right + generalizationNodeMargin
item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2
item.generalizationNode.top =
top + (bottom - top - item.generalizationNode.height) / 2
})
}

View File

@@ -398,7 +398,7 @@ class VerticalTimeline extends Base {
let cx = x1 + (isLeft ? -20 : 20)
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(path)
item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.left =
x +
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -

View File

@@ -36,12 +36,18 @@ export default {
}) {
if (node.parent && node.parent.isRoot) {
line.plot(
`M ${x},${top} L ${x + lineLength},${
top - Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
}`
ctx.transformPath(
`M ${x},${top} L ${x + lineLength},${
top - Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
}`
)
)
} else {
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
line.plot(
ctx.transformPath(
`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`
)
)
}
},
computedLeftTopValue({ layerIndex, node, ctx }) {
@@ -135,14 +141,16 @@ export default {
renderLine({ node, line, top, x, lineLength, height, miny, ctx }) {
if (node.parent && node.parent.isRoot) {
line.plot(
`M ${x},${top + height} L ${x + lineLength},${
top +
height +
Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
}`
ctx.transformPath(
`M ${x},${top + height} L ${x + lineLength},${
top +
height +
Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
}`
)
)
} else {
line.plot(`M ${x},${top} L ${x},${miny}`)
line.plot(ctx.transformPath(`M ${x},${top} L ${x},${miny}`))
}
},
computedLeftTopValue({ layerIndex, node, ctx }) {

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

@@ -451,6 +451,17 @@ class AssociativeLine {
endPoint.x,
endPoint.y
)
// 检查是否存在固定位置的配置
const { associativeLineInitPointsPosition } = this.mindMap.opt
if (associativeLineInitPointsPosition) {
const { from, to } = associativeLineInitPointsPosition
if (from) {
startPoint.dir = from
}
if (to) {
endPoint.dir = to
}
}
let offsetList =
fromNode.getData('associativeLineTargetControlOffsets') || []
// 保存的实际是控制点和端点的差值,否则当节点位置改变了,控制点还是原来的位置,连线就不对了

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()
}
@@ -125,28 +127,58 @@ class Cooperate {
// 当前思维导图改变后的处理,触发同步
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 +209,12 @@ class Cooperate {
this.waitNodeUidMap = {}
}
// 监听思维导图数据的重新设置事件
onSetData(data) {
this.isSetData = true
this.initData(data)
}
// 设置用户信息
/**
* {
@@ -220,6 +258,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

@@ -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 {
@@ -294,6 +295,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

@@ -7,7 +7,8 @@ import {
isWhite,
getVisibleColorFromTheme,
isUndef,
checkSmmFormatData
checkSmmFormatData,
removeHtmlNodeByClass
} from '../utils'
import { CONSTANTS } from '../constants/constant'
@@ -309,6 +310,8 @@ class RichText {
// 获取当前正在编辑的内容
getEditText() {
let html = this.quill.container.firstChild.innerHTML
// 去除ql-cursor节点
html = removeHtmlNodeByClass(html, '.ql-cursor')
// 去除最后的空行
return html.replace(/<p><br><\/p>$/, '')
}
@@ -323,10 +326,10 @@ class RichText {
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
list.forEach(node => {
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
if (node.isGeneralization) {
// if (node.isGeneralization) {
// 概要节点
node.generalizationBelongNode.updateGeneralization()
}
// node.generalizationBelongNode.updateGeneralization()
// }
this.mindMap.render()
})
this.mindMap.emit('hide_text_edit', this.textEditNode, list)
@@ -488,7 +491,7 @@ class RichText {
// 格式化当前选中的文本
formatText(config = {}, clear = false, pure = false) {
if (!this.range && !this.lastRange) return
if(!pure) this.syncFormatToNodeConfig(config, clear)
if (!pure) this.syncFormatToNodeConfig(config, clear)
let rangeLost = !this.range
let range = rangeLost ? this.lastRange : this.range
clear
@@ -636,36 +639,6 @@ class RichText {
}
}
// 处理导出为图片
async handleExportPng(node) {
let el = document.createElement('div')
el.style.position = 'absolute'
el.style.left = '-9999999px'
el.appendChild(node)
this.mindMap.el.appendChild(el)
// 遍历所有节点将它们的margin和padding设为0
let walk = root => {
root.style.margin = 0
root.style.padding = 0
if (root.hasChildNodes()) {
Array.from(root.children).forEach(item => {
walk(item)
})
}
}
walk(node)
// 如果使用html2canvas
// let canvas = await html2canvas(el, {
// backgroundColor: null
// })
// return canvas.toDataURL()
const res = await domtoimage.toPng(el)
this.mindMap.el.removeChild(el)
return res
}
// 将所有节点转换成非富文本节点
transformAllNodesToNormalNode() {
walk(

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,14 @@ 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
bfsWalk(tree, node => {
let { richText, text } = isOnlySearchCurrentRenderNodes
? node.getData()
: node.data
if (richText) {
text = getTextFromHtml(text)
}
@@ -103,14 +110,25 @@ 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 =
currentNode instanceof Node
? currentNode.getData('uid')
: currentNode.data.uid
const targetNode = this.mindMap.renderer.findNodeByUid(uid)
this.mindMap.execCommand('GO_TARGET_NODE', uid, node => {
if (!(currentNode instanceof Node)) {
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
}
})
}

View File

@@ -221,6 +221,7 @@ function resetControlPoint() {
// 渲染控制点
function renderControls(startPoint, endPoint, point1, point2) {
if (!this.mindMap.opt.enableAdjustAssociativeLinePoints) return
if (!this.controlLine1) {
this.createControlNodes()
}

View File

@@ -1,3 +1,5 @@
import { getRectRelativePosition } from '../../utils/index'
// 获取目标节点在起始节点的目标数组中的索引
export const getAssociativeLineTargetIndex = (node, toNode) => {
return node.getData('associativeLineTargets').findIndex(item => {
@@ -7,14 +9,21 @@ export const getAssociativeLineTargetIndex = (node, toNode) => {
// 计算贝塞尔曲线的控制点
export const computeCubicBezierPathPoints = (x1, y1, x2, y2) => {
const min = 5
let cx1 = x1 + (x2 - x1) / 2
let cy1 = y1
let cx2 = cx1
let cy2 = y2
if (Math.abs(x1 - x2) <= 5) {
if (Math.abs(x1 - x2) <= min) {
cx1 = x1 + (y2 - y1) / 2
cx2 = cx1
}
if (Math.abs(y1 - y2) <= min) {
cx1 = x1
cy1 = y1 - (x2 - x1) / 2
cx2 = x2
cy2 = cy1
}
return [
{
x: cx1,
@@ -39,7 +48,9 @@ const getNodeRect = node => {
right: left + width,
bottom: top + height,
left,
top
top,
width,
height
}
}
@@ -169,22 +180,26 @@ export const getNodePoint = (node, dir = 'right', range = 0, e = null) => {
case 'left':
return {
x: left,
y: top + height / 2 - range
y: top + height / 2 - range,
dir
}
case 'right':
return {
x: left + width,
y: top + height / 2 - range
y: top + height / 2 - range,
dir
}
case 'top':
return {
x: left + width / 2 - range,
y: top
y: top,
dir
}
case 'bottom':
return {
x: left + width / 2 - range,
y: top + height
y: top + height,
dir
}
default:
break
@@ -193,34 +208,64 @@ export const getNodePoint = (node, dir = 'right', range = 0, e = null) => {
// 根据两个节点的位置计算节点的连接点
export const computeNodePoints = (fromNode, toNode) => {
let fromRect = getNodeRect(fromNode)
let fromCx = (fromRect.right + fromRect.left) / 2
let fromCy = (fromRect.bottom + fromRect.top) / 2
let toRect = getNodeRect(toNode)
let toCx = (toRect.right + toRect.left) / 2
let toCy = (toRect.bottom + toRect.top) / 2
// 中心点坐标的差值
let offsetX = toCx - fromCx
let offsetY = toCy - fromCy
if (offsetX === 0 && offsetY === 0) return []
const fromRect = getNodeRect(fromNode)
const toRect = getNodeRect(toNode)
let fromDir = ''
let toDir = ''
if (offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY) {
// left
fromDir = 'left'
toDir = 'right'
} else if (offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY) {
// right
fromDir = 'right'
toDir = 'left'
} else if (offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX) {
// up
fromDir = 'top'
toDir = 'bottom'
} else if (offsetY > 0 && -offsetY < offsetX && offsetY > offsetX) {
// down
fromDir = 'right'
toDir = 'right'
const dir = getRectRelativePosition(
{
x: fromRect.left,
y: fromRect.top,
width: fromRect.width,
height: fromRect.height
},
{
x: toRect.left,
y: toRect.top,
width: toRect.width,
height: toRect.height
}
)
// 起始矩形在结束矩形的什么方向
switch (dir) {
case 'left-top':
fromDir = 'right'
toDir = 'top'
break
case 'right-top':
fromDir = 'left'
toDir = 'top'
break
case 'right-bottom':
fromDir = 'left'
toDir = 'bottom'
break
case 'left-bottom':
fromDir = 'right'
toDir = 'bottom'
break
case 'left':
fromDir = 'right'
toDir = 'left'
break
case 'right':
fromDir = 'left'
toDir = 'right'
break
case 'top':
fromDir = 'right'
toDir = 'right'
break
case 'bottom':
fromDir = 'left'
toDir = 'left'
break
case 'overlap':
fromDir = 'right'
toDir = 'right'
break
default:
break
}
return [getNodePoint(fromNode, fromDir), getNodePoint(toNode, toDir)]
}
@@ -230,8 +275,9 @@ export const getNodeLinePath = (startPoint, endPoint, node, toNode) => {
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
// 控制点
let controlPoints = []
let associativeLineTargetControlOffsets =
node.getData('associativeLineTargetControlOffsets')
let associativeLineTargetControlOffsets = node.getData(
'associativeLineTargetControlOffsets'
)
if (
associativeLineTargetControlOffsets &&
associativeLineTargetControlOffsets[targetIndex]

View File

@@ -20,9 +20,11 @@ export default {
lineStyle: 'straight', // 曲线curve【仅支持logicalStructure、mindMap、verticalTimeline三种结构】、直线straight、直连direct【仅支持logicalStructure、mindMap、organizationStructure、verticalTimeline四种结构】
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型设为true后都为 { 型。仅支持logicalStructure、mindMap两种结构
rootLineKeepSameInCurve: true,
// 曲线连接时根节点和其他节点的连线起始位置保持统一默认根节点的连线起始位置在节点中心其他节点在节点右侧如果该配置设为true那么根节点的连线起始位置也会在节点右侧
rootLineStartPositionKeepSameInCurve: false,
// 直线连接(straight)时连线的圆角大小设置为0代表没有圆角仅支持logicalStructure、mindMap、verticalTimeline三种结构
lineRadius: 5,
// 连线尾部是否显示标记,目前只支持箭头
// 连线是否显示标记,目前只支持箭头
showLineMarker: false,
// 概要连线的粗细
generalizationLineWidth: 1,
@@ -77,7 +79,9 @@ export default {
textDecoration: 'none',
gradientStyle: false,
startColor: '#549688',
endColor: '#fff'
endColor: '#fff',
// 连线标记的位置start头部、end尾部该配置在showLineMarker配置为true时生效
lineMarkerDir: 'end'
},
// 二级节点样式
second: {
@@ -98,7 +102,8 @@ export default {
textDecoration: 'none',
gradientStyle: false,
startColor: '#549688',
endColor: '#fff'
endColor: '#fff',
lineMarkerDir: 'end'
},
// 三级及以下节点样式
node: {
@@ -119,7 +124,8 @@ export default {
textDecoration: 'none',
gradientStyle: false,
startColor: '#549688',
endColor: '#fff'
endColor: '#fff',
lineMarkerDir: 'end'
},
// 概要节点样式
generalization: {
@@ -176,6 +182,7 @@ const nodeSizeIndependenceList = [
'backgroundPosition',
'backgroundSize',
'rootLineKeepSameInCurve',
'rootLineStartPositionKeepSameInCurve',
'showLineMarker',
'gradientStyle',
'lineRadius',
@@ -196,4 +203,9 @@ export const checkIsNodeSizeIndependenceConfig = config => {
return true
}
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']
export const lineStyleProps = [
'lineColor',
'lineDasharray',
'lineWidth',
'lineMarkerDir'
]

View File

@@ -4,6 +4,7 @@ import {
selfCloseTagList
} from '../constants/constant'
import MersenneTwister from './mersenneTwister'
// 深度优先遍历树
export const walk = (
root,
@@ -489,9 +490,27 @@ export const removeHtmlStyle = html => {
}
// 给html标签中指定的标签添加内联样式
let addHtmlStyleEl = null
export const addHtmlStyle = (html, tag, style) => {
const reg = new RegExp(`(<${tag}[^>]*)(>[^<>]*</${tag}>)`, 'g')
return html.replaceAll(reg, `$1 style="${style}"$2`)
if (!addHtmlStyleEl) {
addHtmlStyleEl = document.createElement('div')
}
addHtmlStyleEl.innerHTML = html
let walk = root => {
let childNodes = root.childNodes
childNodes.forEach(node => {
if (node.nodeType === 1) {
// 元素节点
if (node.tagName.toLowerCase() === tag) {
node.style.cssText = style
} else {
walk(node)
}
}
})
}
walk(addHtmlStyleEl)
return addHtmlStyleEl.innerHTML
}
// 检查一个字符串是否是富文本字符
@@ -535,6 +554,20 @@ export const replaceHtmlText = (html, searchText, replaceText) => {
return replaceHtmlTextEl.innerHTML
}
// 去除html字符串中指定选择器的节点然后返回html字符串
let removeHtmlNodeByClassEl = null
export const removeHtmlNodeByClass = (html, selector) => {
if (!removeHtmlNodeByClassEl) {
removeHtmlNodeByClassEl = document.createElement('div')
}
removeHtmlNodeByClassEl.innerHTML = html
const node = removeHtmlNodeByClassEl.querySelector(selector)
if (node) {
node.parentNode.removeChild(node)
}
return removeHtmlNodeByClassEl.innerHTML
}
// 判断一个颜色是否是白色
export const isWhite = color => {
color = String(color).replaceAll(/\s+/g, '')
@@ -656,6 +689,52 @@ export const textToNodeRichTextWithWrap = html => {
.join('')
}
// 去除富文本内容的样式包括样式标签比如strong、em、s等
// 但要保留数学公式内容
let removeRichTextStyesEl = null
export const removeRichTextStyes = html => {
if (!removeRichTextStyesEl) {
removeRichTextStyesEl = document.createElement('div')
}
removeRichTextStyesEl.innerHTML = html
// 首先用占位文本替换掉所有的公式
const formulaList = removeRichTextStyesEl.querySelectorAll('.ql-formula')
Array.from(formulaList).forEach(el => {
const placeholder = document.createTextNode('$smmformula$')
el.parentNode.replaceChild(placeholder, el)
})
// 然后遍历每行节点,去掉内部的所有标签,转为文本
const childNodes = removeRichTextStyesEl.childNodes
let list = []
for (let i = 0; i < childNodes.length; i++) {
const node = childNodes[i]
if (node.nodeType === 1) {
// 元素节点
list.push(node.textContent)
} else if (node.nodeType === 3) {
// 文本节点
list.push(node.nodeValue)
}
}
// 拼接文本
html = list
.map(item => {
return `<p><span>${htmlEscape(item)}</span></p>`
})
.join('')
// 将公式添加回去
if (formulaList.length > 0) {
html = html.replace(/\$smmformula\$/g, '<span class="smmformula"></span>')
removeRichTextStyesEl.innerHTML = html
const els = removeRichTextStyesEl.querySelectorAll('.smmformula')
Array.from(els).forEach((el, index) => {
el.parentNode.replaceChild(formulaList[index], el)
})
html = removeRichTextStyesEl.innerHTML
}
return html
}
// 判断是否是移动端环境
export const isMobile = () => {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
@@ -1198,3 +1277,41 @@ export const transformObjectToTreeData = data => {
})
return res
}
// 计算两个点的直线距离
export const getTwoPointDistance = (x1, y1, x2, y2) => {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
}
// 判断两个矩形的相对位置
// 第一个矩形在第二个矩形的什么方向
export const getRectRelativePosition = (rect1, rect2) => {
// 获取第一个矩形的中心点坐标
const rect1CenterX = rect1.x + rect1.width / 2
const rect1CenterY = rect1.y + rect1.height / 2
// 获取第二个矩形的中心点坐标
const rect2CenterX = rect2.x + rect2.width / 2
const rect2CenterY = rect2.y + rect2.height / 2
// 判断第一个矩形在第二个矩形的哪个方向
if (rect1CenterX < rect2CenterX && rect1CenterY < rect2CenterY) {
return 'left-top'
} else if (rect1CenterX > rect2CenterX && rect1CenterY < rect2CenterY) {
return 'right-top'
} else if (rect1CenterX > rect2CenterX && rect1CenterY > rect2CenterY) {
return 'right-bottom'
} else if (rect1CenterX < rect2CenterX && rect1CenterY > rect2CenterY) {
return 'left-bottom'
} else if (rect1CenterX < rect2CenterX && rect1CenterY === rect2CenterY) {
return 'left'
} else if (rect1CenterX > rect2CenterX && rect1CenterY === rect2CenterY) {
return 'right'
} else if (rect1CenterX === rect2CenterX && rect1CenterY < rect2CenterY) {
return 'top'
} else if (rect1CenterX === rect2CenterX && rect1CenterY > rect2CenterY) {
return 'bottom'
} else {
return 'overlap'
}
}

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,4 @@ content = content.replace(
/(MindMap.version\s*=\s*)[^\n]+(\n)/,
`$1'${pkg.version}'$2`
)
fs.writeFileSync(file, content)
fs.writeFileSync(file, content)

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -55,7 +55,9 @@ export const themeMap = {
classic5: require('../assets/img/themes/classic5.jpg'),
dark3: require('../assets/img/themes/dark3.jpg'),
dark4: require('../assets/img/themes/dark4.jpg'),
cactus: require('../assets/img/themes/cactus.jpg')
cactus: require('../assets/img/themes/cactus.jpg'),
classic6: require('../assets/img/themes/classic6.jpg'),
classic7: require('../assets/img/themes/classic7.jpg'),
}
// 公式列表

View File

@@ -330,6 +330,36 @@ export const shortcutKeyList = [
value: 'Ctrl + i'
}
]
},
{
type: 'Outline Operation',
list: [
{
icon: 'iconhuanhang',
name: 'Text Wrap',
value: 'Shift + Enter'
},
{
icon: 'iconshanchu',
name: 'Delete current node',
value: 'Delete'
},
{
icon: 'icontianjiazijiedian',
name: 'Inert child node',
value: 'Tab'
},
{
icon: 'iconjiedian',
name: 'Insert sibling node',
value: 'Enter'
},
{
icon: 'icondodeparent',
name: 'Move up one level',
value: 'Shift + Tab'
},
]
}
]
@@ -450,5 +480,11 @@ export const downTypeList = [
type: 'xmind',
icon: 'iconxmind',
desc: 'XMind file'
},
{
name: 'Txt',
type: 'txt',
icon: 'iconTXT',
desc: 'Plain text file'
}
]

View File

@@ -397,6 +397,36 @@ export const shortcutKeyList = [
value: 'Ctrl + i'
}
]
},
{
type: '大纲操作',
list: [
{
icon: 'iconhuanhang',
name: '文本换行',
value: 'Shift + Enter'
},
{
icon: 'iconshanchu',
name: '删除节点',
value: 'Delete'
},
{
icon: 'icontianjiazijiedian',
name: '插入下级节点',
value: 'Tab'
},
{
icon: 'iconjiedian',
name: '插入同级节点',
value: 'Enter'
},
{
icon: 'icondodeparent',
name: '上移一个层级',
value: 'Shift + Tab'
},
]
}
]
@@ -545,5 +575,11 @@ export const downTypeList = [
type: 'xmind',
icon: 'iconxmind',
desc: 'XMind格式'
},
{
name: 'Txt',
type: 'txt',
icon: 'iconTXT',
desc: '纯文本文件'
}
]

View File

@@ -0,0 +1,45 @@
// 经典6
export default {
backgroundColor: 'rgb(255, 255, 255)',
// 连线的颜色
lineColor: 'rgb(0, 0, 0)',
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: 'rgb(0, 0, 0)',
// 关联线默认状态的颜色
associativeLineColor: 'rgb(152, 162, 171)',
// 关联线文字颜色
associativeLineTextColor: 'rgb(68, 68, 68)',
// 根节点样式
root: {
fillColor: 'rgb(237, 182, 72)',
color: 'rgb(0, 0, 0)',
borderColor: 'rgb(0, 0, 0)',
borderWidth: 2,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(114, 158, 28)',
color: '#fff',
borderColor: 'rgb(0, 0, 0)',
borderWidth: 2,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(10, 2, 2)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: '#fff',
borderColor: '',
borderWidth: 0,
color: 'rgb(10, 2, 2)'
}
}

View File

@@ -0,0 +1,44 @@
// 经典7
export default {
backgroundColor: 'rgb(255, 255, 255)',
// 连线的颜色
lineColor: 'rgb(237, 185, 81)',
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: 'rgb(226, 90, 64)',
// 关联线默认状态的颜色
associativeLineColor: 'rgb(152, 162, 171)',
// 关联线文字颜色
associativeLineTextColor: 'rgb(68, 68, 68)',
// 根节点样式
root: {
fillColor: 'rgb(226, 90, 64)',
color: '#fff',
borderColor: '',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(43, 118, 239)',
color: '#fff',
borderColor: '',
borderWidth: 0,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(43, 118, 239)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: '#fff',
borderColor: '',
borderWidth: 0,
color: 'rgb(43, 118, 239)'
}
}

View File

@@ -7,6 +7,8 @@ import neonLamp from './neonLamp'
import darkNightLceBlade from './darkNightLceBlade'
import morandi from './morandi'
import classic5 from './classic5'
import classic6 from './classic6'
import classic7 from './classic7'
import dark3 from './dark3'
import dark4 from './dark4'
import cactus from './cactus'
@@ -83,5 +85,17 @@ export default [
value: 'cactus',
theme: cactus,
dark: false
},
{
name: '脑图经典6',
value: 'classic6',
theme: classic6,
dark: false
},
{
name: '脑图经典7',
value: 'classic7',
theme: classic7,
dark: false
}
]

View File

@@ -58,7 +58,11 @@ export default {
associativeLineText: 'Associative line text',
fontFamily: 'Font family',
fontSize: 'Font size',
isShowScrollbar: 'Is show scrollbar'
isShowScrollbar: 'Is show scrollbar',
isUseHandDrawnLikeStyle: 'Is use hand drawn like style',
rootLineStartPos: 'Root line start pos',
center: 'Center',
right: 'Right'
},
color: {
moreColor: 'More color'
@@ -90,7 +94,9 @@ export default {
fitCanvas: 'Fit canvas',
removeImage: 'Remove image',
removeHyperlink: 'Remove hyperlink',
removeNote: 'Remove note'
removeNote: 'Remove note',
removeCustomStyles: 'Remove custom styles',
removeAllNodeCustomStyles: 'Remove all node custom styles'
},
count: {
words: 'Words',
@@ -143,7 +149,8 @@ export default {
openMiniMap: 'Open mini map',
closeMiniMap: 'Close mini map',
readonly: 'Change to eadonly',
edit: 'Change to edit'
edit: 'Change to edit',
backToRoot: 'Back to root node'
},
nodeHyperlink: {
title: 'Link',
@@ -206,7 +213,10 @@ export default {
vertical: 'Vertical',
gradientStyle: 'Gradient',
startColor: 'Start',
endColor: 'End'
endColor: 'End',
arrowDir: 'Arrow dir',
arrowDirStart: 'Start',
arrowDirEnd: 'End'
},
theme: {
title: 'Theme',
@@ -254,7 +264,8 @@ export default {
fileContentError: 'File content error',
fileOpenFailed: 'File open failed',
defaultFileName: 'Mind map',
creatingTip: 'Creating file'
creatingTip: 'Creating file',
directory: 'Directory'
},
edit: {
newFeatureNoticeTitle: 'New feature reminder',

View File

@@ -58,7 +58,11 @@ export default {
associativeLineText: '关联线文字',
fontFamily: '字体',
fontSize: '字号',
isShowScrollbar: '是否显示滚动条'
isShowScrollbar: '是否显示滚动条',
isUseHandDrawnLikeStyle: '是否开启手绘风格',
rootLineStartPos: '根节点连线起始位置',
center: '中心',
right: '右侧'
},
color: {
moreColor: '更多颜色'
@@ -90,7 +94,9 @@ export default {
fitCanvas: '适应画布',
removeImage: '移除图片',
removeHyperlink: '移除超链接',
removeNote: '移除备注'
removeNote: '移除备注',
removeCustomStyles: '一键去除自定义样式',
removeAllNodeCustomStyles: '一键去除所有节点自定义样式'
},
count: {
words: '字数',
@@ -141,7 +147,8 @@ export default {
openMiniMap: '开启小地图',
closeMiniMap: '关闭小地图',
readonly: '切换为只读模式',
edit: '切换为编辑模式'
edit: '切换为编辑模式',
backToRoot: '回到根节点'
},
nodeHyperlink: {
title: '超链接',
@@ -204,7 +211,10 @@ export default {
vertical: '垂直',
gradientStyle: '渐变',
startColor: '起始',
endColor: '结束'
endColor: '结束',
arrowDir: '箭头位置',
arrowDirStart: '头部',
arrowDirEnd: '尾部'
},
theme: {
title: '主题',
@@ -250,7 +260,8 @@ export default {
fileContentError: '文件内容有误',
fileOpenFailed: '文件打开失败',
defaultFileName: '思维导图',
creatingTip: '正在创建文件'
creatingTip: '正在创建文件',
directory: '目录'
},
edit: {
newFeatureNoticeTitle: '新特性提醒',

View File

@@ -38,6 +38,7 @@ let APIList = [
'scrollbar',
'formula',
'cooperate',
'handDrawnLikeStyle',
'xmind',
'markdown',
'utils'

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