Compare commits

..

57 Commits

Author SHA1 Message Date
街角小林
2d0310f675 打包0.12.2 2024-12-17 09:12:52 +08:00
街角小林
4ef99958b6 Feat:去除richTextEditFakeInPlace实例化选项 2024-12-17 09:01:25 +08:00
街角小林
ec677c781e Feat:支持调整关联线控制点位置后激活状态不丢失,提升使用体验 2024-12-16 17:20:28 +08:00
街角小林
2822dcc99a Fix:修复关联线设置激活样式时控制线条颜色没同步更新的问题 2024-12-16 17:09:29 +08:00
wanglin2
46e3d85b5f update 2024-12-14 17:54:48 +08:00
wanglin2
024271ec54 update 2024-12-14 17:38:58 +08:00
街角小林
da9fd4c36d 打包0.12.2 2024-12-13 15:19:36 +08:00
街角小林
5a291b4a5f Fix:修复添加数学公式报错的问题 2024-12-12 16:33:24 +08:00
街角小林
3f002ce2ee Feat:增加节点宽度拖拽手柄的大小 2024-12-12 09:18:31 +08:00
街角小林
78a242faff Feat:新增扩展快捷键内部键值映射对象的方法 2024-12-11 18:19:47 +08:00
街角小林
be229a0c04 Demo:支持单独设置每条关联线的样式 2024-12-11 17:54:27 +08:00
街角小林
9a36cd4478 Fix:修复关联线文本编辑时按回车键无法结束编辑的问题 2024-12-11 17:53:47 +08:00
街角小林
732b6b50b0 Feat:1.支持单独定义每条关联线的样式;2.新增取消激活关联线的事件;3.修复关联线文本存在空行时编辑框渲染异常的问题 2024-12-11 17:53:08 +08:00
街角小林
ade7a95f3c Feat:转换富文本节点数据的逻辑由Render类移至RichText插件本身 2024-12-09 18:35:31 +08:00
街角小林
322975528e Feat:新增改变[思维导图]布局结构行为的插件,使之更符合目前主流思维导图的做法 2024-12-09 18:32:54 +08:00
街角小林
2a76f5a0bc Feat:新增抛出命令执行后的事件 2024-12-09 18:10:18 +08:00
街角小林
2a7eaefac5 Feat:updateData和setData方法新增抛出更新数据前的事件 2024-12-09 18:05:39 +08:00
街角小林
68784f3e4d Feat:升级quill版本;Fix:修复富文本模式下文本输入连续多个空格会压缩成一个的问题 2024-12-09 11:38:11 +08:00
街角小林
c29477ed55 Demo:支持双击节点备注图标进入备注编辑 2024-12-06 17:43:24 +08:00
街角小林
88a6442539 Feat:新增节点备注图标双击事件 2024-12-06 17:40:19 +08:00
街角小林
24363d55a4 Feat:非富文本模式支持输入和渲染空行;Fix:修复非富文本模式下节点存在图标时,进入文本编辑时文本编辑框位置不正确的问题 2024-12-06 16:41:27 +08:00
街角小林
7fd4c7504d Feat:去掉文本编辑框元素的某些样式,避免文本模糊的问题 2024-12-06 16:39:50 +08:00
街角小林
efb4dcf236 Fix:修复非富文本模式下文本编辑实时渲染功能在一些结构中会出现异常的问题 2024-12-06 15:17:59 +08:00
街角小林
53c2af0bc0 Fix:修复只读模式实例化后再切换为编辑模式时没有将当前数据入栈的问题 2024-12-06 09:45:07 +08:00
街角小林
74d302639a Feat:新增实例化时是否进行一次历史数据入栈操作的选项 2024-12-06 09:28:20 +08:00
街角小林
0a2e4e7c14 Feat:去除富文本插件中去除最后一行空行的逻辑 2024-12-05 09:56:52 +08:00
街角小林
7d399b436b Feat:上移、下移命令支持指定操作节点 2024-12-04 18:49:23 +08:00
街角小林
f02098f697 Demo:支持配置节点连线的流动效果 2024-12-03 18:35:45 +08:00
街角小林
fbb3b47b7d Feat:修改tryAddHtmlStyle方法的逻辑 2024-12-03 17:52:23 +08:00
街角小林
508d8fe357 Feat:1.优化节点样式设置,如果设置的是连线样式不触发节点重新创建;2.优化富文本模式下同时对大量节点调用setStyle方法修改文本样式非常慢的问题 2024-11-27 19:06:14 +08:00
街角小林
7258ed9ea7 Feat:新增自定义处理节点连线元素的实例化选项 2024-11-22 16:51:56 +08:00
街角小林
6ae5d244f1 Fix:修复http协议下可以在概要节点上粘贴下级节点的问题 2024-11-20 18:31:30 +08:00
街角小林
7213348c12 Feat:调整粘贴逻辑,如果支持操作剪贴板数据,那么以剪贴板数据为准,否则以画布内数据为准 2024-11-20 18:27:38 +08:00
街角小林
93092db49f Fix:修复直线风格连线圆角不为0时某些场景会存在不必要的凸起问题 2024-11-13 09:17:13 +08:00
街角小林
b8df51eb02 Fix:修复代码缺陷和优化代码 2024-11-12 18:44:35 +08:00
街角小林
6cdc2ff526 Merge pull request #1000 from Tarrency/switchtheme-fontcolor
fix: 解决切换主题前后,设置了部分字体样式的节点,其未设置的字体样式没有响应新主题样式的bug
2024-11-12 18:34:53 +08:00
街角小林
b1303ce7a5 Fix:修复搜索内容为全部替换内容的子串时连续点击全部替换搜索结果异常的问题 2024-11-12 17:53:42 +08:00
街角小林
ac5bb1d684 Fix:修复插件文件的类型定义文件没有生成的问题 2024-11-12 16:57:13 +08:00
wangqi01
ad8cf74bba fix: 解决切换主题前后,设置了部分字体样式的节点,其未设置的字体样式没有响应新主题样式的bug 2024-11-12 16:02:58 +08:00
街角小林
4e6688e4e0 Demo:节点右键菜单新增收起所有下级节点按钮 2024-11-08 17:19:05 +08:00
街角小林
15859e76b6 Merge pull request #985 from ZhangMingZhao1/fix-richtext-list
fix: 限定富文本节点的编辑态格式类型 , 避免富文本节点编辑态下会触发渲染问题和结束编辑后的不一致问题,如有序列表(数字加点符号加空格触发)和无序列表(-符号加空格触发)
2024-11-08 09:22:19 +08:00
ZhangMingZhao1
eb89bd71e1 fix: 明确支持的指定的富文本格式渲染,避免富文本节点输入时触发其他渲染错乱问题 2024-11-07 17:16:02 +08:00
街角小林
d8a88f94d7 Feat:阻止非富文本模式时的节点文本可被选中 2024-11-07 09:41:00 +08:00
街角小林
fa7761b5a3 Feat:1.只有当按键事件的事件目标为body或节点文本编辑框元素时才响应快捷键事件;2.新增自定义响应快捷键事件的实例化选项 2024-11-07 09:33:26 +08:00
街角小林
e769c9602b Feat:只有当按键事件的事件目标为body时才允许自动进入节点文本编辑模式 2024-11-07 09:27:18 +08:00
街角小林
2078d38092 update 2024-11-07 09:08:04 +08:00
街角小林
97834086d0 Merge branch 'feature' of https://github.com/wanglin2/mind-map into feature 2024-11-07 09:07:15 +08:00
街角小林
8fe0a53ba1 Merge pull request #973 from ZhangMingZhao1/fix-scale-problem
feat: 解决缩放非整数时编辑浮层dom会出现锯齿问题
2024-11-07 09:07:08 +08:00
街角小林
20fb9c3067 update 2024-11-07 09:03:05 +08:00
街角小林
1573141f2c Merge pull request #941 from ZhangMingZhao1/fix-mousedown-style
fix: 解决设置mousedownEventPreventDefault下框选会选中节点文字闪动问题
2024-11-07 09:02:32 +08:00
街角小林
dba711c9ef Feat:mousedownEventPreventDefault选项默认值改为false、富文本模式节点文字禁止被选中 2024-11-06 19:08:21 +08:00
街角小林
be3faa0aef Feat:新增通过按键进入文本编辑时是否自动清空原有文本 2024-11-06 18:16:02 +08:00
街角小林
d93511eca4 Fix:修复富文本模式的一些情况下节点文本和编辑文本位置存在偏差的问题 2024-11-06 18:03:23 +08:00
街角小林
1d75e8519d update README 2024-11-04 17:08:41 +08:00
zhangmingzhao
02460c0642 feat: 解决缩放非整数时编辑浮层dom会出现锯齿问题 2024-11-01 19:47:05 +08:00
街角小林
a43ad7aa06 update README 2024-10-30 15:49:30 +08:00
ZhangMingZhao1
c6a8ec257c fix: 解决设置mousedownEventPreventDefault下框选会选中节点文字闪动问题 2024-10-16 11:07:03 +08:00
50 changed files with 1571 additions and 618 deletions

222
README.md
View File

@@ -44,7 +44,7 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。百度云
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
> RichText节点富文本插件、Select鼠标多选节点插件、Drag节点拖拽插件、AssociativeLine关联线插件、Export导出插件、KeyboardNavigation键盘导航插件、MiniMap小地图插件、Watermark水印插件、TouchEvent移动端触摸事件支持插件、NodeImgAdjust拖拽调整节点图片大小插件、Search搜索插件、Painter节点格式刷插件、Scrollbar滚动条插件、Formula数学公式插件、Cooperate协同编辑插件、RainbowLines彩虹线条插件、Demonstrate演示模式插件、OuterFrame外框插件、HandDrawnLikeStyle手绘风格插件[收费]、Notation节点标记插件[收费]、Numbers节点编号插件[收费]、FreemindFreemind格式导入导出插件[收费]、ExcelExcel格式导入导出插件[收费]、Checkbox待办插件[收费]
> RichText节点富文本插件、Select鼠标多选节点插件、Drag节点拖拽插件、AssociativeLine关联线插件、Export导出插件、KeyboardNavigation键盘导航插件、MiniMap小地图插件、Watermark水印插件、TouchEvent移动端触摸事件支持插件、NodeImgAdjust拖拽调整节点图片大小插件、Search搜索插件、Painter节点格式刷插件、Scrollbar滚动条插件、Formula数学公式插件、Cooperate协同编辑插件、RainbowLines彩虹线条插件、Demonstrate演示模式插件、OuterFrame外框插件MindMapLayoutPro思维导图布局插件HandDrawnLikeStyle手绘风格插件[收费]、Notation节点标记插件[收费]、Numbers节点编号插件[收费]、FreemindFreemind格式导入导出插件[收费]、ExcelExcel格式导入导出插件[收费]、Checkbox待办插件[收费]、Lineflow节点连线流动插件[收费]
本项目不会实现的特性:
@@ -93,17 +93,15 @@ const mindMap = new MindMap({
});
```
即可得到一个思维导图。
想要实现更多功能?可以查看[开发文档](https://wanglin2.github.io/mind-map-docs/)。
即可得到一个思维导图。想要实现更多功能?可以查看[开发文档](https://wanglin2.github.io/mind-map-docs/)。
# License
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用如不想保留可联系作者。
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用如不想保留可联系作者。
# 微信交流群
微信添加`wanglinguanfang`拉你入群。根据过往的经验大部分问题都可以通过查看issue列表或文档解决所以提问前请确保你已经阅读完了所有文档文档里没有的可在群里提问不必私聊作者。
微信添加`wanglinguanfang`拉你入群。根据过往的经验大部分问题都可以通过查看issue列表或文档解决所以提问前请确保你已经阅读完了所有文档文档里没有的可在群里提问不必私聊作者如果你一定要私聊请先发红包¥9.9+每次)
# star
@@ -117,19 +115,119 @@ const mindMap = new MindMap({
# 请作者喝杯咖啡
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~你的赞助对项目的可持续发展非常重要,是作者持续维护的最大动力。
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
>
> 也可以通过购买付费插件来支持我们:[付费插件](https://wanglin2.github.io/mind-map-docs/plugins/about.html)。
>
> 为什么需要你的赞助simple-mind-map 的目标是成为开源中最好的思维导图为开发者提供一个快速实现思维导图产品的js库为用户提供一个免费好用的思维导图软件为了这个目标作者已经持续开发维护了3年多耗费了非常多的精力随着时间的推移simple-mind-map 已经取得了一定的成绩,相比最初,无论是功能,还是体验都已经有了翻天覆地的改变,但是收益方面却可以忽略不计,因为 simple-mind-map 是采用 MIT 许可的开源项目,永久免费,保留版权下可随意商用,这也意味着很难直接通过项目获取收益,为爱发电的激情总会慢慢消退,所以你的赞助对项目的可持续发展非常重要,是作者持续维护的最大动力。
> 赞助等级最强王者¥500+、星耀赞助¥300+、钻石赞助¥150+、黄金赞助¥50+)、青铜赞助
<p>
<img src="./web/src/assets/img/alipay.jpg" style="width: 300px" />
<img src="./web/src/assets/img/wechat.jpg" style="width: 300px" />
</p>
## 钻石赞助
<p>
<span>
<img src="./web/src/assets/avatar/黄智彪@一米一栗科技.png" style="width: 50px;height: 50px;" />
<span>黄智彪@一米一栗科技</span>
</span>
</p>
## 黄金赞助
<p>
<span>
<img src="./web/src/assets/avatar/小土渣的宇宙.jpeg" style="width: 50px;height: 50px;" />
<span>小土渣的宇宙</span>
</span>
<span>
<img src="./web/src/assets/avatar/Chris.jpg" style="width: 50px;height: 50px;" />
<span>Chris</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>LiuJL</span>
</span>
<span>
<img src="./web/src/assets/avatar/Kyle.jpg" style="width: 50px;height: 50px;" />
<span>Kyle</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/ccccs.jpg" style="width: 50px;height: 50px;" />
<span>ccccs</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>
<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>Matt</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>
## 青铜赞助
<p>
<span>
<img src="./web/src/assets/avatar/Think.jpg" style="width: 50px;height: 50px;" />
@@ -139,10 +237,6 @@ 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/小土渣的宇宙.jpeg" style="width: 50px;height: 50px;" />
<span>小土渣的宇宙</span>
</span>
<span>
<img src="./web/src/assets/avatar/qp.jpg" style="width: 50px;height: 50px;" />
<span>qp</span>
@@ -159,22 +253,10 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/suka.jpg" style="width: 50px;height: 50px;" />
<span>suka</span>
</span>
<span>
<img src="./web/src/assets/avatar/Chris.jpg" style="width: 50px;height: 50px;" />
<span>Chris</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>
@@ -191,10 +273,6 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>Luke</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>
@@ -211,10 +289,6 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/敏.jpg" style="width: 50px;height: 50px;" />
<span>敏</span>
</span>
<span>
<img src="./web/src/assets/avatar/沐风牧草.jpg" style="width: 50px;height: 50px;" />
<span>沐风牧草</span>
</span>
<span>
<img src="./web/src/assets/avatar/有希.jpg" style="width: 50px;height: 50px;" />
<span>有希</span>
@@ -223,10 +297,6 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/樊笼.jpg" style="width: 50px;height: 50px;" />
<span>樊笼</span>
</span>
<span>
<img src="./web/src/assets/avatar/达仁科技.jpg" style="width: 50px;height: 50px;" />
<span>达仁科技</span>
</span>
<span>
<img src="./web/src/assets/avatar/小逗比.png" style="width: 50px;height: 50px;" />
<span>小逗比</span>
@@ -303,10 +373,6 @@ const mindMap = new MindMap({
<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>
@@ -319,10 +385,6 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/皇登攀.jpg" style="width: 50px;height: 50px;" />
<span>皇登攀</span>
</span>
<span>
<img src="./web/src/assets/avatar/风格.jpg" style="width: 50px;height: 50px;" />
<span>风格</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>SR</span>
@@ -331,10 +393,6 @@ 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>LiuJL</span>
</span>
<span>
<img src="./web/src/assets/avatar/L.jpg" style="width: 50px;height: 50px;" />
<span>L</span>
@@ -359,10 +417,6 @@ 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/Alex.jpg" style="width: 50px;height: 50px;" />
<span>Alex</span>
@@ -379,18 +433,10 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/最多5个字.jpg" style="width: 50px;height: 50px;" />
<span>最多5个字</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/ZX.jpg" style="width: 50px;height: 50px;" />
<span>ZX</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>
@@ -403,18 +449,10 @@ 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/Kyle.jpg" style="width: 50px;height: 50px;" />
<span>Kyle</span>
</span>
<span>
<img src="./web/src/assets/avatar/lsytyrt.jpg" style="width: 50px;height: 50px;" />
<span>lsytyrt</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/buddy.jpg" style="width: 50px;height: 50px;" />
<span>buddy</span>
@@ -435,14 +473,6 @@ 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/ccccs.jpg" style="width: 50px;height: 50px;" />
<span>ccccs</span>
</span>
<span>
<img src="./web/src/assets/avatar/。.png" style="width: 50px;height: 50px;" />
<span>。</span>
@@ -455,10 +485,6 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/张文建.jpg" style="width: 50px;height: 50px;" />
<span>张文建</span>
</span>
<span>
<img src="./web/src/assets/avatar/炫.jpg" style="width: 50px;height: 50px;" />
<span>炫</span>
</span>
<span>
<img src="./web/src/assets/avatar/Lawliet.jpg" style="width: 50px;height: 50px;" />
<span>Lawliet</span>
@@ -467,10 +493,6 @@ 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/default.png" style="width: 50px;height: 50px;" />
<span>Eric</span>
@@ -483,10 +505,6 @@ 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/海云.jpg" style="width: 50px;height: 50px;" />
<span>海云</span>
@@ -499,4 +517,32 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/h.r.w.jpg" style="width: 50px;height: 50px;" />
<span>h.r.w</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/xbkkjbs0246658.png" style="width: 50px;height: 50px;" />
<span>xbkkjbs0246658</span>
</span>
<span>
<img src="./web/src/assets/avatar/4399行星元帅.jpg" style="width: 50px;height: 50px;" />
<span>4399行星元帅</span>
</span>
<span>
<img src="./web/src/assets/avatar/Xavier.png" style="width: 50px;height: 50px;" />
<span>Xavier</span>
</span>
<span>
<img src="./web/src/assets/avatar/冒号括号.png" style="width: 50px;height: 50px;" />
<span>:)</span>
</span>
</p>

File diff suppressed because one or more lines are too long

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

69
dist/js/chunk-9a7bee60.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@
})
} catch (error) {
console.log(error)
}</script><link href="dist/css/chunk-vendors.css?fc8e52cca177f49cac0d" rel="stylesheet"><link href="dist/css/app.css?fc8e52cca177f49cac0d" 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 = () => {
}</script><link href="dist/css/chunk-vendors.css?f5839763ea0d5c47d80a" rel="stylesheet"><link href="dist/css/app.css?f5839763ea0d5c47d80a" 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({
@@ -74,4 +74,4 @@
// 可以通过window.$bus.$on()来监听应用的一些事件
// 实例化页面
window.initApp()
}</script><script src="dist/js/chunk-vendors.js?fc8e52cca177f49cac0d"></script><script src="dist/js/app.js?fc8e52cca177f49cac0d"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?f5839763ea0d5c47d80a"></script><script src="dist/js/app.js?f5839763ea0d5c47d80a"></script></body></html>

View File

@@ -0,0 +1,21 @@
const { exec } = require('child_process')
const fs = require('fs')
const base = './src/plugins/'
const list = fs.readdirSync(base)
const files = []
list.forEach(item => {
const stat = fs.statSync(base + item)
if (stat.isFile()) {
files.push(item)
}
})
const str = files
.map(item => {
return base + item
})
.join(' ')
exec(
`tsc ${str} --declaration --allowJs --emitDeclarationOnly --outDir types/src/ --target es2017 --skipLibCheck `
)

View File

@@ -18,6 +18,7 @@ import Formula from './src/plugins/Formula.js'
import RainbowLines from './src/plugins/RainbowLines.js'
import Demonstrate from './src/plugins/Demonstrate.js'
import OuterFrame from './src/plugins/OuterFrame.js'
import MindMapLayoutPro from './src/plugins/MindMapLayoutPro.js'
import xmind from './src/parse/xmind.js'
import markdown from './src/parse/markdown.js'
import icons from './src/svg/icons.js'
@@ -29,7 +30,7 @@ MindMap.markdown = markdown
MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.12.1'
MindMap.version = '0.12.2'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)
@@ -50,5 +51,6 @@ MindMap.usePlugin(MiniMap)
.usePlugin(RainbowLines)
.usePlugin(Demonstrate)
.usePlugin(OuterFrame)
.usePlugin(MindMapLayoutPro)
export default MindMap

View File

@@ -123,9 +123,11 @@ class MindMap {
// 初始渲染
this.render(this.opt.fit ? () => this.view.fit() : () => {})
setTimeout(() => {
if (this.opt.data) this.command.addHistory()
}, 0)
// 将初始数据添加到历史记录堆栈中
if (this.opt.addHistoryOnInit && this.opt.data) {
this.command.addHistory()
}
}
// 配置参数处理
@@ -336,7 +338,7 @@ class MindMap {
this.opt.themeConfig = config
if (!notRender) {
// 检查改变的是否是节点大小无关的主题属性
let res = checkIsNodeSizeIndependenceConfig(changedConfig)
const res = checkIsNodeSizeIndependenceConfig(changedConfig)
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
}
}
@@ -393,14 +395,17 @@ class MindMap {
// 更新画布数据,如果新的数据是在当前画布节点数据基础上增删改查后形成的,那么可以使用该方法来更新画布数据
updateData(data) {
this.emit('before_update_data', data)
this.renderer.setData(data)
this.render()
this.command.addHistory()
this.emit('update_data', data)
}
// 动态设置思维导图数据,纯节点数据
setData(data) {
data = this.handleData(data)
this.emit('before_set_data', data)
this.opt.data = data
this.execCommand('CLEAR_ACTIVE_NODE')
this.command.clearHistory()
@@ -489,6 +494,10 @@ class MindMap {
this.execCommand('CLEAR_ACTIVE_NODE')
}
this.opt.readonly = isReadonly
// 切换为编辑模式时,如果历史记录堆栈是空的,那么进行一次入栈操作
if (!isReadonly && this.command.history.length <= 0) {
this.command.originAddHistory()
}
this.emit('mode_change', mode)
}
@@ -574,7 +583,7 @@ class MindMap {
this.watermark.isInExport = false
}
// 添加必要的样式
;[this.joinCss(), ...cssTextList].forEach(s => {
[this.joinCss(), ...cssTextList].forEach(s => {
clone.add(SVG(`<style>${s}</style>`))
})
// 附加内容

View File

@@ -1,11 +1,11 @@
{
"name": "simple-mind-map",
"version": "0.10.6",
"version": "0.12.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.10.6",
"version": "0.12.1",
"license": "MIT",
"dependencies": {
"@svgdotjs/svg.js": "3.2.0",
@@ -15,7 +15,7 @@
"katex": "^0.16.8",
"mdast-util-from-markdown": "^1.3.0",
"pdf-lib": "^1.17.1",
"quill": "^2.0.2",
"quill": "^2.0.3",
"tern": "^0.24.3",
"uuid": "^9.0.0",
"ws": "^7.5.9",
@@ -1822,9 +1822,9 @@
]
},
"node_modules/quill": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
"dependencies": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",
@@ -3553,9 +3553,9 @@
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
"quill": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
"requires": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.12.1",
"version": "0.12.2",
"description": "一个简单的web在线思维导图",
"authors": [
{
@@ -22,7 +22,7 @@
"scripts": {
"lint": "eslint src/",
"format": "prettier --write .",
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck",
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck & node ./bin/createPluginsTypeFiles.js",
"wsServe": "node ./bin/wsServer.mjs"
},
"module": "index.js",
@@ -35,7 +35,7 @@
"katex": "^0.16.8",
"mdast-util-from-markdown": "^1.3.0",
"pdf-lib": "^1.17.1",
"quill": "^2.0.2",
"quill": "^2.0.3",
"tern": "^0.24.3",
"uuid": "^9.0.0",
"ws": "^7.5.9",

View File

@@ -75,6 +75,11 @@ export const CONSTANTS = {
TAG_POSITION: {
RIGHT: 'right',
BOTTOM: 'bottom'
},
EDIT_NODE_CLASS: {
SMM_NODE_EDIT_WRAP: 'smm-node-edit-wrap',
RICH_TEXT_EDIT_WRAP: 'ql-editor',
ASSOCIATIVE_LINE_TEXT_EDIT_WRAP: 'associative-line-text-edit-warp'
}
}
@@ -168,7 +173,8 @@ export const nodeDataNoStylePropList = [
'customLeft',
'customTop',
'customTextWidth',
'checkbox'
'checkbox',
'dir'
]
// 错误类型
@@ -201,6 +207,10 @@ export const cssContent = `
opacity: 1;
stroke-width: 2;
}
.smm-text-node-wrap {
user-select: none;
}
`
// html自闭合标签列表
@@ -215,4 +225,4 @@ export const selfCloseTagList = [
]
// 非富文本模式下的节点文本行高
export const noneRichTextNodeLineHeight = 1.2
export const noneRichTextNodeLineHeight = 1.2

View File

@@ -84,6 +84,9 @@ export const defaultOpt = {
isShowExpandNum: true,
// 是否只有当鼠标在画布内才响应快捷键事件
enableShortcutOnlyWhenMouseInSvg: true,
// 自定义判断是否响应快捷键事件优先级比enableShortcutOnlyWhenMouseInSvg选项高
// 可以传递一个函数接收事件对象e为参数需要返回true或false返回true代表允许响应快捷键事件反之不允许库默认当事件目标为body或为文本编辑框元素普通文本编辑框、富文本编辑框、关联线文本编辑框时响应快捷键其他不响应
customCheckEnableShortcut: null,
// 初始根节点的位置
initRootNodePosition: null,
// 节点文本编辑框的z-index
@@ -130,6 +133,8 @@ export const defaultOpt = {
// 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式
// 开启该特性后需要给你的输入框绑定keydown事件并禁止冒泡
enableAutoEnterTextEditWhenKeydown: false,
// 当enableAutoEnterTextEditWhenKeydown选项开启时生效当通过按键进入文本编辑时是否自动清空原有文本
autoEmptyTextWhenKeydownEnterEdit: false,
// 自定义对剪贴板文本的处理。当按ctrl+v粘贴时会读取用户剪贴板中的文本和图片默认只会判断文本是否是普通文本和simple-mind-map格式的节点数据如果你想处理其他思维导图的数据比如processon、zhixi等那么可以传递一个函数接受当前剪贴板中的文本为参数返回处理后的数据可以返回两种类型
/*
1.返回一个纯文本,那么会直接以该文本创建一个子节点
@@ -247,8 +252,9 @@ export const defaultOpt = {
emptyTextMeasureHeightText: 'abc123我和你',
// 是否在进行节点文本编辑时实时更新节点大小和节点位置,开启后当节点数量比较多时可能会造成卡顿
openRealtimeRenderOnNodeTextEdit: false,
// 默认会给容器元素el绑定mousedown事件并且会阻止其默认事件,这会带来一定问题,比如你聚焦在思维导图外的其他输入框,点击画布就不会触发其失焦,可以通过该选项关闭阻止。关闭后也会带来一定问题,比如鼠标框选节点时可能会选中节点文字,看你如何取舍
mousedownEventPreventDefault: true,
// 默认会给容器元素el绑定mousedown事件可通过该选项设置是否阻止其默认事件
// 如果设置为true会带来一定问题比如你聚焦在思维导图外的其他输入框点击画布就不会触发其失焦
mousedownEventPreventDefault: false,
// 在激活上粘贴用户剪贴板中的数据时,如果同时存在文本和图片,那么只粘贴文本,忽略图片
onlyPasteTextWhenHasImgAndText: true,
// 是否允许拖拽调整节点的宽度实际上压缩的是节点里面文本内容的宽度当节点文本内容宽度压缩到最小时无法继续压缩。如果节点存在图片那么最小值以图片宽度和文本内容最小宽度的最大值为准目前该特性仅在两种情况下可用1.开启了富文本模式即注册了RichText插件2.自定义节点内容)
@@ -257,6 +263,11 @@ export const defaultOpt = {
minNodeTextModifyWidth: 20,
// 同minNodeTextModifyWidth最大值传-1代表不限制
maxNodeTextModifyWidth: -1,
// 自定义处理节点的连线方法可以传递一个函数函数接收三个参数node节点实例、line节点的某条连线@svgjs库的path对象, { width, color, dasharray }dasharray该条连线的虚线样式为none代表实线你可以修改line对象来达到修改节点连线样式的效果比如增加流动效果
customHandleLine: null,
// 实例化完后是否立刻进行一次历史数据入栈操作
// 即调用mindMap.command.addHistory方法
addHistoryOnInit: true,
// 【Select插件】
// 多选节点时鼠标移动到边缘时的画布移动偏移量
@@ -426,9 +437,6 @@ export const defaultOpt = {
transformRichTextOnEnterEdit: null,
// 可以传递一个函数即将结束富文本编辑前会执行该函数函数接收richText实例所以你可以在此时机更新quill文档数据
beforeHideRichTextEdit: null,
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
richTextEditFakeInPlace: false,
// 【OuterFrame】插件
outerFramePaddingX: 10,

View File

@@ -61,6 +61,7 @@ class Command {
this.commands[name].forEach(fn => {
fn(...args)
})
this.mindMap.emit('afterExecCommand', name, ...args)
if (
['BACK', 'FORWARD', 'SET_NODE_ACTIVE', 'CLEAR_ACTIVE_NODE'].includes(
name

View File

@@ -1,4 +1,5 @@
import { keyMap } from './keyMap'
import { CONSTANTS } from '../../constants/constant'
// 快捷按键、命令处理类
export default class KeyCommand {
@@ -15,6 +16,18 @@ export default class KeyCommand {
this.bindEvent()
}
// 扩展按键映射
extendKeyMap(key, code) {
keyMap[key] = code
}
// 从按键映射中删除某个键
removeKeyMap(key) {
if (typeof keyMap[key] !== 'undefined') {
delete keyMap[key]
}
}
// 暂停快捷键响应
pause() {
this.isPause = true
@@ -73,10 +86,29 @@ export default class KeyCommand {
window.removeEventListener('keydown', this.onKeydown)
}
// 根据事件目标判断是否响应快捷键事件
defaultEnableCheck(e) {
const target = e.target
return (
target === document.body ||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP) ||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP) ||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.ASSOCIATIVE_LINE_TEXT_EDIT_WRAP)
)
}
// 按键事件
onKeydown(e) {
const { enableShortcutOnlyWhenMouseInSvg, beforeShortcutRun } =
this.mindMap.opt
const {
enableShortcutOnlyWhenMouseInSvg,
beforeShortcutRun,
customCheckEnableShortcut
} = this.mindMap.opt
const checkFn =
typeof customCheckEnableShortcut === 'function'
? customCheckEnableShortcut
: this.defaultEnableCheck
if (!checkFn(e)) return
if (this.isPause || (enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)) {
return
}

View File

@@ -33,7 +33,9 @@ import {
removeRichTextStyes,
formatGetNodeGeneralization,
sortNodeList,
throttle
throttle,
checkClipboardReadEnable,
isNodeNotNeedRenderData
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../theme/default'
@@ -92,12 +94,7 @@ class Render {
// 文本编辑框需要再bindEvent之前实例化否则单击事件只能触发隐藏文本编辑框而无法保存文本修改
this.textEdit = new TextEdit(this)
// 当前复制的数据
this.lastBeingCopyData = null
this.beingCopyData = null
this.beingPasteText = ''
this.beingPasteImgSize = 0
this.currentBeingPasteType = ''
this.pasteData = { text: null, img: null }
// 节点高亮框
this.highlightBoxNode = null
this.highlightBoxNodeStyle = null
@@ -115,20 +112,17 @@ class Render {
// 设置布局结构
setLayout() {
const { layout } = this.mindMap.opt
this.layout = new (
layouts[this.mindMap.opt.layout]
? layouts[this.mindMap.opt.layout]
layouts[layout]
? layouts[layout]
: layouts[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]
)(this, this.mindMap.opt.layout)
)(this, layout)
}
// 重新设置思维导图数据
setData(data) {
if (this.hasRichTextPlugin()) {
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
} else {
this.renderTree = data
}
this.renderTree = data || null
}
// 绑定事件
@@ -195,7 +189,7 @@ class Render {
})
// 处理非https下的复制黏贴问题
// 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡
// if (!navigator.clipboard) {
// if (!checkClipboardReadEnable()) {
// this.handlePaste = this.handlePaste.bind(this)
// window.addEventListener('paste', this.handlePaste)
// this.mindMap.on('beforeDestroy', () => {
@@ -212,8 +206,7 @@ class Render {
node.height = height
node.layout()
this.mindMap.render(() => {
// 输入框的left不会改变所以无需更新
this.textEdit.updateTextEditNode(['left'])
this.textEdit.updateTextEditNode()
})
}
@@ -434,7 +427,7 @@ class Render {
})
// 粘贴节点
this.mindMap.keyCommand.addShortcut('Control+v', () => {
if (navigator.clipboard) this.paste()
this.paste()
})
// 根节点居中显示
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
@@ -991,11 +984,12 @@ class Render {
}
// 上移节点,多个节点只会操作第一个节点
upNode() {
if (this.activeNodeList.length <= 0) {
upNode(appointNode) {
if (this.activeNodeList.length <= 0 && !appointNode) {
return
}
let node = this.activeNodeList[0]
const list = appointNode ? [appointNode] : this.activeNodeList
const node = list[0]
if (node.isRoot) {
return
}
@@ -1016,11 +1010,12 @@ class Render {
}
// 下移节点,多个节点只会操作第一个节点
downNode() {
if (this.activeNodeList.length <= 0) {
downNode(appointNode) {
if (this.activeNodeList.length <= 0 && !appointNode) {
return
}
let node = this.activeNodeList[0]
const list = appointNode ? [appointNode] : this.activeNodeList
const node = list[0]
if (node.isRoot) {
return
}
@@ -1154,8 +1149,6 @@ class Render {
text = clipboardData.getData('text')
}
})
this.pasteData.img = img
this.pasteData.text = text
this.paste()
}
@@ -1168,134 +1161,121 @@ class Render {
disabledClipboard,
onlyPasteTextWhenHasImgAndText
} = this.mindMap.opt
// 读取剪贴板的文字和图片
let text = ''
let img = null
if (!disabledClipboard) {
// 如果支持剪贴板操作,那么以剪贴板数据为准
if (!disabledClipboard && checkClipboardReadEnable()) {
try {
const res = navigator.clipboard
? await getDataFromClipboard()
: this.pasteData
text = res.text || ''
img = res.img || null
const res = await getDataFromClipboard()
let text = res.text || ''
let img = res.img || null
// 存在文本,则创建子节点
if (text) {
// 判断粘贴的是否是simple-mind-map的数据
let smmData = null
let useDefault = true
// 用户自定义处理
if (this.mindMap.opt.customHandleClipboardText) {
try {
const res = await this.mindMap.opt.customHandleClipboardText(text)
if (!isUndef(res)) {
useDefault = false
const checkRes = checkSmmFormatData(res)
if (checkRes.isSmm) {
smmData = checkRes.data
} else {
text = checkRes.data
}
}
} catch (error) {
errorHandler(
ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR,
error
)
}
}
// 默认处理
if (useDefault) {
const checkRes = checkSmmFormatData(text)
if (checkRes.isSmm) {
smmData = checkRes.data
} else {
text = checkRes.data
}
}
if (smmData) {
this.mindMap.execCommand(
'INSERT_MULTI_CHILD_NODE',
[],
Array.isArray(smmData) ? smmData : [smmData]
)
} else {
text = htmlEscape(text)
const textArr = text
.split(new RegExp('\r?\n|(?<!\n)\r', 'g'))
.filter(item => {
return !!item
})
// 判断是否需要根据换行自动分割节点
if (textArr.length > 1 && handleIsSplitByWrapOnPasteCreateNewNode) {
handleIsSplitByWrapOnPasteCreateNewNode()
.then(() => {
this.mindMap.execCommand(
'INSERT_MULTI_CHILD_NODE',
[],
textArr.map(item => {
return {
data: {
text: item
},
children: []
}
})
)
})
.catch(() => {
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
text
})
})
} else {
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
text
})
}
}
}
// 存在图片,则添加到当前激活节点
if (img && (!text || !onlyPasteTextWhenHasImgAndText)) {
try {
let imgData = null
// 自定义图片处理函数
if (
handleNodePasteImg &&
typeof handleNodePasteImg === 'function'
) {
imgData = await handleNodePasteImg(img)
} else {
imgData = await loadImage(img)
}
if (this.activeNodeList.length > 0) {
this.activeNodeList.forEach(node => {
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
url: imgData.url,
title: '',
width: imgData.size.width,
height: imgData.size.height
})
})
}
} catch (error) {
errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error)
}
}
} catch (error) {
errorHandler(ERROR_TYPES.READ_CLIPBOARD_ERROR, error)
}
}
// 检查剪切板数据是否有变化
// 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法
const imgSize = img ? img.size : 0
if (this.beingPasteText !== text || this.beingPasteImgSize !== imgSize) {
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CLIP_BOARD
this.beingPasteText = text
this.beingPasteImgSize = imgSize
}
// 检查要粘贴的节点数据是否有变化,节点优先级高于剪切板
if (this.lastBeingCopyData !== this.beingCopyData) {
this.lastBeingCopyData = this.beingCopyData
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CANVAS
}
// 粘贴剪切板的数据
if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) {
// 存在文本,则创建子节点
if (text) {
// 判断粘贴的是否是simple-mind-map的数据
let smmData = null
let useDefault = true
// 用户自定义处理
if (this.mindMap.opt.customHandleClipboardText) {
try {
const res = await this.mindMap.opt.customHandleClipboardText(text)
if (!isUndef(res)) {
useDefault = false
const checkRes = checkSmmFormatData(res)
if (checkRes.isSmm) {
smmData = checkRes.data
} else {
text = checkRes.data
}
}
} catch (error) {
errorHandler(ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR, error)
}
}
// 默认处理
if (useDefault) {
const checkRes = checkSmmFormatData(text)
if (checkRes.isSmm) {
smmData = checkRes.data
} else {
text = checkRes.data
}
}
if (smmData) {
this.mindMap.execCommand(
'INSERT_MULTI_CHILD_NODE',
[],
Array.isArray(smmData) ? smmData : [smmData]
)
} else {
text = htmlEscape(text)
const textArr = text
.split(new RegExp('\r?\n|(?<!\n)\r', 'g'))
.filter(item => {
return !!item
})
// 判断是否需要根据换行自动分割节点
if (textArr.length > 1 && handleIsSplitByWrapOnPasteCreateNewNode) {
handleIsSplitByWrapOnPasteCreateNewNode()
.then(() => {
this.mindMap.execCommand(
'INSERT_MULTI_CHILD_NODE',
[],
textArr.map(item => {
return {
data: {
text: item
},
children: []
}
})
)
})
.catch(() => {
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
text
})
})
} else {
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
text
})
}
}
}
// 存在图片,则添加到当前激活节点
if (img && (!text || !onlyPasteTextWhenHasImgAndText)) {
try {
let imgData = null
// 自定义图片处理函数
if (handleNodePasteImg && typeof handleNodePasteImg === 'function') {
imgData = await handleNodePasteImg(img)
} else {
imgData = await loadImage(img)
}
if (this.activeNodeList.length > 0) {
this.activeNodeList.forEach(node => {
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
url: imgData.url,
title: '',
width: imgData.size.width,
height: imgData.size.height
})
})
}
} catch (error) {
errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error)
}
}
} else {
// 粘贴节点数据
// 禁用剪贴板或不支持剪贴板时
// 粘贴画布内的节点数据
if (this.beingCopyData) {
this.mindMap.execCommand('PASTE_NODE', this.beingCopyData)
}
@@ -1570,6 +1550,8 @@ class Render {
return
}
this.activeNodeList.forEach(node => {
// 概要节点不允许添加下级节点
if (node.isGeneralization) return
node.setData({
expand: true
})
@@ -1597,28 +1579,32 @@ class Render {
// 设置节点样式
setNodeStyle(node, prop, value) {
let data = {
const data = {
[prop]: value
}
// 如果开启了富文本,则需要应用到富文本上
if (this.hasRichTextPlugin()) {
this.mindMap.richText.setNotActiveNodeStyle(node, {
[prop]: value
})
if (
this.hasRichTextPlugin() &&
this.mindMap.richText.isHasRichTextStyle(data)
) {
data.resetRichText = true
}
this.setNodeDataRender(node, data)
// 更新了连线的样式
if (lineStyleProps.includes(prop)) {
;(node.parent || node).renderLine(true)
(node.parent || node).renderLine(true)
}
}
// 设置节点多个样式
setNodeStyles(node, style) {
let data = { ...style }
const data = { ...style }
// 如果开启了富文本,则需要应用到富文本上
if (this.hasRichTextPlugin()) {
this.mindMap.richText.setNotActiveNodeStyle(node, style)
if (
this.hasRichTextPlugin() &&
this.mindMap.richText.isHasRichTextStyle(data)
) {
data.resetRichText = true
}
this.setNodeDataRender(node, data)
// 更新了连线的样式
@@ -1630,7 +1616,7 @@ class Render {
}
})
if (hasLineStyleProps) {
;(node.parent || node).renderLine(true)
(node.parent || node).renderLine(true)
}
}
@@ -1981,6 +1967,10 @@ class Render {
// 设置节点数据,并判断是否渲染
setNodeDataRender(node, data, notRender = false) {
this.mindMap.execCommand('SET_NODE_DATA', node, data)
if (isNodeNotNeedRenderData(data)) {
this.mindMap.emit('node_tree_render_end')
return
}
this.reRenderNodeCheckChange(node, notRender)
}

View File

@@ -133,6 +133,7 @@ export default class TextEdit {
// 按键事件
onKeydown(e) {
if (e.target !== document.body) return
const activeNodeList = this.mindMap.renderer.activeNodeList
if (activeNodeList.length <= 0 || activeNodeList.length > 1) return
const node = activeNodeList[0]
@@ -265,7 +266,8 @@ export default class TextEdit {
nodeTextEditZIndex,
textAutoWrapWidth,
selectTextOnEnterEditText,
openRealtimeRenderOnNodeTextEdit
openRealtimeRenderOnNodeTextEdit,
autoEmptyTextWhenKeydownEnterEdit
} = this.mindMap.opt
if (!isFromScale) {
this.mindMap.emit('before_show_text_edit')
@@ -273,7 +275,9 @@ export default class TextEdit {
this.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.classList.add('smm-node-edit-wrap')
this.textEditNode.classList.add(
CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP
)
this.textEditNode.style.cssText = `
position: fixed;
box-sizing: border-box;
@@ -338,7 +342,11 @@ export default class TextEdit {
this.textEditNode.style.background = this.getBackground(node)
}
this.textEditNode.style.zIndex = nodeTextEditZIndex
this.textEditNode.innerHTML = textLines.join('<br>')
if (isFromKeyDown && autoEmptyTextWhenKeydownEnterEdit) {
this.textEditNode.innerHTML = ''
} else {
this.textEditNode.innerHTML = textLines.join('<br>')
}
this.textEditNode.style.minWidth =
rect.width + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight = rect.height + 'px'
@@ -368,8 +376,7 @@ export default class TextEdit {
}
// 更新文本编辑框的大小和位置
// notChangeProps不会发生改变的属性列表
updateTextEditNode(notChangeProps = []) {
updateTextEditNode() {
if (this.mindMap.richText) {
this.mindMap.richText.updateTextEditNode()
return
@@ -382,8 +389,7 @@ export default class TextEdit {
rect.width + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight =
rect.height + this.textNodePaddingY * 2 + 'px'
if (!notChangeProps.includes('left'))
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
}

View File

@@ -192,14 +192,15 @@ class Style {
}
// 生成内联样式
createStyleText() {
createStyleText(customStyle = {}) {
const styles = {
color: this.merge('color'),
fontFamily: this.merge('fontFamily'),
fontSize: this.merge('fontSize'),
fontWeight: this.merge('fontWeight'),
fontStyle: this.merge('fontStyle'),
textDecoration: this.merge('textDecoration')
textDecoration: this.merge('textDecoration'),
...customStyle
}
return `
color: ${styles.color};
@@ -277,6 +278,10 @@ class Style {
// 连线
line(line, { width, color, dasharray } = {}, enableMarker, childNode) {
const { customHandleLine } = this.ctx.mindMap.opt
if (typeof customHandleLine === 'function') {
customHandleLine(this.ctx, line, { width, color, dasharray })
}
line.stroke({ color, dasharray, width }).fill({ color: 'none' })
// 可以显示箭头
if (enableMarker) {
@@ -354,6 +359,17 @@ class Style {
return res
}
// 获取自定义的样式
getCustomStyle() {
const customStyle = {}
Object.keys(this.ctx.getData()).forEach(item => {
if (checkIsNodeStyleDataKey(item)) {
customStyle[item] = this.ctx.getData(item)
}
})
return customStyle
}
// hover和激活节点
hoverNode(node) {
const hoverRectColor =

View File

@@ -124,6 +124,20 @@ function createIconNode() {
})
}
// 尝试给html指定标签添加内联样式
function tryAddHtmlStyle(text, style) {
const tagList = ['span', 'strong', 's', 'em', 'u']
// let _text = text
// for (let i = 0; i < tagList.length; i++) {
// text = addHtmlStyle(text, tagList[i], style)
// if (text !== _text) {
// break
// }
// }
// return text
return addHtmlStyle(text, tagList, style)
}
// 创建富文本节点
function createRichTextNode(specifyText) {
const hasCustomWidth = this.hasCustomWidth()
@@ -140,25 +154,22 @@ function createRichTextNode(specifyText) {
}
if ([CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
// 如果自定义过样式则不允许覆盖
if (!this.hasCustomStyle()) {
recoverText = true
}
// if (!this.hasCustomStyle() ) {
recoverText = true
// }
}
if (recoverText && !isUndef(text)) {
// 判断节点内容是否是富文本
let isRichText = checkIsRichText(text)
const isRichText = checkIsRichText(text)
// 获取自定义样式
const customStyle = this.style.getCustomStyle()
// 样式字符串
let style = this.style.createStyleText()
const style = this.style.createStyleText(customStyle)
if (isRichText) {
// 如果是富文本那么线移除内联样式
text = removeHtmlStyle(text)
// 再添加新的内联样式
let _text = text
text = addHtmlStyle(text, 'span', style)
// 给span添加样式没有成功则尝试给strong标签添加样式
if (text === _text) {
text = addHtmlStyle(text, 'strong', style)
}
text = this.tryAddHtmlStyle(text, style)
} else {
// 非富文本
text = `<p><span style="${style}">${text}</span></p>`
@@ -259,9 +270,15 @@ function createTextNode(specifyText) {
}
textArr[index] = lines.join('\n')
})
textArr = textArr.join('\n').split(/\n/gim)
textArr = textArr.join('\n').replace(/\n$/g, '').split(/\n/gim)
textArr.forEach((item, index) => {
let node = new Text().text(item)
// 避免尾部的空行不占宽度
// 同时解决该问题https://github.com/wanglin2/mind-map/issues/1037
if (item === '') {
item = ''
}
const node = new Text().text(item)
node.addClass('smm-text-node-wrap')
this.style.text(node)
node.y(
fontSize * noneRichTextNodeLineHeight * index +
@@ -453,6 +470,9 @@ function createNoteNode() {
node.on('click', e => {
this.mindMap.emit('node_note_click', this, e, node)
})
node.on('dblclick', e => {
this.mindMap.emit('node_note_dblclick', this, e, node)
})
return {
node,
width: iconSize,
@@ -536,6 +556,7 @@ export default {
createImgNode,
getImgShowSize,
createIconNode,
tryAddHtmlStyle,
createRichTextNode,
createTextNode,
createHyperlinkNode,

View File

@@ -8,7 +8,7 @@ function initDragHandle() {
// 拖拽手柄元素
this._dragHandleNodes = null
// 手柄元素的宽度
this.dragHandleWidth = 2
this.dragHandleWidth = 4
// 鼠标按下时的x坐标
this.dragHandleMousedownX = 0
// 鼠标是否处于按下状态

View File

@@ -452,8 +452,10 @@ class Base {
const end = list[len - 1]
// 如果三点在一条直线,那么不用处理
const isOneLine =
(start[0] === center[0] && center[0] === end[0]) ||
(start[1] === center[1] && center[1] === end[1])
(start[0].toFixed(0) === center[0].toFixed(0) &&
center[0].toFixed(0) === end[0].toFixed(0)) ||
(start[1].toFixed(0) === center[1].toFixed(0) &&
center[1].toFixed(0) === end[1].toFixed(0))
if (!isOneLine) {
const cStart = this.computeNewPoint(start, center, lineRadius)
const cEnd = this.computeNewPoint(end, center, lineRadius)

View File

@@ -35,7 +35,14 @@ class MindMap extends Base {
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex, index, ancestors) => {
let newNode = this.createNode(cur, parent, isRoot, layerIndex, index, ancestors)
let newNode = this.createNode(
cur,
parent,
isRoot,
layerIndex,
index,
ancestors
)
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
@@ -47,9 +54,10 @@ class MindMap extends Base {
} else {
// 节点生长方向
newNode.dir =
index % 2 === 0
newNode.getData('dir') ||
(index % 2 === 0
? CONSTANTS.LAYOUT_GROW_DIR.RIGHT
: CONSTANTS.LAYOUT_GROW_DIR.LEFT
: CONSTANTS.LAYOUT_GROW_DIR.LEFT)
}
// 根据生长方向定位到父节点的左侧或右侧
newNode.left =

View File

@@ -11,11 +11,25 @@ import {
import associativeLineControlsMethods from './associativeLine/associativeLineControls'
import associativeLineTextMethods from './associativeLine/associativeLineText'
const styleProps = [
'associativeLineWidth',
'associativeLineColor',
'associativeLineActiveWidth',
'associativeLineActiveColor',
'associativeLineDasharray',
'associativeLineTextColor',
'associativeLineTextFontSize',
'associativeLineTextLineHeight',
'associativeLineTextFontFamily'
]
// 关联线插件
class AssociativeLine {
constructor(opt = {}) {
this.mindMap = opt.mindMap
this.associativeLineDraw = this.mindMap.associativeLineDraw
// 本次不要重新渲染连线
this.isNotRenderAllLines = false
// 当前所有连接线
this.lineList = []
// 当前激活的连接线
@@ -27,9 +41,6 @@ class AssociativeLine {
this.overlapNode = null // 创建过程中的目标节点
// 是否有节点正在被拖拽
this.isNodeDragging = false
// 箭头图标
this.markerPath = null
this.marker = this.createMarker()
// 控制点
this.controlLine1 = null
this.controlLine2 = null
@@ -112,6 +123,25 @@ class AssociativeLine {
this.mindMap.off('beforeDestroy', this.onBeforeDestroy)
}
// 获取关联线的样式配置
// 优先级:关联线自定义样式、节点自定义样式、主题的节点层级样式、主题的最外层样式
getStyleConfig(node, toNode) {
let lineStyle = {}
if (toNode) {
const associativeLineStyle = node.getData('associativeLineStyle') || {}
lineStyle = associativeLineStyle[toNode.getData('uid')] || {}
}
const res = {}
styleProps.forEach(prop => {
if (typeof lineStyle[prop] !== 'undefined') {
res[prop] = lineStyle[prop]
} else {
res[prop] = node.getStyle(prop)
}
})
return res
}
// 实例销毁时清除关联线文字编辑框
onBeforeDestroy() {
this.hideEditTextBox()
@@ -140,12 +170,12 @@ class AssociativeLine {
}
// 创建箭头
createMarker() {
createMarker(callback = () => {}) {
return this.associativeLineDraw.marker(20, 20, add => {
add.ref(12, 5)
add.size(10, 10)
add.attr('orient', 'auto-start-reverse')
this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z')
callback(add.path('M0,0 L2,5 L0,10 L10,5 Z'))
})
}
@@ -172,6 +202,10 @@ class AssociativeLine {
// 渲染所有连线
renderAllLines() {
if (this.isNotRenderAllLines) {
this.isNotRenderAllLines = false
return
}
// 先移除
this.removeAllLines()
this.removeControls()
@@ -223,11 +257,14 @@ class AssociativeLine {
associativeLineWidth,
associativeLineColor,
associativeLineActiveWidth,
associativeLineActiveColor,
associativeLineDasharray
} = this.mindMap.themeConfig
} = this.getStyleConfig(node, toNode)
// 箭头
this.markerPath
let markerPath = null
const marker = this.createMarker(p => {
markerPath = p
})
markerPath
.stroke({ color: associativeLineColor })
.fill({ color: associativeLineColor })
// 路径
@@ -247,7 +284,7 @@ class AssociativeLine {
})
.fill({ color: 'none' })
path.plot(pathStr)
path.marker('end', this.marker)
path.marker('end', marker)
// 不可见的点击线
let clickPath = this.associativeLineDraw.path()
clickPath
@@ -258,6 +295,7 @@ class AssociativeLine {
let text = this.createText({
path,
clickPath,
markerPath,
node,
toNode,
startPoint,
@@ -270,6 +308,7 @@ class AssociativeLine {
this.setActiveLine({
path,
clickPath,
markerPath,
text,
node,
toNode,
@@ -284,14 +323,72 @@ class AssociativeLine {
this.showEditTextBox(text)
})
// 渲染关联线文字
this.renderText(this.getText(node, toNode), path, text)
this.renderText(this.getText(node, toNode), path, text, node, toNode)
this.lineList.push([path, clickPath, text, node, toNode])
}
// 更新当前激活连线的样式,一般在自定义了节点关联线的样式后调用
// 直接调用node.setStyle方法更新样式会直接触发关联线更新但是关联线的激活状态会丢失
// 所以可以调用node.setData方法更新数据然后再调用该方法更新样式这样关联线激活状态不会丢失
updateActiveLineStyle() {
if (!this.activeLine) return
this.isNotRenderAllLines = true
const [path, clickPath, text, node, toNode, markerPath] = this.activeLine
const {
associativeLineWidth,
associativeLineColor,
associativeLineDasharray,
associativeLineActiveWidth,
associativeLineActiveColor,
associativeLineTextColor,
associativeLineTextFontFamily,
associativeLineTextFontSize
} = this.getStyleConfig(node, toNode)
path
.stroke({
width: associativeLineWidth,
color: associativeLineColor,
dasharray: associativeLineDasharray || [6, 4]
})
.fill({ color: 'none' })
clickPath
.stroke({
width: associativeLineActiveWidth,
color: associativeLineActiveColor
})
.fill({ color: 'none' })
markerPath
.stroke({ color: associativeLineColor })
.fill({ color: associativeLineColor })
text.find('text').forEach(textNode => {
textNode
.fill({
color: associativeLineTextColor
})
.css({
'font-family': associativeLineTextFontFamily,
'font-size': associativeLineTextFontSize + 'px'
})
})
if (this.controlLine1) {
this.controlLine1.stroke({ color: associativeLineActiveColor })
}
if (this.controlLine2) {
this.controlLine2.stroke({ color: associativeLineActiveColor })
}
if (this.controlPoint1) {
this.controlPoint1.stroke({ color: associativeLineActiveColor })
}
if (this.controlPoint2) {
this.controlPoint2.stroke({ color: associativeLineActiveColor })
}
}
// 激活某根关联线
setActiveLine({
path,
clickPath,
markerPath,
text,
node,
toNode,
@@ -299,25 +396,33 @@ class AssociativeLine {
endPoint,
controlPoints
}) {
let { associativeLineActiveColor } = this.mindMap.themeConfig
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
// 如果当前存在激活节点,那么取消激活节点
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
// 否则清除当前的关联线的激活状态,如果有的话
this.clearActiveLine()
// 保存当前激活的关联线信息
this.activeLine = [path, clickPath, text, node, toNode]
this.activeLine = [path, clickPath, text, node, toNode, markerPath]
// 让不可见的点击线显示
clickPath.stroke({ color: associativeLineActiveColor })
// 如果没有输入过关联线文字,那么显示默认文字
if (!this.getText(node, toNode)) {
this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text)
this.renderText(
this.mindMap.opt.defaultAssociativeLineText,
path,
text,
node,
toNode
)
}
// 渲染控制点和连线
this.renderControls(
startPoint,
endPoint,
controlPoints[0],
controlPoints[1]
controlPoints[1],
node,
toNode
)
this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
this.front()
@@ -346,7 +451,7 @@ class AssociativeLine {
associativeLineWidth,
associativeLineColor,
associativeLineDasharray
} = this.mindMap.themeConfig
} = this.getStyleConfig(fromNode)
if (this.isCreatingLine || !fromNode) return
this.front()
this.isCreatingLine = true
@@ -360,10 +465,14 @@ class AssociativeLine {
})
.fill({ color: 'none' })
// 箭头
this.markerPath
let markerPath = null
const marker = this.createMarker(p => {
markerPath = p
})
markerPath
.stroke({ color: associativeLineColor })
.fill({ color: associativeLineColor })
this.creatingLine.marker('end', this.marker)
this.creatingLine.marker('end', marker)
}
// 取消创建关联线
@@ -529,7 +638,8 @@ class AssociativeLine {
associativeLineTargets,
associativeLinePoint,
associativeLineTargetControlOffsets,
associativeLineText
associativeLineText,
associativeLineStyle
} = node.getData()
associativeLinePoint = associativeLinePoint || []
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
@@ -542,6 +652,15 @@ class AssociativeLine {
}
})
}
// 更新关联线样式数据
let newAssociativeLineStyle = {}
if (associativeLineStyle) {
Object.keys(associativeLineStyle).forEach(item => {
if (item !== toNode.getData('uid')) {
newAssociativeLineStyle[item] = associativeLineStyle[item]
}
})
}
this.mindMap.execCommand('SET_NODE_DATA', node, {
// 目标
associativeLineTargets: associativeLineTargets.filter((_, index) => {
@@ -558,7 +677,9 @@ class AssociativeLine {
})
: [],
// 文本
associativeLineText: newAssociativeLineText
associativeLineText: newAssociativeLineText,
// 样式
associativeLineStyle: newAssociativeLineStyle
})
}
@@ -578,6 +699,7 @@ class AssociativeLine {
this.activeLine = null
this.removeControls()
this.back()
this.mindMap.emit('associative_line_deactivate')
}
}

View File

@@ -0,0 +1,117 @@
import { CONSTANTS } from '../constants/constant'
// 该插件会向节点数据的data中添加dir字段
/*
需要更新数据的情况:
1.实例化时的数据
2.调用setData和updateData方法
3.执行完命令
4.切换结构
*/
class MindMapLayoutPro {
constructor(opt) {
this.opt = opt
this.mindMap = opt.mindMap
this.init()
}
init() {
this.updateNodeTree = this.updateNodeTree.bind(this)
this.afterExecCommand = this.afterExecCommand.bind(this)
this.layoutChange = this.layoutChange.bind(this)
// 处理实例化时传入的数据
if (this.mindMap.opt.data && this.isMindMapLayout()) {
this.updateNodeTree(this.mindMap.opt.data)
}
this.mindMap.on('layout_change', this.layoutChange)
this.mindMap.on('afterExecCommand', this.afterExecCommand)
this.mindMap.on('before_update_data', this.updateNodeTree)
this.mindMap.on('before_set_data', this.updateNodeTree)
}
restore() {
this.mindMap.off('layout_change', this.layoutChange)
this.mindMap.off('afterExecCommand', this.afterExecCommand)
this.mindMap.off('before_update_data', this.updateNodeTree)
this.mindMap.off('before_set_data', this.updateNodeTree)
}
// 监听命令执行后的事件
afterExecCommand(name) {
if (!this.isMindMapLayout()) return
if (
![
'BACK',
'FORWARD',
'INSERT_NODE',
'INSERT_MULTI_NODE',
'INSERT_CHILD_NODE',
'INSERT_MULTI_CHILD_NODE',
'INSERT_PARENT_NODE',
'UP_NODE',
'DOWN_NODE',
'MOVE_UP_ONE_LEVEL',
'INSERT_AFTER',
'INSERT_BEFORE',
'MOVE_NODE_TO',
'REMOVE_NODE',
'REMOVE_CURRENT_NODE',
'PASTE_NODE',
'CUT_NODE'
].includes(name)
)
return
this.updateRenderTree()
}
// 更新布局结构
layoutChange(layout) {
if (layout === CONSTANTS.LAYOUT.MIND_MAP) {
this.updateRenderTree()
}
}
// 更新当前的渲染树
updateRenderTree() {
this.updateNodeTree(this.mindMap.renderer.renderTree)
}
// 更新节点树,修改二级节点的排列位置
updateNodeTree(tree) {
if (!this.isMindMapLayout()) return
const root = tree
const childrenLength = root.children.length
if (childrenLength <= 0) return
const center = Math.ceil(childrenLength / 2)
root.children.forEach((item, index) => {
if (index + 1 <= center) {
item.data.dir = CONSTANTS.LAYOUT_GROW_DIR.RIGHT
} else {
item.data.dir = CONSTANTS.LAYOUT_GROW_DIR.LEFT
}
})
}
// 判断当前是否是思维导图布局结构
isMindMapLayout() {
return this.mindMap.opt.layout === CONSTANTS.LAYOUT.MIND_MAP
}
// 插件被移除前做的事情
beforePluginRemove() {
this.restore()
}
// 插件被卸载前做的事情
beforePluginDestroy() {
this.restore()
}
}
MindMapLayoutPro.instanceName = 'mindMapLayoutPro'
export default MindMapLayoutPro

View File

@@ -57,6 +57,14 @@ class RichText {
this.isCompositing = false
this.textNodePaddingX = 6
this.textNodePaddingY = 4
this.supportStyleProps = [
'fontFamily',
'fontSize',
'fontWeight',
'fontStyle',
'textDecoration',
'color'
]
this.initOpt()
this.extendQuill()
this.appendCss()
@@ -73,9 +81,12 @@ class RichText {
this.onCompositionStart = this.onCompositionStart.bind(this)
this.onCompositionUpdate = this.onCompositionUpdate.bind(this)
this.onCompositionEnd = this.onCompositionEnd.bind(this)
this.handleSetData = this.handleSetData.bind(this)
window.addEventListener('compositionstart', this.onCompositionStart)
window.addEventListener('compositionupdate', this.onCompositionUpdate)
window.addEventListener('compositionend', this.onCompositionEnd)
this.mindMap.on('before_update_data', this.handleSetData)
this.mindMap.on('before_set_data', this.handleSetData)
}
// 解绑事件
@@ -83,6 +94,8 @@ class RichText {
window.removeEventListener('compositionstart', this.onCompositionStart)
window.removeEventListener('compositionupdate', this.onCompositionUpdate)
window.removeEventListener('compositionend', this.onCompositionEnd)
this.mindMap.off('before_update_data', this.handleSetData)
this.mindMap.off('before_set_data', this.handleSetData)
}
// 插入样式
@@ -92,6 +105,7 @@ class RichText {
`
.smm-richtext-node-wrap {
word-break: break-all;
user-select: none;
}
.smm-richtext-node-wrap p {
@@ -100,7 +114,7 @@ class RichText {
`
)
let cssText = `
.ql-editor {
.${CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP} {
overflow: hidden;
padding: 0;
height: auto;
@@ -182,13 +196,13 @@ class RichText {
return
}
let {
richTextEditFakeInPlace,
customInnerElsAppendTo,
nodeTextEditZIndex,
textAutoWrapWidth,
selectTextOnEnterEditText,
transformRichTextOnEnterEdit,
openRealtimeRenderOnNodeTextEdit
openRealtimeRenderOnNodeTextEdit,
autoEmptyTextWhenKeydownEnterEdit
} = this.mindMap.opt
textAutoWrapWidth = node.hasCustomWidth()
? node.customTextWidth
@@ -205,28 +219,23 @@ class RichText {
let originWidth = g.attr('data-width')
let originHeight = g.attr('data-height')
// 缩放值
let scaleX = rect.width / originWidth
let scaleY = rect.height / originHeight
const scaleX = Math.ceil(rect.width) / originWidth
const scaleY = Math.ceil(rect.height) / originHeight
// 内边距
let paddingX = this.textNodePaddingX
let paddingY = this.textNodePaddingY
if (richTextEditFakeInPlace) {
let paddingValue = node.getPaddingVale()
paddingX = paddingValue.paddingX
paddingY = paddingValue.paddingY
}
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.classList.add('smm-richtext-node-edit-wrap')
this.textEditNode.style.cssText = `
position:fixed;
box-sizing: border-box;
position:fixed;
box-sizing: border-box;
${
openRealtimeRenderOnNodeTextEdit
? ''
: 'box-shadow: 0 0 20px rgba(0,0,0,.5);'
}
outline: none;
outline: none;
word-break: break-all;
padding: ${paddingY}px ${paddingX}px;
`
@@ -259,13 +268,6 @@ class RichText {
this.textEditNode.style.maxWidth = textAutoWrapWidth + paddingX * 2 + 'px'
this.textEditNode.style.transform = `scale(${scaleX}, ${scaleY})`
this.textEditNode.style.transformOrigin = 'left top'
if (richTextEditFakeInPlace) {
this.textEditNode.style.borderRadius =
(node.style.merge('borderRadius') || 5) + 'px'
if (node.style.merge('shape') == 'roundedRectangle') {
this.textEditNode.style.borderRadius = (node.height || 50) + 'px'
}
}
// 节点文本内容
let nodeText = node.getData('text')
if (typeof transformRichTextOnEnterEdit === 'function') {
@@ -279,7 +281,10 @@ class RichText {
if (isEmptyText) {
this.lostStyle = true
}
if (noneEmptyNoneRichText) {
if (isFromKeyDown && autoEmptyTextWhenKeydownEnterEdit) {
this.textEditNode.innerHTML = ''
this.lostStyle = true
} else if (noneEmptyNoneRichText) {
// 还不是富文本
let text = String(nodeText).split(/\n/gim).join('<br>')
let html = `<p>${text}</p>`
@@ -289,7 +294,9 @@ class RichText {
this.textEditNode.innerHTML = this.cacheEditingText || nodeText
}
this.initQuillEditor()
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
document.querySelector(
'.' + CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP
).style.minHeight = originHeight + 'px'
this.showTextEdit = true
// 如果是刚创建的节点那么默认全选否则普通激活不全选除非selectTextOnEnterEditText配置为true
// 在selectTextOnEnterEditText时如果是在keydown事件进入的节点编辑也不需要全选
@@ -355,13 +362,16 @@ class RichText {
// 获取当前正在编辑的内容
getEditText() {
let html = this.quill.container.firstChild.innerHTML
// https://github.com/slab/quill/issues/4509
return this.quill.container.firstChild.innerHTML.replaceAll(/ +/g, match =>
'&nbsp;'.repeat(match.length)
)
// 去除ql-cursor节点
// https://github.com/wanglin2/mind-map/commit/138cc4b3e824671143f0bf70e5c46796f48520d0
// https://github.com/wanglin2/mind-map/commit/0760500cebe8ec4e8ad84ab63f877b8b2a193aa1
// html = removeHtmlNodeByClass(html, '.ql-cursor')
// 去除最后的空行
return html.replace(/<p><br><\/p>$/, '')
// return html.replace(/<p><br><\/p>$/, '')
}
// 给html字符串中的节点样式按样式名首字母排序
@@ -463,6 +473,17 @@ class RichText {
}
}
},
formats: [
'bold',
'italic',
'underline',
'strike',
'color',
'background',
'font',
'size',
'formula'
], // 明确指定允许的格式,不包含有序列表,无序列表等
theme: 'snow'
})
// 拦截复制事件即Ctrl + c去除多余的空行
@@ -655,14 +676,7 @@ class RichText {
// 再将样式恢复为当前主题改节点的默认样式
const style = {}
if (this.node) {
;[
'fontFamily',
'fontSize',
'fontWeight',
'fontStyle',
'textDecoration',
'color'
].forEach(key => {
this.supportStyleProps.forEach(key => {
style[key] = this.node.style.merge(key)
})
}
@@ -693,14 +707,7 @@ class RichText {
if (!this.node) return
if (clear) {
// 清除文本样式
;[
'fontFamily',
'fontSize',
'fontWeight',
'fontStyle',
'textDecoration',
'color'
].forEach(prop => {
this.supportStyleProps.forEach(prop => {
delete this.node.nodeData.data[prop]
})
} else {
@@ -775,6 +782,18 @@ class RichText {
return data
}
// 判断一个对象是否包含了富文本支持的样式字段
isHasRichTextStyle(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (this.supportStyleProps.includes(key)) {
return true
}
}
return false
}
// 给未激活的节点设置富文本样式
setNotActiveNodeStyle(node, style) {
const config = this.normalStyleToRichTextStyle(style)
@@ -787,17 +806,9 @@ class RichText {
// 检查指定节点是否存在自定义的富文本样式
checkNodeHasCustomRichTextStyle(node) {
const list = [
'fontFamily',
'fontSize',
'fontWeight',
'fontStyle',
'textDecoration',
'color'
]
const nodeData = node instanceof MindMapNode ? node.getData() : node
for (let i = 0; i < list.length; i++) {
if (nodeData[list[i]] !== undefined) {
for (let i = 0; i < this.supportStyleProps.length; i++) {
if (nodeData[this.supportStyleProps[i]] !== undefined) {
return true
}
}

View File

@@ -257,6 +257,7 @@ class Search {
replaceText = String(replaceText)
// 如果当前搜索文本是替换文本的子串,那么该节点还是符合搜索结果的
const keep = replaceText.includes(this.searchText)
this.notResetSearchText = true
const hasRichTextPlugin = this.mindMap.renderer.hasRichTextPlugin()
this.matchNodeList.forEach(node => {
const text = this.getReplacedText(node, this.searchText, replaceText)

View File

@@ -6,8 +6,8 @@ import {
} from './associativeLineUtils'
// 创建控制点、连线节点
function createControlNodes() {
let { associativeLineActiveColor } = this.mindMap.themeConfig
function createControlNodes(node, toNode) {
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
// 连线
this.controlLine1 = this.associativeLineDraw
.line()
@@ -16,13 +16,13 @@ function createControlNodes() {
.line()
.stroke({ color: associativeLineActiveColor, width: 2 })
// 控制点
this.controlPoint1 = this.createOneControlNode('controlPoint1')
this.controlPoint2 = this.createOneControlNode('controlPoint2')
this.controlPoint1 = this.createOneControlNode('controlPoint1', node, toNode)
this.controlPoint2 = this.createOneControlNode('controlPoint2', node, toNode)
}
// 创建控制点
function createOneControlNode(pointKey) {
let { associativeLineActiveColor } = this.mindMap.themeConfig
function createOneControlNode(pointKey, node, toNode) {
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
return this.associativeLineDraw
.circle(this.controlPointDiameter)
.stroke({ color: associativeLineActiveColor })
@@ -202,6 +202,7 @@ function onControlPointMouseup(e) {
associativeLineTargetControlOffsets: offsetList,
associativeLinePoint
})
this.isNotRenderAllLines = true
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚所以重置isControlPointMousedown需要等draw_click事件触发完以后
setTimeout(() => {
this.resetControlPoint()
@@ -221,10 +222,10 @@ function resetControlPoint() {
}
// 渲染控制点
function renderControls(startPoint, endPoint, point1, point2) {
function renderControls(startPoint, endPoint, point1, point2, node, toNode) {
if (!this.mindMap.opt.enableAdjustAssociativeLinePoints) return
if (!this.controlLine1) {
this.createControlNodes()
this.createControlNodes(node, toNode)
}
let radius = this.controlPointDiameter / 2
// 控制点和起终点的连线

View File

@@ -43,6 +43,7 @@ function showEditTextBox(g) {
// 输入框元素没有创建过,则先创建
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.className = 'associative-line-text-edit-warp'
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
this.textEditNode.setAttribute('contenteditable', true)
this.textEditNode.addEventListener('keyup', e => {
@@ -54,14 +55,14 @@ function showEditTextBox(g) {
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
targetNode.appendChild(this.textEditNode)
}
let [, , , node, toNode] = this.activeLine
let {
associativeLineTextFontSize,
associativeLineTextFontFamily,
associativeLineTextLineHeight
} = this.mindMap.themeConfig
} = this.getStyleConfig(node, toNode)
let { defaultAssociativeLineText, nodeTextEditZIndex } = this.mindMap.opt
let scale = this.mindMap.view.scale
let [, , , node, toNode] = this.activeLine
let text = this.getText(node, toNode)
let textLines = (text || defaultAssociativeLineText).split(/\n/gim)
this.textEditNode.style.fontFamily = associativeLineTextFontFamily
@@ -124,7 +125,7 @@ function hideEditTextBox() {
this.textEditNode.style.display = 'none'
this.textEditNode.innerHTML = ''
this.showTextEdit = false
this.renderText(str, path, text)
this.renderText(str, path, text, node, toNode)
this.mindMap.emit('hide_text_edit')
}
@@ -138,29 +139,35 @@ function getText(node, toNode) {
}
// 渲染关联线文字
function renderText(str, path, text) {
function renderText(str, path, text, node, toNode) {
if (!str) return
let { associativeLineTextFontSize, associativeLineTextLineHeight } =
this.mindMap.themeConfig
this.getStyleConfig(node, toNode)
text.clear()
let textArr = str.split(/\n/gim)
let textArr = str.replace(/\n$/g, '').split(/\n/gim)
textArr.forEach((item, index) => {
let node = new Text().text(item)
node.y(associativeLineTextFontSize * associativeLineTextLineHeight * index)
this.styleText(node)
text.add(node)
// 避免尾部的空行不占宽度,导致文本编辑框定位异常的问题
if (item === '') {
item = ''
}
let textNode = new Text().text(item)
textNode.y(
associativeLineTextFontSize * associativeLineTextLineHeight * index
)
this.styleText(textNode, node, toNode)
text.add(textNode)
})
updateTextPos(path, text)
}
// 给文本设置样式
function styleText(node) {
function styleText(textNode, node, toNode) {
let {
associativeLineTextColor,
associativeLineTextFontSize,
associativeLineTextFontFamily
} = this.mindMap.themeConfig
node
} = this.getStyleConfig(node, toNode)
textNode
.fill({
color: associativeLineTextColor
})

View File

@@ -15,6 +15,12 @@ export default {
lineColor: '#549688',
// 连线样式
lineDasharray: 'none',
// 连线是否开启流动效果仅在虚线时有效需要注册LineFlow插件
lineFlow: false,
// 流动效果一个周期的时间单位s
lineFlowDuration: 1,
// 流动方向是否是从父节点到子节点
lineFlowForward: true,
// 连线风格
lineStyle: 'straight', // 曲线curve【仅支持logicalStructure、mindMap、verticalTimeline三种结构】、直线straight、直连direct【仅支持logicalStructure、mindMap、organizationStructure、verticalTimeline四种结构】
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型设为true后都为 { 型。仅支持logicalStructure、mindMap两种结构
@@ -88,8 +94,16 @@ export default {
hoverRectColor: '',
// 点鼠标hover和激活时显示的矩形边框的圆角大小
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
// 下列样式也支持给节点设置,用于覆盖最外层的设置
// paddingX,
// paddingY,
// lineWidth,
// lineColor,
// lineDasharray,
// lineFlow,
// lineFlowDuration,
// lineFlowForward
// 关联线的所有样式
},
// 二级节点样式
second: {
@@ -115,8 +129,6 @@ export default {
lineMarkerDir: 'end',
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
},
// 三级及以下节点样式
node: {
@@ -142,8 +154,6 @@ export default {
lineMarkerDir: 'end',
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
},
// 概要节点样式
generalization: {
@@ -168,8 +178,6 @@ export default {
endDir: [1, 0],
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
}
}
@@ -197,14 +205,12 @@ const nodeSizeIndependenceList = [
'rootLineKeepSameInCurve',
'rootLineStartPositionKeepSameInCurve',
'showLineMarker',
'gradientStyle',
'lineRadius',
'startColor',
'endColor',
'startDir',
'endDir',
'hoverRectColor',
'hoverRectRadius'
'hoverRectRadius',
'lineFlow',
'lineFlowDuration',
'lineFlowForward'
]
export const checkIsNodeSizeIndependenceConfig = config => {
let keys = Object.keys(config)
@@ -220,9 +226,13 @@ export const checkIsNodeSizeIndependenceConfig = config => {
return true
}
// 连线的样式
export const lineStyleProps = [
'lineColor',
'lineDasharray',
'lineWidth',
'lineMarkerDir'
'lineMarkerDir',
'lineFlow',
'lineFlowDuration',
'lineFlowForward'
]

View File

@@ -6,6 +6,7 @@ import {
import MersenneTwister from './mersenneTwister'
import { ForeignObject } from '@svgdotjs/svg.js'
import merge from 'deepmerge'
import { lineStyleProps } from '../theme/default'
// 深度优先遍历树
export const walk = (
@@ -507,13 +508,14 @@ export const addHtmlStyle = (html, tag, style) => {
if (!addHtmlStyleEl) {
addHtmlStyleEl = document.createElement('div')
}
const tags = Array.isArray(tag) ? tag : [tag]
addHtmlStyleEl.innerHTML = html
let walk = root => {
let childNodes = root.childNodes
childNodes.forEach(node => {
if (node.nodeType === 1) {
// 元素节点
if (node.tagName.toLowerCase() === tag) {
if (tags.includes(node.tagName.toLowerCase())) {
node.style.cssText = style
} else {
walk(node)
@@ -790,6 +792,18 @@ export const checkIsNodeStyleDataKey = key => {
return false
}
// 判断一个对象是否不需要触发节点重新创建
export const isNodeNotNeedRenderData = config => {
const list = [...lineStyleProps] // 节点连线样式
const keys = Object.keys(config)
for (let i = 0; i < keys.length; i++) {
if (!list.includes(keys[i])) {
return false
}
}
return true
}
// 合并图标数组
// const data = [
// { type: 'priority', name: '优先级图标', list: [{ name: '1', icon: 'a' }, { name: 2, icon: 'b' }] },
@@ -1069,9 +1083,14 @@ export const isSameObject = (a, b) => {
}
}
// 检查navigator.clipboard对象的读取是否可用
export const checkClipboardReadEnable = () => {
return navigator.clipboard && typeof navigator.clipboard.read === 'function'
}
// 将数据设置到用户剪切板中
export const setDataToClipboard = data => {
if (navigator.clipboard) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(JSON.stringify(data))
}
}
@@ -1080,7 +1099,7 @@ export const setDataToClipboard = data => {
export const getDataFromClipboard = async () => {
let text = null
let img = null
if (navigator.clipboard) {
if (checkClipboardReadEnable()) {
const items = await navigator.clipboard.read()
if (items && items.length > 0) {
for (const clipboardItem of items) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -123,6 +123,7 @@ export default {
copyFail: 'Copy fail',
number: 'Number child nodes',
expandNodeChild: 'Expand all sub nodes',
unExpandNodeChild: 'Un expand all sub nodes',
addToDo: 'Add toDo',
removeToDo: 'Remove toDo'
},
@@ -252,7 +253,12 @@ export default {
arrowDir: 'Arrow dir',
arrowDirStart: 'Start',
arrowDirEnd: 'End',
direction: 'Direction'
direction: 'Direction',
selectNodeTip: 'Please select a node',
openLineFlow: 'Open line flow',
lineFlowDuration: 'Line flow duration',
forward: 'Forward',
reverse: 'Reverse'
},
theme: {
title: 'Theme',

View File

@@ -121,6 +121,7 @@ export default {
copyFail: '复制失败',
number: '编号其子节点',
expandNodeChild: '展开所有下级节点',
unExpandNodeChild: '收起所有下级节点',
addToDo: '添加待办',
removeToDo: '删除待办'
},
@@ -248,7 +249,12 @@ export default {
arrowDir: '箭头位置',
arrowDirStart: '头部',
arrowDirEnd: '尾部',
direction: '方向'
direction: '方向',
selectNodeTip: '请选择一个节点',
openLineFlow: '开启流动效果',
lineFlowDuration: '一个流动周期的时间',
forward: '正向',
reverse: '反向'
},
theme: {
title: '主题',

View File

@@ -122,6 +122,7 @@ export default {
copyFail: '複製失敗',
number: '將其子節點編號',
expandNodeChild: '展開所有下級節點',
unExpandNodeChild: '收起所有下級節點',
addToDo: '添加待辦',
removeToDo: '刪除待辦'
},
@@ -248,7 +249,12 @@ export default {
endColor: '結束',
arrowDir: '箭頭位置',
arrowDirStart: '頭部',
arrowDirEnd: '尾部'
arrowDirEnd: '尾部',
selectNodeTip: '請選擇壹個節點',
openLineFlow: '開啓流動效果',
lineFlowDuration: '一個流動周期的時間',
forward: '正向',
reverse: '反向'
},
theme: {
title: '主題',

View File

@@ -0,0 +1,420 @@
<template>
<Sidebar ref="sidebar" :title="'关联线样式'">
<div class="sidebarContent" :class="{ isDark: isDark }">
<div class="title noTop">{{ $t('baseStyle.associativeLine') }}</div>
<div class="row">
<div class="rowItem">
<span class="name">{{ $t('baseStyle.associativeLineColor') }}</span>
<span
class="block"
v-popover:popover4
:style="{ backgroundColor: style.associativeLineColor }"
></span>
<el-popover ref="popover4" placement="bottom" trigger="click">
<Color
:color="style.associativeLineColor"
@change="
color => {
update('associativeLineColor', color)
}
"
></Color>
</el-popover>
</div>
<div class="rowItem">
<span class="name">{{ $t('baseStyle.associativeLineWidth') }}</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.associativeLineWidth"
placeholder=""
@change="
value => {
update('associativeLineWidth', value)
}
"
>
<el-option
v-for="item in lineWidthList"
:key="item"
:label="item"
:value="item"
>
<span
v-if="item > 0"
class="borderLine"
:class="{ isDark: isDark }"
:style="{ height: item + 'px' }"
></span>
</el-option>
</el-select>
</div>
</div>
<div class="row">
<div class="rowItem">
<span class="name">{{
$t('baseStyle.associativeLineActiveColor')
}}</span>
<span
class="block"
v-popover:popover5
:style="{ backgroundColor: style.associativeLineActiveColor }"
></span>
<el-popover ref="popover5" placement="bottom" trigger="click">
<Color
:color="style.associativeLineActiveColor"
@change="
color => {
update('associativeLineActiveColor', color)
}
"
></Color>
</el-popover>
</div>
<div class="rowItem">
<span class="name">{{
$t('baseStyle.associativeLineActiveWidth')
}}</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.associativeLineActiveWidth"
placeholder=""
@change="
value => {
update('associativeLineActiveWidth', value)
}
"
>
<el-option
v-for="item in lineWidthList"
:key="item"
:label="item"
:value="item"
>
<span
v-if="item > 0"
class="borderLine"
:class="{ isDark: isDark }"
:style="{ height: item + 'px' }"
></span>
</el-option>
</el-select>
</div>
</div>
<div class="row">
<div class="rowItem">
<span class="name">{{ $t('style.style') }}</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.associativeLineDasharray"
placeholder=""
@change="
value => {
update('associativeLineDasharray', value)
}
"
>
<el-option
v-for="item in borderDasharrayList"
:key="item.value"
:label="item.name"
:value="item.value"
>
<svg width="120" height="34">
<line
x1="10"
y1="17"
x2="110"
y2="17"
stroke-width="2"
:stroke="
style.associativeLineDasharray === item.value
? '#409eff'
: isDark
? '#fff'
: '#000'
"
:stroke-dasharray="item.value"
></line>
</svg>
</el-option>
</el-select>
</div>
</div>
<!-- 关联线文字 -->
<div class="title noTop">{{ $t('baseStyle.associativeLineText') }}</div>
<div class="row">
<div class="rowItem">
<span class="name">{{ $t('baseStyle.fontFamily') }}</span>
<el-select
size="mini"
v-model="style.associativeLineTextFontFamily"
placeholder=""
@change="update('associativeLineTextFontFamily', $event)"
>
<el-option
v-for="item in fontFamilyList"
:key="item.value"
:label="item.name"
:value="item.value"
:style="{ fontFamily: item.value }"
>
</el-option>
</el-select>
</div>
</div>
<div class="row">
<div class="rowItem">
<span class="name">{{ $t('baseStyle.color') }}</span>
<span
class="block"
v-popover:popover6
:style="{ backgroundColor: style.associativeLineTextColor }"
></span>
<el-popover ref="popover6" placement="bottom" trigger="click">
<Color
:color="style.associativeLineTextColor"
@change="
color => {
update('associativeLineTextColor', color)
}
"
></Color>
</el-popover>
</div>
<div class="rowItem">
<span class="name">{{ $t('baseStyle.fontSize') }}</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.associativeLineTextFontSize"
placeholder=""
@change="update('associativeLineTextFontSize', $event)"
>
<el-option
v-for="item in fontSizeList"
:key="item"
:label="item"
:value="item"
:style="{ fontSize: item + 'px' }"
>
</el-option>
</el-select>
</div>
</div>
</div>
</Sidebar>
</template>
<script>
import Sidebar from './Sidebar'
import Color from './Color'
import {
lineWidthList,
lineStyleList,
backgroundRepeatList,
backgroundPositionList,
backgroundSizeList,
fontFamilyList,
fontSizeList,
rootLineKeepSameInCurveList,
lineStyleMap,
borderDasharrayList
} from '@/config'
import { mapState, mapMutations } from 'vuex'
import {
supportLineStyleLayoutsMap,
supportLineRadiusLayouts,
supportNodeUseLineStyleLayouts,
supportRootLineKeepSameInCurveLayouts,
rainbowLinesOptions
} from '@/config/constant'
const defaultStyle = {
associativeLineColor: '',
associativeLineWidth: 0,
associativeLineActiveWidth: 0,
associativeLineDasharray: '',
associativeLineActiveColor: '',
associativeLineTextFontSize: 0,
associativeLineTextColor: '',
associativeLineTextFontFamily: ''
}
export default {
name: 'BaseStyle',
components: {
Sidebar,
Color
},
props: {
mindMap: {
type: Object
}
},
data() {
return {
lineWidthList,
fontSizeList,
activeLineNode: null,
activeLineToNode: null,
style: {
...defaultStyle
}
}
},
computed: {
...mapState({
activeSidebar: state => state.activeSidebar,
isDark: state => state.localConfig.isDark
}),
fontFamilyList() {
return fontFamilyList[this.$i18n.locale] || fontFamilyList.zh
},
borderDasharrayList() {
return borderDasharrayList[this.$i18n.locale] || borderDasharrayList.zh
}
},
watch: {
activeSidebar(val) {
if (val === 'associativeLineStyle') {
this.$refs.sidebar.show = true
} else {
this.$refs.sidebar.show = false
}
}
},
created() {
this.mindMap.on('associative_line_click', this.onAssociativeLineClick)
this.mindMap.on(
'associative_line_deactivate',
this.associativeLineDeactivate
)
},
methods: {
...mapMutations(['setActiveSidebar']),
onAssociativeLineClick(a, b, node, toNode) {
this.activeLineNode = node
this.activeLineToNode = toNode
const styleConfig = this.mindMap.associativeLine.getStyleConfig(
node,
toNode
)
Object.keys(this.style).forEach(item => {
this.style[item] = styleConfig[item]
})
this.setActiveSidebar('associativeLineStyle')
},
associativeLineDeactivate() {
this.setActiveSidebar('')
this.activeLineNode = null
this.activeLineToNode = null
this.style = {
...defaultStyle
}
},
update(prop, value) {
this.style[prop] = value
const associativeLineStyle =
this.activeLineNode.getData('associativeLineStyle') || {}
const toNodeUid = this.activeLineToNode.getData('uid')
const lineStyle = associativeLineStyle[toNodeUid] || {}
this.activeLineNode.setData({
associativeLineStyle: {
...associativeLineStyle,
[toNodeUid]: {
...lineStyle,
...this.style
}
}
})
this.mindMap.associativeLine.updateActiveLineStyle()
}
}
}
</script>
<style lang="less" scoped>
.sidebarContent {
padding: 20px;
padding-top: 10px;
&.isDark {
.title {
color: #fff;
}
.row {
.rowItem {
.name {
color: hsla(0, 0%, 100%, 0.6);
}
}
}
}
.title {
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: rgba(26, 26, 26, 0.9);
margin-bottom: 10px;
margin-top: 20px;
&.noTop {
margin-top: 0;
}
}
.row {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.rowItem {
display: flex;
align-items: center;
margin-bottom: 5px;
.name {
font-size: 12px;
margin-right: 10px;
white-space: nowrap;
}
.block {
display: inline-block;
width: 30px;
height: 30px;
border: 1px solid #dcdfe6;
border-radius: 4px;
cursor: pointer;
}
}
}
}
.borderLine {
display: inline-block;
width: 100%;
background-color: #000;
&.isDark {
background-color: #fff;
}
}
</style>
<style lang="less">
.el-select-dropdown__item.selected {
.borderLine {
background-color: #409eff;
}
}
</style>

View File

@@ -273,6 +273,61 @@
>
</div>
</div>
<!-- 流动效果 -->
<div class="row" v-if="supportLineFlow">
<div class="rowItem">
<span class="name">{{ $t('style.openLineFlow') }}</span>
<el-checkbox
v-model="style.lineFlow"
@change="
value => {
update('lineFlow', value)
}
"
></el-checkbox>
</div>
<div class="rowItem">
<span class="name">{{ $t('style.direction') }}</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.lineFlowForward"
placeholder=""
@change="
value => {
update('lineFlowForward', value)
}
"
>
<el-option
key="1"
:label="$t('style.forward')"
:value="true"
></el-option>
<el-option
key="2"
:label="$t('style.reverse')"
:value="false"
></el-option>
</el-select>
</div>
</div>
<div class="row" v-if="supportLineFlow">
<div class="rowItem">
<span class="name">{{ $t('style.lineFlowDuration') }}</span>
<el-input-number
v-model="style.lineFlowDuration"
@change="
value => {
update('lineFlowDuration', value)
}
"
:min="0.1"
size="mini"
:step="0.5"
></el-input-number>
</div>
</div>
<!-- 彩虹线条 -->
<div class="title noTop">{{ $t('baseStyle.rainbowLines') }}</div>
<div class="row">
@@ -811,6 +866,9 @@ export default {
rootLineKeepSameInCurve: '',
rootLineStartPositionKeepSameInCurve: '',
lineRadius: 0,
lineFlow: false,
lineFlowForward: true,
lineFlowDuration: 1,
generalizationLineWidth: '',
generalizationLineColor: '',
associativeLineColor: '',
@@ -847,7 +905,8 @@ export default {
...mapState({
activeSidebar: state => state.activeSidebar,
localConfig: state => state.localConfig,
isDark: state => state.localConfig.isDark
isDark: state => state.localConfig.isDark,
supportLineFlow: state => state.supportLineFlow
}),
lineStyleList() {
return lineStyleList[this.$i18n.locale] || lineStyleList.zh
@@ -947,36 +1006,7 @@ export default {
// 初始样式
initStyle() {
;[
'backgroundColor',
'lineWidth',
'lineStyle',
'showLineMarker',
'rootLineKeepSameInCurve',
'rootLineStartPositionKeepSameInCurve',
'lineRadius',
'lineColor',
'generalizationLineWidth',
'generalizationLineColor',
'associativeLineColor',
'associativeLineWidth',
'associativeLineActiveWidth',
'associativeLineDasharray',
'associativeLineActiveColor',
'associativeLineTextFontSize',
'associativeLineTextColor',
'associativeLineTextFontFamily',
'paddingX',
'paddingY',
'imgMaxWidth',
'imgMaxHeight',
'iconSize',
'backgroundImage',
'backgroundRepeat',
'backgroundPosition',
'backgroundSize',
'nodeUseLineStyle'
].forEach(key => {
Object.keys(this.style).forEach(key => {
this.style[key] = this.mindMap.getThemeConfig(key)
if (key === 'backgroundImage' && this.style[key] === 'none') {
this.style[key] = ''

View File

@@ -56,6 +56,9 @@
<span class="name">{{ $t('contextmenu.moveDownNode') }}</span>
<span class="desc">Ctrl + </span>
</div>
<div class="item" @click="exec('UNEXPAND_ALL')">
<span class="name">{{ $t('contextmenu.unExpandNodeChild') }}</span>
</div>
<div class="item" @click="exec('EXPAND_ALL')">
<span class="name">{{ $t('contextmenu.expandNodeChild') }}</span>
</div>
@@ -473,8 +476,12 @@ export default {
this.node
)
break
case 'UNEXPAND_ALL':
const uid = this.node ? this.node.uid : ''
this.$bus.$emit('execCommand', key, !uid, uid)
break
case 'EXPAND_ALL':
this.$bus.$emit('execCommand', key, this.node.uid)
this.$bus.$emit('execCommand', key, this.node ? this.node.uid : '')
break
default:
this.$bus.$emit('execCommand', key, ...args)

View File

@@ -17,6 +17,10 @@
<OutlineSidebar :mindMap="mindMap"></OutlineSidebar>
<Style v-if="!isZenMode"></Style>
<BaseStyle :data="mindMapData" :mindMap="mindMap"></BaseStyle>
<AssociativeLineStyle
v-if="mindMap"
:mindMap="mindMap"
></AssociativeLineStyle>
<Theme v-if="mindMap" :data="mindMapData" :mindMap="mindMap"></Theme>
<Structure :mindMap="mindMap"></Structure>
<ShortcutKey></ShortcutKey>
@@ -72,17 +76,19 @@ import Formula from 'simple-mind-map/src/plugins/Formula.js'
import RainbowLines from 'simple-mind-map/src/plugins/RainbowLines.js'
import Demonstrate from 'simple-mind-map/src/plugins/Demonstrate.js'
import OuterFrame from 'simple-mind-map/src/plugins/OuterFrame.js'
import MindMapLayoutPro from 'simple-mind-map/src/plugins/MindMapLayoutPro.js'
import Themes from 'simple-mind-map-plugin-themes'
// 协同编辑插件
// import Cooperate from 'simple-mind-map/src/plugins/Cooperate.js'
// 以下插件为付费插件详情请查看开发文档。依次为手绘风格插件、标记插件、编号插件、Freemind软件格式导入导出插件、Excel软件格式导入导出插件、待办插件
// 以下插件为付费插件详情请查看开发文档。依次为手绘风格插件、标记插件、编号插件、Freemind软件格式导入导出插件、Excel软件格式导入导出插件、待办插件、节点连线流动效果插件
// import HandDrawnLikeStyle from 'simple-mind-map-plugin-handdrawnlikestyle'
// import Notation from 'simple-mind-map-plugin-notation'
// import Numbers from 'simple-mind-map-plugin-numbers'
// import Freemind from 'simple-mind-map-plugin-freemind'
// import Excel from 'simple-mind-map-plugin-excel'
// import Checkbox from 'simple-mind-map-plugin-checkbox'
// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map-plugin-checkbox simple-mind-map simple-mind-map-plugin-themes
// import LineFlow from 'simple-mind-map-plugin-lineflow'
// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map-plugin-checkbox simple-mind-map simple-mind-map-plugin-themes simple-mind-map-plugin-lineflow
import OutlineSidebar from './OutlineSidebar'
import Style from './Style'
import BaseStyle from './BaseStyle'
@@ -120,6 +126,7 @@ import NodeAttachment from './NodeAttachment.vue'
import NodeOuterFrame from './NodeOuterFrame.vue'
import NodeTagStyle from './NodeTagStyle.vue'
import Setting from './Setting.vue'
import AssociativeLineStyle from './AssociativeLineStyle.vue'
// 注册插件
MindMap.usePlugin(MiniMap)
@@ -139,16 +146,12 @@ MindMap.usePlugin(MiniMap)
.usePlugin(RainbowLines)
.usePlugin(Demonstrate)
.usePlugin(OuterFrame)
.usePlugin(MindMapLayoutPro)
// .usePlugin(Cooperate) // 协同插件
// 注册主题
Themes.init(MindMap)
/**
* @Author: 王林
* @Date: 2021-06-24 22:56:17
* @Desc: 编辑区域
*/
export default {
name: 'Edit',
components: {
@@ -176,7 +179,8 @@ export default {
NodeAttachment,
NodeOuterFrame,
NodeTagStyle,
Setting
Setting,
AssociativeLineStyle
},
data() {
return {
@@ -290,21 +294,13 @@ export default {
}
},
/**
* @Author: 王林
* @Date: 2021-07-03 22:11:37
* @Desc: 获取思维导图数据,实际应该调接口获取
*/
// 获取思维导图数据,实际应该调接口获取
getData() {
let storeData = getData()
this.mindMapData = storeData
},
/**
* @Author: 王林
* @Date: 2021-08-01 10:19:07
* @Desc: 存储数据当数据有变时
*/
// 存储数据当数据有变时
bindSaveEvent() {
this.$bus.$on('data_change', data => {
storeData(data)
@@ -319,21 +315,13 @@ export default {
})
},
/**
* @Author: 王林
* @Date: 2021-08-02 23:19:52
* @Desc: 手动保存
*/
// 手动保存
manualSave() {
let data = this.mindMap.getData(true)
storeConfig(data)
},
/**
* @Author: 王林
* @Date: 2021-04-10 15:01:01
* @Desc: 初始化
*/
// 初始化
init() {
let hasFileURL = this.hasFileURL()
let { root, layout, theme, view, config } = this.mindMapData
@@ -581,6 +569,10 @@ export default {
this.mindMap.addPlugin(Checkbox)
this.$store.commit('setSupportCheckbox', true)
}
if (typeof LineFlow !== 'undefined') {
this.mindMap.addPlugin(LineFlow)
this.$store.commit('setSupportLineFlow', true)
}
this.mindMap.keyCommand.addShortcut('Control+s', () => {
this.manualSave()
})
@@ -609,7 +601,8 @@ export default {
'node_attachmentClick',
'node_attachmentContextmenu',
'demonstrate_jump',
'exit_demonstrate'
'exit_demonstrate',
'node_note_dblclick'
].forEach(event => {
this.mindMap.on(event, (...args) => {
this.$bus.$emit(event, ...args)
@@ -658,11 +651,7 @@ export default {
return /\.(smm|json|xmind|md|xlsx)$/.test(fileURL)
},
/**
* @Author: 王林
* @Date: 2021-08-03 23:01:13
* @Desc: 动态设置思维导图数据
*/
// 动态设置思维导图数据
setData(data) {
this.handleShowLoading()
if (data.root) {
@@ -674,29 +663,17 @@ export default {
this.manualSave()
},
/**
* @Author: 王林
* @Date: 2021-05-05 13:32:11
* @Desc: 重新渲染
*/
// 重新渲染
reRender() {
this.mindMap.reRender()
},
/**
* @Author: 王林
* @Date: 2021-05-04 13:08:28
* @Desc: 执行命令
*/
// 执行命令
execCommand(...args) {
this.mindMap.execCommand(...args)
},
/**
* @Author: 王林
* @Date: 2021-07-01 22:33:02
* @Desc: 导出
*/
// 导出
async export(...args) {
try {
showLoading()

View File

@@ -42,7 +42,8 @@ export default {
note: '',
activeNodes: [],
editor: null,
isMobile: isMobile()
isMobile: isMobile(),
appointNode: null
}
},
watch: {
@@ -63,6 +64,10 @@ export default {
methods: {
handleNodeActive(...args) {
this.activeNodes = [...args[1]]
this.updateNoteInfo()
},
updateNoteInfo() {
if (this.activeNodes.length > 0) {
let firstNode = this.activeNodes[0]
this.note = firstNode.getData('note') || ''
@@ -71,19 +76,18 @@ export default {
}
},
handleShowNodeNote() {
handleShowNodeNote(node) {
this.$bus.$emit('startTextEdit')
if (node) {
this.appointNode = node
this.note = node.getData('note') || ''
}
this.dialogVisible = true
this.$nextTick(() => {
this.initEditor()
})
},
/**
* @Author: 王林25
* @Date: 2022-05-09 11:37:05
* @Desc: 初始化编辑器
*/
initEditor() {
if (!this.editor) {
this.editor = new Editor({
@@ -96,25 +100,24 @@ export default {
this.editor.setMarkdown(this.note)
},
/**
* @Author: 王林
* @Date: 2021-06-22 22:08:11
* @Desc: 取消
*/
cancel() {
this.dialogVisible = false
if (this.appointNode) {
this.appointNode = null
this.updateNoteInfo()
}
},
/**
* @Author: 王林
* @Date: 2021-06-06 22:28:20
* @Desc: 确定
*/
confirm() {
this.note = this.editor.getMarkdown()
this.activeNodes.forEach(node => {
node.setNote(this.note)
})
if (this.appointNode) {
this.appointNode.setNote(this.note)
} else {
this.activeNodes.forEach(node => {
node.setNote(this.note)
})
}
this.cancel()
}
}

View File

@@ -415,6 +415,49 @@
</el-select>
</div>
</div>
<!-- 流动效果 -->
<div class="row" v-if="supportLineFlow">
<div class="rowItem">
<span class="name">{{ $t('style.openLineFlow') }}</span>
<el-checkbox
v-model="style.lineFlow"
@change="update('lineFlow')"
></el-checkbox>
</div>
<div class="rowItem">
<span class="name">{{ $t('style.direction') }}</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.lineFlowForward"
placeholder=""
@change="update('lineFlowForward')"
>
<el-option
key="1"
:label="$t('style.forward')"
:value="true"
></el-option>
<el-option
key="2"
:label="$t('style.reverse')"
:value="false"
></el-option>
</el-select>
</div>
</div>
<div class="row" v-if="supportLineFlow">
<div class="rowItem">
<span class="name">{{ $t('style.lineFlowDuration') }}</span>
<el-input-number
v-model="style.lineFlowDuration"
@change="update('lineFlowDuration')"
:min="0.1"
size="mini"
:step="0.5"
></el-input-number>
</div>
</div>
<!-- 节点内边距 -->
<div class="title noTop">{{ $t('style.nodePadding') }}</div>
<div class="row">
@@ -441,7 +484,7 @@
</div>
<div class="tipBox" v-else>
<div class="tipIcon iconfont icontianjiazijiedian"></div>
<div class="tipText">请选择一个节点</div>
<div class="tipText">{{ $t('style.selectNodeTip') }}</div>
</div>
</Sidebar>
</template>
@@ -500,14 +543,18 @@ export default {
gradientStyle: false,
startColor: '',
endColor: '',
linearGradientDir: ''
linearGradientDir: '',
lineFlow: false,
lineFlowForward: true,
lineFlowDuration: 1
}
}
},
computed: {
...mapState({
isDark: state => state.localConfig.isDark,
activeSidebar: state => state.activeSidebar
activeSidebar: state => state.activeSidebar,
supportLineFlow: state => state.supportLineFlow
}),
fontFamilyList() {
return fontFamilyList[this.$i18n.locale] || fontFamilyList.zh
@@ -564,29 +611,7 @@ export default {
if (this.activeNodes.length <= 0) {
return
}
;[
'shape',
'paddingX',
'paddingY',
'color',
'fontFamily',
'fontSize',
'textDecoration',
'fontWeight',
'fontStyle',
'borderWidth',
'borderColor',
'fillColor',
'borderDasharray',
'borderRadius',
'lineColor',
'lineDasharray',
'lineWidth',
'lineMarkerDir',
'gradientStyle',
'startColor',
'endColor'
].forEach(item => {
Object.keys(this.style).forEach(item => {
this.style[item] = this.activeNodes[0].getStyle(item, false)
})
this.initLinearGradientDir()

View File

@@ -234,12 +234,14 @@ export default {
window.addEventListener('resize', this.computeToolbarShowThrottle)
this.$bus.$on('lang_change', this.computeToolbarShowThrottle)
window.addEventListener('beforeunload', this.onUnload)
this.$bus.$on('node_note_dblclick', this.onNodeNoteDblclick)
},
beforeDestroy() {
this.$bus.$off('write_local_file', this.onWriteLocalFile)
window.removeEventListener('resize', this.computeToolbarShowThrottle)
this.$bus.$off('lang_change', this.computeToolbarShowThrottle)
window.removeEventListener('beforeunload', this.onUnload)
this.$bus.$off('node_note_dblclick', this.onNodeNoteDblclick)
},
methods: {
// 计算工具按钮如何显示
@@ -501,6 +503,11 @@ export default {
}
this.$message.warning(this.$t('toolbar.notSupportTip'))
}
},
onNodeNoteDblclick(node, e) {
e.stopPropagation()
this.$bus.$emit('showNodeNote', node)
}
}
}

View File

@@ -34,6 +34,7 @@ const store = new Vuex.Store({
supportFreemind: false, // 是否支持Freemind插件
supportExcel: false, // 是否支持Excel插件
supportCheckbox: false, // 是否支持Checkbox插件
supportLineFlow: false, // 是否支持LineFlow插件
isDragOutlineTreeNode: false // 当前是否正在拖拽大纲树的节点
},
mutations: {
@@ -111,6 +112,11 @@ const store = new Vuex.Store({
state.supportCheckbox = data
},
// 设置是否支持Lineflow插件
setSupportLineFlow(state, data) {
state.supportLineFlow = data
},
// 设置树节点拖拽
setIsDragOutlineTreeNode(state, data) {
state.isDragOutlineTreeNode = data

View File

@@ -62,14 +62,14 @@ export const copy = text => {
// 复制文本到剪贴板
export const setDataToClipboard = data => {
if (navigator.clipboard) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(data)
}
}
// 复制图片到剪贴板
export const setImgToClipboard = img => {
if (navigator.clipboard) {
if (navigator.clipboard && navigator.clipboard.write) {
const data = [new ClipboardItem({ ['image/png']: img })]
navigator.clipboard.write(data)
}