Compare commits

...

168 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
街角小林
0041be9892 打包0.12.1 2024-10-25 10:02:17 +08:00
街角小林
7ebab0298b 打包Demo 2024-10-24 18:27:39 +08:00
街角小林
079b963ae3 Fix:修复拖拽调整节点宽度后回退操作时节点宽度没有回退的问题 2024-10-24 18:25:31 +08:00
街角小林
fb6fcd6bd3 Fix:修复当存在滚动条插件,且思维导图被限制在画布内时,演示模式中边缘节点无法正常显示的问题 2024-10-24 09:49:07 +08:00
街角小林
59950b2ba0 Demo:增加是否自动进入文本编辑的配置 2024-10-24 09:35:52 +08:00
街角小林
1f23257917 Demo:增加是否一直显示展开收起按钮的配置 2024-10-24 09:26:25 +08:00
街角小林
4e1db01f44 Demo:增加节点标签显示位置的配置 2024-10-24 09:20:34 +08:00
街角小林
38769c3b55 打包demo 2024-10-23 18:14:06 +08:00
街角小林
a4ef09779d Demo:将设置类的配置从基础样式移到单独的设置栏中 2024-10-23 18:11:48 +08:00
街角小林
4821dd6052 打包Demo 2024-10-23 09:30:19 +08:00
街角小林
f33c886d6a Feat:修复处于文本编辑中切换为只读模式时文本未保存的问题 2024-10-23 09:26:25 +08:00
街角小林
b5b8c2be60 Merge pull request #951 from ZhangMingZhao1/fix-exit-edit-pr
fix: 修复设置readonly如果处于编辑态还能编辑的问题
2024-10-23 09:18:18 +08:00
街角小林
4a7485c58e Demo:节点右键菜单新增添加待办按钮 2024-10-22 17:32:46 +08:00
街角小林
c097d20748 Feat:去除代码中对编号插件的硬编码,新增节点库前置内容的创建逻辑 2024-10-22 17:22:54 +08:00
ZhangMingZhao1
eeeae7d0e2 fix: 修复设置readonly如果处于编辑态还能编辑的问题 2024-10-22 10:49:29 +08:00
街角小林
dd3e169946 Fix:修复在节点文本编辑中调用destroy方法时setBackgroundStyle方法会报错的问题 2024-10-21 09:37:13 +08:00
街角小林
b895a58194 打包Demo 2024-10-17 18:02:25 +08:00
街角小林
2b42b9fafa Demo:支持设置节点文本编辑是否实时更新节点大小,默认开启 2024-10-17 17:56:03 +08:00
街角小林
c2125b07ca Feat:非富文本模式文本编辑框的样式同步节点样式 2024-10-17 17:54:41 +08:00
街角小林
eb342bf69b Feat:当开启openRealtimeRenderOnNodeTextEdit选项后,会去除文本编辑框的背景和阴影,达到类似原地编辑的效果 2024-10-17 13:51:17 +08:00
街角小林
a7eb66a6c9 Feat:after_update_config事件新增上一次配置的返回参数 2024-10-17 11:32:35 +08:00
街角小林
e24fd9bdbb Fix:修复开启maxImgResizeWidthInheritTheme选项后第一次上传图片拖动图片到最大值会导致后续调整按钮无法显示的问题 2024-10-17 09:34:17 +08:00
街角小林
34d7c6fed2 Fix:修复开启openRealtimeRenderOnNodeTextEdit选项后非富文本模式编辑文本时输入框会左右抖动的问题 2024-10-17 09:13:55 +08:00
街角小林
a0f88031c1 Demo:去除节点样式的行高配置 2024-10-16 18:42:38 +08:00
街角小林
889ec13dbf Feat:1.去除主题的行高配置;2.优化非富文本模式下的文本编辑效果 2024-10-16 18:40:47 +08:00
街角小林
4aa5a8c48b Fix:修复给Text文本设置的字号没有加单位导致在一些浏览器上不生效的问题 2024-10-16 17:39:25 +08:00
ZhangMingZhao1
c6a8ec257c fix: 解决设置mousedownEventPreventDefault下框选会选中节点文字闪动问题 2024-10-16 11:07:03 +08:00
街角小林
0ec20b8fa0 update 2024-10-16 10:09:46 +08:00
街角小林
f3285cf4e6 Doc update 2024-10-15 17:08:29 +08:00
街角小林
d7786cd449 update 2024-10-14 17:48:06 +08:00
街角小林
777a9d9047 打包0.12.0 2024-10-14 17:44:48 +08:00
街角小林
0dd7b0ed03 Feat:新增拦截关联线创建的实例化选项 2024-10-14 17:41:35 +08:00
街角小林
b661e8cc92 update 2024-10-14 17:30:01 +08:00
街角小林
1412fb5d09 打包0.12.0 2024-10-14 09:46:53 +08:00
街角小林
ec6a40e381 Feat:同时激活多个节点时编辑某个节点的文本只对该节点生效 2024-10-14 09:33:20 +08:00
街角小林
ca5075d50c 打包0.12.0 2024-10-12 17:35:44 +08:00
街角小林
fa1bf89e70 Feat:非富文本模式下节点文本编辑框增加类名,方便覆盖样式 2024-10-12 17:20:54 +08:00
街角小林
729533b3c1 Feat:mousedownEventPreventDefault选项支持控制节点的mousedown事件是否阻止默认事件 2024-10-12 11:12:03 +08:00
街角小林
6ffd26fd7f Feat:新增拖拽调整图片大小的最小值实例化选项 2024-10-12 10:09:59 +08:00
街角小林
48da6cb642 Feat:支持设置拖拽调整图片大小的最大值 2024-10-11 17:46:47 +08:00
街角小林
0055bbb39d Fix:修复缩放图片工具方法有误的问题;Feat:默认主题的最大图片宽度改为200;Feat:缩放节点图片按钮大小支持配置 2024-10-11 09:16:50 +08:00
街角小林
3349df2183 Fix:修复开启性能模式时拖动子节点在画布外的节点时报错的问题 2024-10-10 17:30:27 +08:00
街角小林
6c4800a6f0 update 2024-10-10 17:15:18 +08:00
街角小林
ca40204d43 Fix:修复某些场景(搜索全部替换等)下节点状态未更新的问题,节点实例新增了数据快照的属性 2024-10-10 11:16:24 +08:00
街角小林
cd28be4b01 Fix:修复非富文本模式下新建节点操作撤回也需要两次的问题 2024-10-10 10:27:40 +08:00
街角小林
af752ea761 Feat:新增添加和删除必要的css样式的方法;Fix:修复富文本模式下节点文本存在连续的数字或字母时导出图片换行失效的问题 2024-10-10 09:28:28 +08:00
街角小林
f2b72830b4 update 2024-10-09 19:14:53 +08:00
街角小林
f5cf7abd4f Feat:抽离库和demo中的主题文件为单独的包 2024-10-09 09:27:51 +08:00
街角小林
29695d1d7e Fix:修改派发节点激活事件的逻辑,去除不必要的判断,避免激活节点没有改变的情况下也会触发事件 2024-09-30 17:35:26 +08:00
街角小林
1510f7a135 打包Demo 2024-09-30 16:56:53 +08:00
街角小林
4b5a691713 Feat:支持拖拽调整节点宽度 2024-09-30 16:43:47 +08:00
街角小林
e9058ed67e Doc: 添加注释 2024-09-29 10:03:31 +08:00
街角小林
fc728ec018 Feat:剪贴板中同时存在文本和图片数据,默认只粘贴文本,可通过实例化选项修改 2024-09-27 17:34:13 +08:00
街角小林
7361df8697 Fix:修复搜索替换时搜索文本是替换文本的子串时搜索结果不正确的问题 2024-09-27 17:20:01 +08:00
街角小林
14e36aa699 Doc: update 2024-09-27 16:49:04 +08:00
街角小林
eff4cd0e77 打包0.11.2 2024-09-25 10:52:21 +08:00
街角小林
82c2d848a9 Feat:增加是否阻止mousedown事件默认事件的实例化选项 2024-09-25 10:31:39 +08:00
街角小林
0344599411 Fix:优化公式插件,适配创建多个实例的情况 2024-09-24 18:21:48 +08:00
街角小林
19fa0af6c0 Fix:修复创建多个思维导图实例时调用addPlugin添加同一个插件只有第一个实例会生效的问题 2024-09-24 17:55:52 +08:00
街角小林
bca1a073f7 Fix:修复创建多个思维导图实例时公式插件会多次扩展Quill的问题 2024-09-24 17:25:06 +08:00
街角小林
98fb23bf7c Feat:主题支持配置各个层级节点的内边距 2024-09-23 17:48:11 +08:00
街角小林
1c9c399b76 Feat:主题新增节点高亮框的圆角配置 2024-09-23 17:19:47 +08:00
wanglin2
bc43fedd87 Feat:新增自定义判断wheel事件是否来自触控板的实例化选项;优化代码 2024-09-21 19:54:43 +08:00
街角小林
156054ed93 Merge pull request #886 from Tarrency/feature-to-the-feature
feat: 配置平移步长和扩缩最值,解决触控板灵敏度问题
2024-09-21 15:24:58 +08:00
wangqi01
937f7d2969 fix: 限值平移步长比例生效只在鼠标/触控板滚动行为内 2024-09-20 18:03:18 +08:00
街角小林
e56a6d36cb Demo:优化节点图片添加了无法访问的图片的展示样式 2024-09-20 17:33:16 +08:00
街角小林
9f19061010 Fix:修复节点富文本编辑能粘贴图片的问题 2024-09-20 16:58:06 +08:00
街角小林
0d465f28f3 Feat:注释掉非https情况下的粘贴逻辑 2024-09-20 16:37:07 +08:00
街角小林
b4fdcd81b0 Fix:修复自定义主题节点渐变色方向无效的问题 2024-09-20 09:32:12 +08:00
Tarrency
38c0fe2e39 feat: 配置平移步长和扩缩最值,解决触控板灵敏度问题 2024-09-19 20:35:46 +08:00
街角小林
29ddbba9b9 Demo:新增一键展开某个节点所有下级节点的右键菜单 2024-09-19 19:52:34 +08:00
街角小林
9f9ed1e84f Fix:修复思维导图非常大的情况下导出图片失败的问题 2024-09-19 19:36:10 +08:00
街角小林
9ebc416167 Demo:修复右键菜单中的二级菜单会超出边界的问题 2024-09-19 18:19:57 +08:00
街角小林
5d49d985c0 优化代码 2024-09-19 18:14:52 +08:00
街角小林
c36338a794 Merge pull request #876 from BlackEyeBear/main
处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本
2024-09-19 18:05:36 +08:00
wanglin2
c21ee4960e Fix:修复存在概要时切换主题会报错的问题 2024-09-18 21:52:11 +08:00
BlackEyeBear
5090f21b0e 处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本 2024-09-16 22:08:55 +08:00
panda
3d9d172fa0 '处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本' 2024-09-16 21:32:52 +08:00
街角小林
766ce310d0 打包0.11.1 2024-09-11 20:21:10 +08:00
街角小林
075bf54d28 Merge branch 'feature' into main 2024-09-11 20:02:17 +08:00
街角小林
14ebd7a239 Merge pull request #839 from PeterDaveHelloKitchen/zh_tw
Add Traditional Chinese(zh_TW) translation
2024-09-11 20:01:33 +08:00
街角小林
ac13aa8bc9 Doc: update 2024-09-11 19:59:59 +08:00
街角小林
f4d84aeb55 Demo:支持导入导出Excel 2024-09-11 17:41:57 +08:00
街角小林
9b7305de1e Demo:新增删除节点图片前的二次提示 2024-09-09 18:04:56 +08:00
街角小林
ef526fe302 Feat:新增拦截删除节点图片的实例化选项 2024-09-09 18:04:37 +08:00
街角小林
007a5f2815 Feat:组织结构图支持曲线连线 2024-09-09 17:47:17 +08:00
街角小林
e04b680cdc Feat:非富文本模式下文本编辑支持粘贴带换行的文本 2024-09-06 17:21:55 +08:00
街角小林
156c866bc1 Feat:1.去除highlightNodeBoxStyle选项;2.highlightNode方法新增参数;3.概要区间高亮框的颜色由主题的hoverRectColor选项和hoverRectColor实例化选项确定 2024-09-06 16:56:22 +08:00
街角小林
62b734890b Fix:修复切换主题时概要节点的样式没有更新的问题 2024-09-06 09:36:02 +08:00
街角小林
c7f3dd4d7e Feat:主题支持设置节点hover和激活时矩形框的颜色 2024-09-06 09:35:10 +08:00
街角小林
5014a2feb7 Feat:展开所有和收起所有的命令支持指定节点的uid 2024-09-05 19:14:18 +08:00
街角小林
8f8c6c9d95 Feat:expandBtnNumHandler选项新增节点实例的回调参数 2024-09-05 18:21:38 +08:00
街角小林
c8d5a34640 Demo:支持导入和导出FreeMind文件 2024-09-05 09:40:41 +08:00
街角小林
bd0fc37f03 Demo:删除无用文件 2024-09-04 09:07:21 +08:00
Peter Dave Hello
c05d947fa3 Add Traditional Chinese(zh_TW) translation 2024-09-03 21:44:43 +08:00
街角小林
1f303145c6 Fix:修复富文本模式下即使未修改文本也会添加历史记录的问题 2024-09-03 17:20:49 +08:00
街角小林
453e7311b8 Feat:公式插件:去除将公式富文本转换为公式源码时的特殊字符转义逻辑,避免双重转换导致报错 2024-08-30 17:38:39 +08:00
街角小林
c12b7f6dae Feat:更新节点非样式字段列表 2024-08-30 17:02:48 +08:00
街角小林
7ba11be42b Fix:修复公式中存在<>符号时导出svg报错的问题 2024-08-30 14:01:59 +08:00
wanglin2
ce49fcb511 Feat:修改更新节点当前应用样式的逻辑 2024-08-29 22:01:05 +08:00
街角小林
06fb6245b7 Demo:支持设置节点背景渐变方向 2024-08-29 18:29:04 +08:00
街角小林
fa8a80792d Feat:主题支持配置背景渐变的方向 2024-08-29 17:48:03 +08:00
街角小林
570bbb1b16 Feat:新增开启节点文本编辑实时更新节点大小和位置的实例化选项 2024-08-29 15:33:38 +08:00
街角小林
4e327c3a48 Feat:格式刷支持刷节点所有生效的样式,包括来自主题的和自定义的 2024-08-29 10:30:52 +08:00
街角小林
89d89f4dd8 update 2024-08-28 17:41:52 +08:00
街角小林
5ae998f304 Merge branch 'feature' of https://github.com/wanglin2/mind-map into feature 2024-08-28 16:49:51 +08:00
街角小林
428c4fd93b Fix:修复默认主题配置中的normal单词拼写错误的问题 2024-08-28 16:41:59 +08:00
街角小林
2bcc4a7c18 Demo:支持点击画布取消缩放输入框的聚焦状态 2024-08-28 16:39:35 +08:00
街角小林
9229f13172 Fix:调整hide_text_edit事件触发时机,防止一些情况下的死循环问题 2024-08-28 16:20:49 +08:00
wanglin2
afdb557a49 打包demo 2024-08-26 21:16:02 +08:00
wanglin2
62e25cdf86 Merge branch 'feature' of https://github.com/wanglin2/mind-map into feature 2024-08-26 21:10:09 +08:00
wanglin2
d2562f35bd Feat:优化mac触控板双指拖动画布的体验 2024-08-26 07:33:04 +08:00
201 changed files with 5371 additions and 4093 deletions

246
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节点编号插件[收费]
> 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,19 +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`拉你入群。思维导图相关问题皆可在群里提问,不必私聊作者
<img src="./qrcode.jpg" style="width: 300px" />
微信添加`wanglinguanfang`拉你入群。根据过往的经验大部分问题都可以通过查看issue列表或文档解决所以提问前请确保你已经阅读完了所有文档文档里没有的可在群里提问不必私聊作者如果你一定要私聊请先发红包¥9.9+每次)
# star
@@ -119,15 +115,119 @@ const mindMap = new MindMap({
# 请作者喝杯咖啡
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡你的支持是开发者持续维护的最大动力~
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~你的赞助对项目的可持续发展非常重要,是作者持续维护的最大动力
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
>
> 也可以通过购买付费插件来支持我们:[付费插件](https://wanglin2.github.io/mind-map-docs/plugins/about.html)。
>
> 赞助等级最强王者¥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;" />
@@ -137,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>
@@ -157,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>
@@ -189,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>
@@ -209,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>
@@ -221,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>
@@ -301,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>
@@ -317,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>
@@ -329,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>
@@ -357,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>
@@ -377,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>
@@ -401,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>
@@ -433,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>
@@ -454,7 +486,63 @@ const mindMap = new MindMap({
<span>张文建</span>
</span>
<span>
<img src="./web/src/assets/avatar/.jpg" style="width: 50px;height: 50px;" />
<span></span>
<img src="./web/src/assets/avatar/Lawliet.jpg" style="width: 50px;height: 50px;" />
<span>Lawliet</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>Eric</span>
</span>
<span>
<img src="./web/src/assets/avatar/Joe.jpg" style="width: 50px;height: 50px;" />
<span>Joe</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/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>

View File

@@ -13,4 +13,4 @@ if (fs.existsSync(src)) {
fs.unlinkSync(src)
}
console.warn('请检查手绘风格、标记插件、编号插件是否启用!!!')
console.warn('请检查付费插件是否启用!!!')

2
dist/css/app.css vendored
View File

@@ -1 +1 @@
*{margin:0;padding:0;box-sizing:border-box}#app{font-family:Avenir,Helvetica,Arial,sans-serif;color:#2c3e50}@font-face{font-family:iconfont;src:url(../fonts/iconfont.woff2) format("woff2"),url(../fonts/iconfont.woff) format("woff"),url(../fonts/iconfont.ttf) format("truetype")}#app,.iconfont{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal}.iconwaikuang:before{content:"\e640"}.iconhighlight:before{content:"\e6b8"}.iconyanshibofang:before{content:"\e648"}.iconfujian:before{content:"\e88a"}.icongeshihua:before{content:"\e7a3"}.iconyuanma:before{content:"\e658"}.icongundongtiao:before{content:"\e670"}.iconxietongwendang:before{content:"\e60d"}.iconTXT:before{content:"\e6e1"}.iconwenjian1:before{content:"\e69f"}.icondodeparent:before{content:"\e70f"}.icongongshi:before{content:"\e617"}.icontouming:before{content:"\e60c"}.iconlieri:before{content:"\e60b"}.iconmoon_line:before{content:"\e745"}.iconsousuo:before{content:"\e693"}.iconjiantouyou:before{content:"\e62d"}.iconbianji1:before{content:"\e60a"}.icondaohang1:before{content:"\e632"}.iconyanjing:before{content:"\e8bf"}.iconwangzhan:before{content:"\e628"}.iconcsdn:before{content:"\e608"}.iconshejiaotubiao-10:before{content:"\e644"}.iconstar:before{content:"\e7df"}.iconfork:before{content:"\e641"}.iconxiazai:before{content:"\e613"}.iconteamwork:before{content:"\e870"}.iconshuiyin:before{content:"\e67a"}.iconxmind:before{content:"\ea57"}.iconmouseR:before{content:"\e6bd"}.iconmouseL:before{content:"\e6c0"}.iconwenjian:before{content:"\e607"}.iconpdf:before{content:"\e740"}.iconPNG:before{content:"\ec18"}.iconSVG:before{content:"\e621"}.iconmarkdown:before{content:"\ec04"}.iconjson:before{content:"\ea42"}.iconlianjiexian:before{content:"\e75b"}.iconbangzhu:before{content:"\e620"}.iconshezhi:before{content:"\e8b7"}.iconwushuju:before{content:"\e643"}.iconzuijinliulan:before{content:"\e62f"}.icon3zuidahua-3:before{content:"\e692"}.iconzuixiaohua:before{content:"\e650"}.iconzuidahua:before{content:"\e651"}.iconguanbi:before{content:"\e652"}.icondiannao:before{content:"\eac0"}.iconzhuye:before{content:"\e65c"}.iconbendi1x:before{content:"\e606"}.iconbeijingyanse:before{content:"\e6f8"}.iconqingchu:before{content:"\e605"}.iconcase:before{content:"\e6c6"}.iconxingzhuang-wenzi:before{content:"\eb99"}.iconzitijiacu:before{content:"\ec83"}.iconzitixiahuaxian:before{content:"\ec85"}.iconzitixieti:before{content:"\ec86"}.iconshanchuxian:before{content:"\e612"}.iconzitiyanse:before{content:"\e854"}.icongithub:before{content:"\e64f"}.iconchoose1:before{content:"\e6c5"}.iconzhuti:before{content:"\e7aa"}.icondaochu1:before{content:"\e63e"}.iconlingcunwei:before{content:"\e657"}.iconexport:before{content:"\e642"}.icondakai:before{content:"\ebdf"}.iconxinjian:before{content:"\e64e"}.iconjianqie:before{content:"\e601"}.iconzhengli:before{content:"\e83b"}.iconfuzhi:before{content:"\e604"}.iconniantie:before{content:"\e63f"}.iconshangyi:before{content:"\e6be"}.iconxiayi:before{content:"\e6bf"}.icongaikuozonglan:before{content:"\e609"}.iconquanxuan:before{content:"\f199"}.icondaoru:before{content:"\e6a3"}.iconhoutui-shi:before{content:"\e656"}.iconqianjin1:before{content:"\e654"}.iconwithdraw:before{content:"\e603"}.iconqianjin:before{content:"\e600"}.iconhuifumoren:before{content:"\e60e"}.iconhuanhang:before{content:"\e61e"}.iconsuoxiao:before{content:"\ec13"}.iconbianji:before{content:"\e626"}.iconfangda:before{content:"\e663"}.iconquanping1:before{content:"\e664"}.icondingwei:before{content:"\e616"}.icondaohang:before{content:"\e611"}.iconjianpan:before{content:"\e64d"}.iconquanping:before{content:"\e602"}.icondaochu:before{content:"\e63d"}.iconbiaoqian:before{content:"\e63c"}.iconflow-Mark:before{content:"\e65b"}.iconchaolianjie:before{content:"\e6f4"}.iconjingzi:before{content:"\e610"}.iconxiaolian:before{content:"\e60f"}.iconimage:before{content:"\e629"}.iconjiegou:before{content:"\e61d"}.iconyangshi:before{content:"\e631"}.iconfuhao-dagangshu:before{content:"\e71f"}.icontianjiazijiedian:before{content:"\e622"}.iconjiedian:before{content:"\e655"}.iconshanchu:before{content:"\e696"}.iconzhankai:before{content:"\e64c"}.iconzhankai1:before{content:"\e673"}
*{margin:0;padding:0;box-sizing:border-box}#app{font-family:Avenir,Helvetica,Arial,sans-serif;color:#2c3e50}@font-face{font-family:iconfont;src:url(../fonts/iconfont.woff2) format("woff2"),url(../fonts/iconfont.woff) format("woff"),url(../fonts/iconfont.ttf) format("truetype")}#app,.iconfont{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal}.iconfile-excel:before{content:"\e7b7"}.iconfreemind:before{content:"\e97d"}.iconwaikuang:before{content:"\e640"}.iconhighlight:before{content:"\e6b8"}.iconyanshibofang:before{content:"\e648"}.iconfujian:before{content:"\e88a"}.icongeshihua:before{content:"\e7a3"}.iconyuanma:before{content:"\e658"}.icongundongtiao:before{content:"\e670"}.iconxietongwendang:before{content:"\e60d"}.iconTXT:before{content:"\e6e1"}.iconwenjian1:before{content:"\e69f"}.icondodeparent:before{content:"\e70f"}.icongongshi:before{content:"\e617"}.icontouming:before{content:"\e60c"}.iconlieri:before{content:"\e60b"}.iconmoon_line:before{content:"\e745"}.iconsousuo:before{content:"\e693"}.iconjiantouyou:before{content:"\e62d"}.iconbianji1:before{content:"\e60a"}.icondaohang1:before{content:"\e632"}.iconyanjing:before{content:"\e8bf"}.iconwangzhan:before{content:"\e628"}.iconcsdn:before{content:"\e608"}.iconshejiaotubiao-10:before{content:"\e644"}.iconstar:before{content:"\e7df"}.iconfork:before{content:"\e641"}.iconxiazai:before{content:"\e613"}.iconteamwork:before{content:"\e870"}.iconshuiyin:before{content:"\e67a"}.iconxmind:before{content:"\ea57"}.iconmouseR:before{content:"\e6bd"}.iconmouseL:before{content:"\e6c0"}.iconwenjian:before{content:"\e607"}.iconpdf:before{content:"\e740"}.iconPNG:before{content:"\ec18"}.iconSVG:before{content:"\e621"}.iconmarkdown:before{content:"\ec04"}.iconjson:before{content:"\ea42"}.iconlianjiexian:before{content:"\e75b"}.iconbangzhu:before{content:"\e620"}.iconshezhi:before{content:"\e8b7"}.iconwushuju:before{content:"\e643"}.iconzuijinliulan:before{content:"\e62f"}.icon3zuidahua-3:before{content:"\e692"}.iconzuixiaohua:before{content:"\e650"}.iconzuidahua:before{content:"\e651"}.iconguanbi:before{content:"\e652"}.icondiannao:before{content:"\eac0"}.iconzhuye:before{content:"\e65c"}.iconbendi1x:before{content:"\e606"}.iconbeijingyanse:before{content:"\e6f8"}.iconqingchu:before{content:"\e605"}.iconcase:before{content:"\e6c6"}.iconxingzhuang-wenzi:before{content:"\eb99"}.iconzitijiacu:before{content:"\ec83"}.iconzitixiahuaxian:before{content:"\ec85"}.iconzitixieti:before{content:"\ec86"}.iconshanchuxian:before{content:"\e612"}.iconzitiyanse:before{content:"\e854"}.icongithub:before{content:"\e64f"}.iconchoose1:before{content:"\e6c5"}.iconzhuti:before{content:"\e7aa"}.icondaochu1:before{content:"\e63e"}.iconlingcunwei:before{content:"\e657"}.iconexport:before{content:"\e642"}.icondakai:before{content:"\ebdf"}.iconxinjian:before{content:"\e64e"}.iconjianqie:before{content:"\e601"}.iconzhengli:before{content:"\e83b"}.iconfuzhi:before{content:"\e604"}.iconniantie:before{content:"\e63f"}.iconshangyi:before{content:"\e6be"}.iconxiayi:before{content:"\e6bf"}.icongaikuozonglan:before{content:"\e609"}.iconquanxuan:before{content:"\f199"}.icondaoru:before{content:"\e6a3"}.iconhoutui-shi:before{content:"\e656"}.iconqianjin1:before{content:"\e654"}.iconwithdraw:before{content:"\e603"}.iconqianjin:before{content:"\e600"}.iconhuifumoren:before{content:"\e60e"}.iconhuanhang:before{content:"\e61e"}.iconsuoxiao:before{content:"\ec13"}.iconbianji:before{content:"\e626"}.iconfangda:before{content:"\e663"}.iconquanping1:before{content:"\e664"}.icondingwei:before{content:"\e616"}.icondaohang:before{content:"\e611"}.iconjianpan:before{content:"\e64d"}.iconquanping:before{content:"\e602"}.icondaochu:before{content:"\e63d"}.iconbiaoqian:before{content:"\e63c"}.iconflow-Mark:before{content:"\e65b"}.iconchaolianjie:before{content:"\e6f4"}.iconjingzi:before{content:"\e610"}.iconxiaolian:before{content:"\e60f"}.iconimage:before{content:"\e629"}.iconjiegou:before{content:"\e61d"}.iconyangshi:before{content:"\e631"}.iconfuhao-dagangshu:before{content:"\e71f"}.icontianjiazijiedian:before{content:"\e622"}.iconjiedian:before{content:"\e655"}.iconshanchu:before{content:"\e696"}.iconzhankai:before{content:"\e64c"}.iconzhankai1:before{content:"\e673"}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/img/classic10.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/classic11.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/classic12.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/classic13.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
dist/img/classic14.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
dist/img/classic15.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/classic8.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
dist/img/classic9.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/dark5.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
dist/img/dark6.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/dark7.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

2
dist/js/app.js vendored

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

File diff suppressed because one or more lines are too long

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

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,20 +18,19 @@ 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'
import * as constants from './src/constants/constant.js'
import themes from './src/themes/index.js'
import * as defaultTheme from './src/themes/default.js'
import * as defaultTheme from './src/theme/default.js'
MindMap.xmind = xmind
MindMap.markdown = markdown
MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.11.0'
MindMap.version = '0.12.2'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)
@@ -52,5 +51,6 @@ MindMap.usePlugin(MiniMap)
.usePlugin(RainbowLines)
.usePlugin(Demonstrate)
.usePlugin(OuterFrame)
.usePlugin(MindMapLayoutPro)
export default MindMap

View File

@@ -2,7 +2,7 @@ import View from './src/core/view/View'
import Event from './src/core/event/Event'
import Render from './src/core/render/Render'
import merge from 'deepmerge'
import theme from './src/themes'
import theme from './src/theme'
import Style from './src/core/render/node/Style'
import KeyCommand from './src/core/command/KeyCommand'
import Command from './src/core/command/Command'
@@ -19,11 +19,12 @@ import {
getObjectChangedProps,
isUndef,
handleGetSvgDataExtraContent,
getNodeTreeBoundingRect
getNodeTreeBoundingRect,
mergeTheme
} from './src/utils'
import defaultTheme, {
checkIsNodeSizeIndependenceConfig
} from './src/themes/default'
} from './src/theme/default'
import { defaultOpt } from './src/constants/defaultOptions'
// 思维导图
@@ -34,6 +35,7 @@ class MindMap {
* @param {defaultOpt} opt
*/
constructor(opt = {}) {
MindMap.instanceCount++
// 合并选项
this.opt = this.handleOpt(merge(defaultOpt, opt))
// 预处理节点数据
@@ -50,9 +52,29 @@ class MindMap {
this.initWidth = this.width
this.initHeight = this.height
// 添加css
// 必要的css样式
this.cssEl = null
this.addCss()
this.cssTextMap = {} // 该样式在实例化时会动态添加到页面同时导出为svg时也会添加到svg源码中
// 节点前置内容列表
/*
{
name: '',// 一个唯一的类型标识
// 创建节点的显示内容:节点元素、宽高
createContent: (node) => {
return {
node: null,
width: 0,
height: 0
}
},
// 创建保存到节点实例的opt对象中的数据
createNodeData: () => {},
// 更新节点实例的opt数据返回数据是否改变了
updateNodeData: () => {},
}
*/
this.nodeInnerPrefixList = []
// 画布
this.initContainer()
@@ -96,11 +118,16 @@ class MindMap {
this.initPlugin(plugin)
})
// 添加必要的css样式
this.addCss()
// 初始渲染
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()
}
}
// 配置参数处理
@@ -168,17 +195,46 @@ class MindMap {
this.otherDraw.clear()
}
// 追加必要的css样式
// 该样式在实例化时会动态添加到页面同时导出为svg时也会添加到svg源码中
appendCss(key, str) {
this.cssTextMap[key] = str
this.removeCss()
this.addCss()
}
// 移除追加的css样式
removeAppendCss(key) {
if (this.cssTextMap[key]) {
delete this.cssTextMap[key]
this.removeCss()
this.addCss()
}
}
// 拼接必要的css样式
joinCss() {
return (
cssContent +
Object.keys(this.cssTextMap)
.map(key => {
return this.cssTextMap[key]
})
.join('\n')
)
}
// 添加必要的css样式到页面
addCss() {
this.cssEl = document.createElement('style')
this.cssEl.type = 'text/css'
this.cssEl.innerHTML = cssContent
this.cssEl.innerHTML = this.joinCss()
document.head.appendChild(this.cssEl)
}
// 移除css
removeCss() {
document.head.removeChild(this.cssEl)
if (this.cssEl) document.head.removeChild(this.cssEl)
}
// 渲染,部分渲染
@@ -252,7 +308,10 @@ class MindMap {
// 设置主题
initTheme() {
// 合并主题配置
this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig)
this.themeConfig = mergeTheme(
theme[this.opt.theme] || theme.default,
this.opt.themeConfig
)
// 设置背景样式
Style.setBackgroundStyle(this.el, this.themeConfig)
}
@@ -279,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)
}
}
@@ -302,8 +361,11 @@ class MindMap {
// 更新配置
updateConfig(opt = {}) {
this.emit('before_update_config', this.opt)
const lastOpt = {
...this.opt
}
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
this.emit('after_update_config', this.opt)
this.emit('after_update_config', this.opt, lastOpt)
}
// 获取当前布局结构
@@ -333,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()
@@ -419,11 +484,20 @@ class MindMap {
}
const isReadonly = mode === CONSTANTS.MODE.READONLY
if (isReadonly === this.opt.readonly) return
this.opt.readonly = isReadonly
if (this.opt.readonly) {
if (isReadonly) {
// 如果处于编辑态,要隐藏所有的编辑框
if (this.renderer.textEdit.isShowTextEdit()) {
this.renderer.textEdit.hideEditTextBox()
this.command.originAddHistory()
}
// 取消当前激活的元素
this.execCommand('CLEAR_ACTIVE_NODE')
}
this.opt.readonly = isReadonly
// 切换为编辑模式时,如果历史记录堆栈是空的,那么进行一次入栈操作
if (!isReadonly && this.command.history.length <= 0) {
this.command.originAddHistory()
}
this.emit('mode_change', mode)
}
@@ -509,7 +583,7 @@ class MindMap {
this.watermark.isInExport = false
}
// 添加必要的样式
;[cssContent, ...cssTextList].forEach(s => {
[this.joinCss(), ...cssTextList].forEach(s => {
clone.add(SVG(`<style>${s}</style>`))
})
// 附加内容
@@ -563,8 +637,8 @@ class MindMap {
let index = MindMap.hasPlugin(plugin)
if (index === -1) {
MindMap.usePlugin(plugin, opt)
this.initPlugin(plugin)
}
this.initPlugin(plugin)
}
// 移除插件
@@ -583,6 +657,7 @@ class MindMap {
// 实例化插件
initPlugin(plugin) {
if (this[plugin.instanceName]) return
this[plugin.instanceName] = new plugin({
mindMap: this,
pluginOpt: plugin.pluginOpt
@@ -616,6 +691,7 @@ class MindMap {
this.el.innerHTML = ''
this.el = null
this.removeCss()
MindMap.instanceCount--
}
}
@@ -632,13 +708,20 @@ MindMap.hasPlugin = plugin => {
return item === plugin
})
}
MindMap.instanceCount = 0
// 定义新主题
MindMap.defineTheme = (name, config = {}) => {
if (theme[name]) {
return new Error('该主题名称已存在')
}
theme[name] = merge(defaultTheme, config)
theme[name] = mergeTheme(defaultTheme, config)
}
// 移除主题
MindMap.removeTheme = name => {
if (theme[name]) {
theme[name] = null
}
}
export default MindMap

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.11.0",
"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

@@ -1,167 +1,3 @@
// 主题列表
export const themeList = [
{
name: '默认',
value: 'default',
dark: false
},
{
name: '暗色2',
value: 'dark2',
dark: true
},
{
name: '天清绿',
value: 'skyGreen',
dark: false
},
{
name: '脑图经典2',
value: 'classic2',
dark: false
},
{
name: '脑图经典3',
value: 'classic3',
dark: false
},
{
name: '经典绿',
value: 'classicGreen',
dark: false
},
{
name: '经典蓝',
value: 'classicBlue',
dark: false
},
{
name: '天空蓝',
value: 'blueSky',
dark: false
},
{
name: '脑残粉',
value: 'brainImpairedPink',
dark: false
},
{
name: '暗色',
value: 'dark',
dark: true
},
{
name: '泥土黄',
value: 'earthYellow',
dark: false
},
{
name: '清新绿',
value: 'freshGreen',
dark: false
},
{
name: '清新红',
value: 'freshRed',
dark: false
},
{
name: '浪漫紫',
value: 'romanticPurple',
dark: false
},
{
name: '粉红葡萄',
value: 'pinkGrape',
dark: false
},
{
name: '薄荷',
value: 'mint',
dark: false
},
{
name: '金色vip',
value: 'gold',
dark: false
},
{
name: '活力橙',
value: 'vitalityOrange',
dark: false
},
{
name: '绿叶',
value: 'greenLeaf',
dark: false
},
{
name: '脑图经典',
value: 'classic',
dark: true
},
{
name: '脑图经典4',
value: 'classic4',
dark: false
},
{
name: '小黄人',
value: 'minions',
dark: false
},
{
name: '简约黑',
value: 'simpleBlack',
dark: false
},
{
name: '课程绿',
value: 'courseGreen',
dark: false
},
{
name: '咖啡',
value: 'coffee',
dark: false
},
{
name: '红色精神',
value: 'redSpirit',
dark: false
},
{
name: '黑色幽默',
value: 'blackHumour',
dark: true
},
{
name: '深夜办公室',
value: 'lateNightOffice',
dark: true
},
{
name: '黑金',
value: 'blackGold',
dark: true
},
{
name: '牛油果',
value: 'avocado',
dark: false
},
{
name: '秋天',
value: 'autumn',
dark: false
},
{
name: '橙汁',
value: 'orangeJuice',
dark: true
}
]
// 常量
export const CONSTANTS = {
CHANGE_THEME: 'changeTheme',
@@ -239,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'
}
}
@@ -328,7 +169,12 @@ export const nodeDataNoStylePropList = [
'notation',
'outerFrame',
'number',
'range'
'range',
'customLeft',
'customTop',
'customTextWidth',
'checkbox',
'dir'
]
// 错误类型
@@ -361,6 +207,10 @@ export const cssContent = `
opacity: 1;
stroke-width: 2;
}
.smm-text-node-wrap {
user-select: none;
}
`
// html自闭合标签列表
@@ -373,3 +223,6 @@ export const selfCloseTagList = [
'meta',
'area'
]
// 非富文本模式下的节点文本行高
export const noneRichTextNodeLineHeight = 1.2

View File

@@ -7,6 +7,8 @@ export const defaultOpt = {
el: null,
// 思维导图回显数据
data: null,
// 要恢复的视图数据一般通过mindMap.view.getTransformData()方法获取
viewData: null,
// 是否只读
readonly: false,
// 布局
@@ -19,6 +21,16 @@ export const defaultOpt = {
themeConfig: {},
// 放大缩小的增量比例
scaleRatio: 0.2,
// 平移的步长比例,只在鼠标滚轮和触控板触发的平移中应用
translateRatio: 1,
// 最小缩小值百分数最小为0该选项只会影响view.narrow方法影响的行为为Ctrl+-快捷键、鼠标滚轮及触控板不会影响其他方法比如view.setScale所以需要你自行限制大小
minZoomRatio: 20,
// 最大放大值,百分数,传-1代表不限制否则传0以上数字该选项只会影响view.enlarge方法
maxZoomRatio: 400,
// 自定义判断wheel事件是否来自电脑的触控板
// 默认是通过判断e.deltaY的值是否小于10显然这种方法是不准确的当鼠标滚动的很慢或者触摸移动的很快时判断就失效了如果你有更好的方法欢迎提交issue
// 如果你希望自己来判断那么传递一个函数接收一个参数e事件对象需要返回true或false代表是否是来自触控板
customCheckIsTouchPad: null,
// 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
mouseScaleCenterUseMousePosition: true,
// 最多显示几个标签
@@ -67,13 +79,14 @@ export const defaultOpt = {
close: ''
},
// 处理收起节点数量
expandBtnNumHandler: num => {
return num
},
expandBtnNumHandler: null,
// 是否显示带数量的收起按钮
isShowExpandNum: true,
// 是否只有当鼠标在画布内才响应快捷键事件
enableShortcutOnlyWhenMouseInSvg: true,
// 自定义判断是否响应快捷键事件优先级比enableShortcutOnlyWhenMouseInSvg选项高
// 可以传递一个函数接收事件对象e为参数需要返回true或false返回true代表允许响应快捷键事件反之不允许库默认当事件目标为body或为文本编辑框元素普通文本编辑框、富文本编辑框、关联线文本编辑框时响应快捷键其他不响应
customCheckEnableShortcut: null,
// 初始根节点的位置
initRootNodePosition: null,
// 节点文本编辑框的z-index
@@ -120,6 +133,8 @@ export const defaultOpt = {
// 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式
// 开启该特性后需要给你的输入框绑定keydown事件并禁止冒泡
enableAutoEnterTextEditWhenKeydown: false,
// 当enableAutoEnterTextEditWhenKeydown选项开启时生效当通过按键进入文本编辑时是否自动清空原有文本
autoEmptyTextWhenKeydownEnterEdit: false,
// 自定义对剪贴板文本的处理。当按ctrl+v粘贴时会读取用户剪贴板中的文本和图片默认只会判断文本是否是普通文本和simple-mind-map格式的节点数据如果你想处理其他思维导图的数据比如processon、zhixi等那么可以传递一个函数接受当前剪贴板中的文本为参数返回处理后的数据可以返回两种类型
/*
1.返回一个纯文本,那么会直接以该文本创建一个子节点
@@ -176,11 +191,6 @@ export const defaultOpt = {
addHistoryTime: 100,
// 是否禁止拖动画布
isDisableDrag: false,
// 鼠标移入概要高亮所属节点时的高亮框样式
highlightNodeBoxStyle: {
stroke: 'rgb(94, 200, 248)',
fill: 'transparent'
},
// 创建新节点时的行为
/*
DEFAULT :默认会激活新创建的节点,并且进入编辑模式。如果同时创建了多个新节点,那么只会激活而不会进入编辑模式
@@ -221,7 +231,7 @@ export const defaultOpt = {
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
// 该选项实际影响的是render.moveNodeToCenter方法moveNodeToCenter方法本身也存在第二个参数resetScale来设置是否复位如果resetScale参数没有传递那么使用resetScaleOnMoveNodeToCenter配置否则使用resetScale配置
resetScaleOnMoveNodeToCenter: false,
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分。如果存在编号、任务勾选框内容,这里添加的前置内容会在这两者之后
createNodePrefixContent: null,
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
createNodePostfixContent: null,
@@ -238,6 +248,26 @@ export const defaultOpt = {
padding: 100, // 超出画布四周指定范围内依旧渲染节点
removeNodeWhenOutCanvas: true // 节点移除画布可视区域后从画布删除
},
// 如果节点文本为空,那么为了避免空白节点高度塌陷,会用该字段指定的文本测量一个高度
emptyTextMeasureHeightText: 'abc123我和你',
// 是否在进行节点文本编辑时实时更新节点大小和节点位置,开启后当节点数量比较多时可能会造成卡顿
openRealtimeRenderOnNodeTextEdit: false,
// 默认会给容器元素el绑定mousedown事件可通过该选项设置是否阻止其默认事件
// 如果设置为true会带来一定问题比如你聚焦在思维导图外的其他输入框点击画布就不会触发其失焦
mousedownEventPreventDefault: false,
// 在激活上粘贴用户剪贴板中的数据时,如果同时存在文本和图片,那么只粘贴文本,忽略图片
onlyPasteTextWhenHasImgAndText: true,
// 是否允许拖拽调整节点的宽度实际上压缩的是节点里面文本内容的宽度当节点文本内容宽度压缩到最小时无法继续压缩。如果节点存在图片那么最小值以图片宽度和文本内容最小宽度的最大值为准目前该特性仅在两种情况下可用1.开启了富文本模式即注册了RichText插件2.自定义节点内容)
enableDragModifyNodeWidth: true,
// 当允许拖拽调整节点的宽度时,可以通过该选项设置节点文本内容允许压缩的最小宽度
minNodeTextModifyWidth: 20,
// 同minNodeTextModifyWidth最大值传-1代表不限制
maxNodeTextModifyWidth: -1,
// 自定义处理节点的连线方法可以传递一个函数函数接收三个参数node节点实例、line节点的某条连线@svgjs库的path对象, { width, color, dasharray }dasharray该条连线的虚线样式为none代表实线你可以修改line对象来达到修改节点连线样式的效果比如增加流动效果
customHandleLine: null,
// 实例化完后是否立刻进行一次历史数据入栈操作
// 即调用mindMap.command.addHistory方法
addHistoryOnInit: true,
// 【Select插件】
// 多选节点时鼠标移动到边缘时的画布移动偏移量
@@ -321,6 +351,8 @@ export const defaultOpt = {
// 导出png、svg、pdf时会获取画布上的svg数据进行克隆然后通过该克隆的元素进行导出如果你想对该克隆元素做一些处理比如新增、替换、修改其中的一些元素那么可以通过该参数传递一个处理函数接收svg元素对象处理后需要返回原svg元素对象。
// 需要注意的是svg对象指的是@svgdotjs/svg.js库的元素对象所以你需要阅读该库的文档来操作该对象
handleBeingExportSvg: null,
// 导出图片或pdf都是通过canvas将svg绘制出来再导出所以如果思维导图特别大宽高可能会超出canvas支持的上限所以会进行缩放这个上限可以通过该参数设置代表canvas宽和高的最大宽度
maxCanvasSize: 16384,
// 【AssociativeLine插件】
// 关联线默认文字
@@ -337,6 +369,8 @@ export const defaultOpt = {
},
// 是否允许调整关联线两个端点的位置
enableAdjustAssociativeLinePoints: true,
// 关联线连接即将完成时执行如果要阻止本次连接可以返回true函数接收一个参数node目标节点实例
beforeAssociativeLineConnection: null,
// 【TouchEvent插件】
// 禁止双指缩放你仍旧可以使用api进行缩放
@@ -403,11 +437,26 @@ export const defaultOpt = {
transformRichTextOnEnterEdit: null,
// 可以传递一个函数即将结束富文本编辑前会执行该函数函数接收richText实例所以你可以在此时机更新quill文档数据
beforeHideRichTextEdit: null,
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
richTextEditFakeInPlace: false,
// 【OuterFrame】插件
outerFramePaddingX: 10,
outerFramePaddingY: 10
outerFramePaddingY: 10,
// 【Painter】插件
// 是否只格式刷节点手动设置的样式,不考虑节点通过主题的应用的样式
onlyPainterNodeCustomStyles: false,
// 【NodeImgAdjust】插件
// 拦截节点图片的删除点击节点图片上的删除按钮删除图片前会调用该函数如果函数返回true则取消删除
beforeDeleteNodeImg: null,
// 删除和调整两个按钮的大小
imgResizeBtnSize: 25,
// 最小允许缩放的尺寸,请传入>=0的数字
minImgResizeWidth: 50,
minImgResizeHeight: 50,
// 最大允许缩放的尺寸依据主题的配置即主题的imgMaxWidth和imgMaxHeight配置如果设置为false那么使用maxImgResizeWidth和maxImgResizeHeight选项
maxImgResizeWidthInheritTheme: false,
// 最大允许缩放的尺寸maxImgResizeWidthInheritTheme选项设置为false时生效不限制最大值可传递Infinity
maxImgResizeWidth: Infinity,
maxImgResizeHeight: Infinity
}

View File

@@ -18,6 +18,7 @@ class Command {
this.activeHistoryIndex = 0
// 注册快捷键
this.registerShortcutKeys()
this.originAddHistory = this.addHistory.bind(this)
this.addHistory = throttle(
this.addHistory,
this.mindMap.opt.addHistoryTime,
@@ -60,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

@@ -156,8 +156,14 @@ class Event extends EventEmitter {
// 判断是否是触控板
let isTouchPad = false
// mac、windows
if (e.wheelDeltaY === e.deltaY * -3 || Math.abs(e.wheelDeltaY) <= 10) {
isTouchPad = true
// if (e.wheelDeltaY === e.deltaY * -3 || Math.abs(e.wheelDeltaY) <= 10) {
// isTouchPad = true
// }
const { customCheckIsTouchPad } = this.mindMap.opt
if (typeof customCheckIsTouchPad === 'function') {
isTouchPad = customCheckIsTouchPad(e)
} else {
isTouchPad = Math.abs(e.deltaY) <= 10
}
this.emit('mousewheel', e, dirs, this, isTouchPad)
}

View File

@@ -33,10 +33,12 @@ import {
removeRichTextStyes,
formatGetNodeGeneralization,
sortNodeList,
throttle
throttle,
checkClipboardReadEnable,
isNodeNotNeedRenderData
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
import { lineStyleProps } from '../../theme/default'
import { CONSTANTS, ERROR_TYPES } from '../../constants/constant'
import { Polygon } from '@svgdotjs/svg.js'
@@ -92,15 +94,11 @@ class Render {
// 文本编辑框需要再bindEvent之前实例化否则单击事件只能触发隐藏文本编辑框而无法保存文本修改
this.textEdit = new TextEdit(this)
// 当前复制的数据
this.lastBeingCopyData = null
this.beingCopyData = null
this.beingPasteText = ''
this.beingPasteImgSize = 0
this.currentBeingPasteType = ''
// 节点高亮框
this.highlightBoxNode = null
this.highlightBoxNodeStyle = null
// 上一次节点激活数据
this.lastActiveNode = null
this.lastActiveNodeList = []
// 布局
this.setLayout()
@@ -114,24 +112,26 @@ 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.mindMap.richText) {
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
} else {
this.renderTree = data
}
this.renderTree = data || null
}
// 绑定事件
bindEvent() {
const {
openPerformance,
performanceConfig,
openRealtimeRenderOnNodeTextEdit
} = this.mindMap.opt
// 画布点击事件清除当前激活节点列表
this.mindMap.on('draw_click', e => {
this.clearActiveNodeListOnDrawClick(e, 'click')
@@ -146,12 +146,6 @@ class Render {
this.setRootNodeCenter()
})
// 性能模式
this.performanceMode()
}
// 性能模式,懒加载节点
performanceMode() {
const { openPerformance, performanceConfig } = this.mindMap.opt
const onViewDataChange = throttle(() => {
if (this.root) {
this.mindMap.emit('node_tree_render_start')
@@ -164,24 +158,56 @@ class Render {
)
}
}, performanceConfig.time)
let lastOpen = false
this.mindMap.on('before_update_config', opt => {
lastOpen = opt.openPerformance
})
this.mindMap.on('after_update_config', opt => {
if (opt.openPerformance && !lastOpen) {
// 动态开启性能模式
this.mindMap.on('view_data_change', onViewDataChange)
if (openPerformance) {
this.mindMap.on('view_data_change', onViewDataChange)
}
// 文本编辑时实时更新节点大小
this.onNodeTextEditChange = this.onNodeTextEditChange.bind(this)
if (openRealtimeRenderOnNodeTextEdit) {
this.mindMap.on('node_text_edit_change', this.onNodeTextEditChange)
}
// 监听配置改变事件
this.mindMap.on('after_update_config', (opt, lastOpt) => {
// 更新openPerformance配置
if (opt.openPerformance !== lastOpt.openPerformance) {
this.mindMap[opt.openPerformance ? 'on' : 'off'](
'view_data_change',
onViewDataChange
)
this.forceLoadNode()
}
if (!opt.openPerformance && lastOpen) {
// 动态关闭性能模式
this.mindMap.off('view_data_change', onViewDataChange)
this.forceLoadNode()
// 更新openRealtimeRenderOnNodeTextEdit配置
if (
opt.openRealtimeRenderOnNodeTextEdit !==
lastOpt.openRealtimeRenderOnNodeTextEdit
) {
this.mindMap[opt.openRealtimeRenderOnNodeTextEdit ? 'on' : 'off'](
'node_text_edit_change',
this.onNodeTextEditChange
)
}
})
if (!openPerformance) return
this.mindMap.on('view_data_change', onViewDataChange)
// 处理非https下的复制黏贴问题
// 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡
// if (!checkClipboardReadEnable()) {
// this.handlePaste = this.handlePaste.bind(this)
// window.addEventListener('paste', this.handlePaste)
// this.mindMap.on('beforeDestroy', () => {
// window.removeEventListener('paste', this.handlePaste)
// })
// }
}
// 监听文本编辑事件,实时更新节点大小
onNodeTextEditChange({ node, text }) {
node._textData = node.createTextNode(text)
const { width, height } = node.getNodeRect()
node.width = width
node.height = height
node.layout()
this.mindMap.render(() => {
this.textEdit.updateTextEditNode()
})
}
// 强制渲染节点,不考虑是否在画布可视区域内
@@ -391,7 +417,7 @@ class Render {
this.mindMap.keyCommand.addShortcut('Control+Down', () => {
this.mindMap.execCommand('DOWN_NODE')
})
// 复制节点
// 复制节点
this.mindMap.keyCommand.addShortcut('Control+c', () => {
this.copy()
})
@@ -411,13 +437,11 @@ class Render {
// 派发节点激活事件
emitNodeActiveEvent(node = null, activeNodeList = [...this.activeNodeList]) {
let isChange = false
isChange = this.lastActiveNode !== node
if (!isChange) {
isChange = !checkNodeListIsEqual(this.lastActiveNodeList, activeNodeList)
}
const isChange = !checkNodeListIsEqual(
this.lastActiveNodeList,
activeNodeList
)
if (!isChange) return
this.lastActiveNode = node
this.lastActiveNodeList = [...activeNodeList]
this.mindMap.batchExecution.push('emitNodeActiveEvent', () => {
this.mindMap.emit('node_active', node, activeNodeList)
@@ -525,7 +549,7 @@ class Render {
}
// 触发一次保存,因为修改了渲染树的数据
if (
this.mindMap.richText &&
this.hasRichTextPlugin() &&
[CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)
) {
this.mindMap.command.addHistory()
@@ -539,7 +563,7 @@ class Render {
// 给当前被收起来的节点数据添加文本复位标志
resetUnExpandNodeStyle() {
if (!this.renderTree) return
if (!this.renderTree || !this.hasRichTextPlugin()) return
walk(this.renderTree, null, node => {
if (!node.data.expand) {
walk(node, null, node2 => {
@@ -716,7 +740,7 @@ class Render {
} = this.mindMap.opt
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
@@ -724,9 +748,9 @@ class Render {
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态
}
if (isRichText) params.resetRichText = isRichText
// 动态指定的子节点数据也需要添加相关属性
appointChildren = addDataToAppointNodes(appointChildren, {
...params
@@ -771,14 +795,14 @@ class Render {
}
this.textEdit.hideEditTextBox()
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode } = this.getNewNodeBehavior(false, true)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) params.resetRichText = isRichText
nodeList = addDataToAppointNodes(nodeList, params)
list.forEach(node => {
if (node.isGeneralization || node.isRoot) {
@@ -814,7 +838,7 @@ class Render {
} = this.mindMap.opt
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
@@ -822,9 +846,9 @@ class Render {
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) params.resetRichText = isRichText
// 动态指定的子节点数据也需要添加相关属性
appointChildren = addDataToAppointNodes(appointChildren, {
...params
@@ -871,14 +895,14 @@ class Render {
}
this.textEdit.hideEditTextBox()
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode } = this.getNewNodeBehavior(false, true)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) params.resetRichText = isRichText
childList = addDataToAppointNodes(childList, params)
list.forEach(node => {
if (node.isGeneralization) {
@@ -913,7 +937,7 @@ class Render {
} = this.mindMap.opt
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
@@ -921,9 +945,9 @@ class Render {
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) params.resetRichText = isRichText
list.forEach(node => {
if (node.isGeneralization || node.isRoot) {
return
@@ -942,9 +966,11 @@ class Render {
},
children: [node.nodeData]
}
node.setData({
resetRichText: true
})
if (isRichText) {
node.setData({
resetRichText: true
})
}
const parent = node.parent
// 获取当前节点所在位置
const index = getNodeDataIndex(node)
@@ -958,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
}
@@ -983,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
}
@@ -1034,7 +1062,7 @@ class Render {
}
})
// 如果是富文本,那么还要处理富文本内容
if (hasCustomStyles && this.mindMap.richText) {
if (hasCustomStyles && this.hasRichTextPlugin()) {
nodeData.resetRichText = true
nodeData.text = removeRichTextStyes(nodeData.text)
}
@@ -1104,140 +1132,150 @@ class Render {
})
}
// 非https下复制黏贴获取内容方法
handlePaste(event) {
const { disabledClipboard } = this.mindMap.opt
if (disabledClipboard) return
const clipboardData =
event.clipboardData || event.originalEvent.clipboardData
const items = clipboardData.items
let img = null
let text = ''
Array.from(items).forEach(item => {
if (item.type.indexOf('image') > -1) {
img = item.getAsFile()
}
if (item.type.indexOf('text') > -1) {
text = clipboardData.getData('text')
}
})
this.paste()
}
// 粘贴
async paste() {
const {
errorHandler,
handleIsSplitByWrapOnPasteCreateNewNode,
handleNodePasteImg,
disabledClipboard
disabledClipboard,
onlyPasteTextWhenHasImgAndText
} = this.mindMap.opt
// 读取剪贴板的文字和图片
let text = ''
let img = null
if (!disabledClipboard) {
// 如果支持剪贴板操作,那么以剪贴板数据为准
if (!disabledClipboard && checkClipboardReadEnable()) {
try {
const res = await getDataFromClipboard()
text = res.text || ''
img = res.img || null
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) {
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)
}
@@ -1293,7 +1331,7 @@ class Render {
// 如果是富文本模式,那么某些层级变化需要更新样式
checkNodeLayerChange(node, toNode, toNodeIsParent = false) {
if (this.mindMap.richText) {
if (this.hasRichTextPlugin()) {
// 如果设置了自定义样式那么不需要更新
if (this.mindMap.richText.checkNodeHasCustomRichTextStyle(node)) {
return
@@ -1512,6 +1550,8 @@ class Render {
return
}
this.activeNodeList.forEach(node => {
// 概要节点不允许添加下级节点
if (node.isGeneralization) return
node.setData({
expand: true
})
@@ -1520,7 +1560,7 @@ class Render {
const newData = simpleDeepClone(item)
createUidForAppointNodes([newData], true, node => {
// 可能跨层级复制,那么富文本样式需要更新
if (this.mindMap.richText) {
if (this.hasRichTextPlugin()) {
// 如果设置了自定义样式那么不需要更新
if (
this.mindMap.richText.checkNodeHasCustomRichTextStyle(node.data)
@@ -1539,28 +1579,32 @@ class Render {
// 设置节点样式
setNodeStyle(node, prop, value) {
let data = {
const data = {
[prop]: value
}
// 如果开启了富文本,则需要应用到富文本上
if (this.mindMap.richText) {
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.mindMap.richText) {
this.mindMap.richText.setNotActiveNodeStyle(node, style)
if (
this.hasRichTextPlugin() &&
this.mindMap.richText.isHasRichTextStyle(data)
) {
data.resetRichText = true
}
this.setNodeDataRender(node, data)
// 更新了连线的样式
@@ -1572,7 +1616,7 @@ class Render {
}
})
if (hasLineStyleProps) {
;(node.parent || node).renderLine(true)
(node.parent || node).renderLine(true)
}
}
@@ -1593,40 +1637,53 @@ class Render {
}
// 展开所有
expandAllNode() {
expandAllNode(uid = '') {
if (!this.renderTree) return
walk(
this.renderTree,
null,
node => {
if (!node.data.expand) {
node.data.expand = true
}
},
null,
true,
0,
0
)
const _walk = (node, enableExpand) => {
// 如果该节点为目标节点,那么修改允许展开的标志
if (!enableExpand && node.data.uid === uid) {
enableExpand = true
}
if (enableExpand && !node.data.expand) {
node.data.expand = true
}
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
_walk(child, enableExpand)
})
}
}
_walk(this.renderTree, !uid)
this.mindMap.render()
}
// 收起所有
unexpandAllNode(isSetRootNodeCenter = true) {
unexpandAllNode(isSetRootNodeCenter = true, uid = '') {
if (!this.renderTree) return
walk(
this.renderTree,
null,
(node, parent, isRoot) => {
if (!isRoot && node.children && node.children.length > 0) {
node.data.expand = false
}
},
null,
true,
0,
0
)
const _walk = (node, isRoot, enableUnExpand) => {
// 如果该节点为目标节点,那么修改允许展开的标志
if (!enableUnExpand && node.data.uid === uid) {
enableUnExpand = true
}
if (
enableUnExpand &&
!isRoot &&
node.children &&
node.children.length > 0
) {
node.data.expand = false
}
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
_walk(child, false, enableUnExpand)
})
}
}
_walk(this.renderTree, true, !uid)
this.mindMap.render(() => {
if (isSetRootNodeCenter) {
this.setRootNodeCenter()
@@ -1741,7 +1798,7 @@ class Render {
// 设置节点公式
insertFormula(formula, appointNodes = []) {
// 只在富文本模式下可用并且需要注册Formula插件
if (!this.mindMap.richText || !this.mindMap.formula) return
if (!this.hasRichTextPlugin() || !this.mindMap.formula) return
appointNodes = formatDataToArray(appointNodes)
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
list.forEach(node => {
@@ -1763,7 +1820,7 @@ class Render {
})
const list = parseAddGeneralizationNodeList(nodeList)
if (list.length <= 0) return
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
list.length > 1
@@ -1778,9 +1835,9 @@ class Render {
range: item.range || null,
uid: createUid(),
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) newData.resetRichText = isRichText
let generalization = item.node.getData('generalization')
generalization = generalization
? Array.isArray(generalization)
@@ -1910,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)
}
@@ -2035,19 +2096,39 @@ class Render {
}
// 高亮节点或子节点
highlightNode(node, range) {
highlightNode(node, range, style) {
// 如果当前正在渲染,那么不进行高亮,因为节点位置可能不正确
if (this.isRendering) return
const { highlightNodeBoxStyle = {} } = this.mindMap.opt
style = {
stroke: 'rgb(94, 200, 248)',
fill: 'transparent',
...(style || {})
}
// 尚未创建
if (!this.highlightBoxNode) {
this.highlightBoxNode = new Polygon()
.stroke({
color: highlightNodeBoxStyle.stroke || 'transparent'
color: style.stroke || 'transparent'
})
.fill({
color: highlightNodeBoxStyle.fill || 'transparent'
color: style.fill || 'transparent'
})
} else if (this.highlightBoxNodeStyle) {
// 样式更新了
if (
this.highlightBoxNodeStyle.stroke !== style.stroke ||
this.highlightBoxNodeStyle.fill !== style.fill
) {
this.highlightBoxNode
.stroke({
color: style.stroke || 'transparent'
})
.fill({
color: style.fill || 'transparent'
})
}
}
this.highlightBoxNodeStyle = { ...style }
let minx = Infinity,
miny = Infinity,
maxx = -Infinity,
@@ -2090,6 +2171,11 @@ class Render {
if (!this.highlightBoxNode) return
this.highlightBoxNode.remove()
}
// 是否存在富文本插件
hasRichTextPlugin() {
return !!this.mindMap.richText
}
}
export default Render

View File

@@ -6,9 +6,15 @@ import {
htmlEscape,
handleInputPasteText,
checkSmmFormatData,
getTextFromHtml
getTextFromHtml,
isWhite,
getVisibleColorFromTheme
} from '../../utils'
import { ERROR_TYPES, CONSTANTS } from '../../constants/constant'
import {
ERROR_TYPES,
CONSTANTS,
noneRichTextNodeLineHeight
} from '../../constants/constant'
// 节点文字编辑类
export default class TextEdit {
@@ -25,6 +31,8 @@ export default class TextEdit {
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
this.cacheEditingText = ''
this.hasBodyMousedown = false
this.textNodePaddingX = 5
this.textNodePaddingY = 3
this.bindEvent()
}
@@ -90,6 +98,32 @@ export default class TextEdit {
this.mindMap.on('beforeDestroy', () => {
this.unBindEvent()
})
this.mindMap.on('after_update_config', (opt, lastOpt) => {
if (
opt.openRealtimeRenderOnNodeTextEdit !==
lastOpt.openRealtimeRenderOnNodeTextEdit
) {
if (this.mindMap.richText) {
this.mindMap.richText.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
opt.openRealtimeRenderOnNodeTextEdit
)
} else {
this.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
opt.openRealtimeRenderOnNodeTextEdit
)
}
}
if (
opt.enableAutoEnterTextEditWhenKeydown !==
lastOpt.enableAutoEnterTextEditWhenKeydown
) {
window[
opt.enableAutoEnterTextEditWhenKeydown
? 'addEventListener'
: 'removeEventListener'
]('keydown', this.onKeydown)
}
})
}
// 解绑事件
@@ -99,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]
@@ -156,7 +191,8 @@ export default class TextEdit {
if (node.isUseCustomNodeContent()) {
return
}
const { beforeTextEdit } = this.mindMap.opt
const { beforeTextEdit, openRealtimeRenderOnNodeTextEdit } =
this.mindMap.opt
if (typeof beforeTextEdit === 'function') {
let isShow = false
try {
@@ -170,7 +206,12 @@ export default class TextEdit {
this.currentNode = node
const { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
this.mindMap.view.translateXY(offsetLeft, offsetTop)
const rect = node._textData.node.node.getBoundingClientRect()
const g = node._textData.node
const rect = g.node.getBoundingClientRect()
// 如果开启了大小实时更新,那么直接隐藏节点原文本
if (openRealtimeRenderOnNodeTextEdit) {
g.hide()
}
const params = {
node,
rect,
@@ -185,6 +226,21 @@ export default class TextEdit {
this.showEditTextBox(params)
}
// 当openRealtimeRenderOnNodeTextEdit配置更新后需要更新编辑框样式
onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
openRealtimeRenderOnNodeTextEdit
) {
if (!this.textEditNode) return
this.textEditNode.style.background = openRealtimeRenderOnNodeTextEdit
? 'transparent'
: this.currentNode
? this.getBackground(this.currentNode)
: ''
this.textEditNode.style.boxShadow = openRealtimeRenderOnNodeTextEdit
? 'none'
: '0 0 20px rgba(0,0,0,.5)'
}
// 处理画布缩放
onScale() {
const node = this.getCurrentEditNode()
@@ -206,15 +262,37 @@ export default class TextEdit {
// 显示文本编辑框
showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
if (this.showTextEdit) return
const { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText } =
this.mindMap.opt
const {
nodeTextEditZIndex,
textAutoWrapWidth,
selectTextOnEnterEditText,
openRealtimeRenderOnNodeTextEdit,
autoEmptyTextWhenKeydownEnterEdit
} = this.mindMap.opt
if (!isFromScale) {
this.mindMap.emit('before_show_text_edit')
}
this.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
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.classList.add(
CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP
)
this.textEditNode.style.cssText = `
position: fixed;
box-sizing: border-box;
${
openRealtimeRenderOnNodeTextEdit
? ''
: `box-shadow: 0 0 20px rgba(0,0,0,.5);`
}
padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;
margin-left: -${this.textNodePaddingX}px;
margin-top: -${this.textNodePaddingY}px;
outline: none;
word-break: break-all;
line-break: anywhere;
`
this.textEditNode.setAttribute('contenteditable', true)
this.textEditNode.addEventListener('keyup', e => {
e.stopPropagation()
@@ -240,32 +318,49 @@ export default class TextEdit {
handleInputPasteText(e)
}
})
this.textEditNode.addEventListener('input', () => {
this.mindMap.emit('node_text_edit_change', {
node: this.currentNode,
text: this.getEditText(),
richText: false
})
})
const targetNode =
this.mindMap.opt.customInnerElsAppendTo || document.body
targetNode.appendChild(this.textEditNode)
}
let scale = this.mindMap.view.scale
let lineHeight = node.style.merge('lineHeight')
let fontSize = node.style.merge('fontSize')
let textLines = (this.cacheEditingText || node.getData('text'))
const scale = this.mindMap.view.scale
const fontSize = node.style.merge('fontSize')
const textLines = (this.cacheEditingText || node.getData('text'))
.split(/\n/gim)
.map(item => {
return htmlEscape(item)
})
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
node.style.domText(this.textEditNode, scale, isMultiLine)
const isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
node.style.domText(this.textEditNode, scale)
if (!openRealtimeRenderOnNodeTextEdit) {
this.textEditNode.style.background = this.getBackground(node)
}
this.textEditNode.style.zIndex = nodeTextEditZIndex
this.textEditNode.innerHTML = textLines.join('<br>')
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
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'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
this.textEditNode.style.maxWidth = textAutoWrapWidth * scale + 'px'
if (isMultiLine && lineHeight !== 1) {
if (isMultiLine) {
this.textEditNode.style.lineHeight = noneRichTextNodeLineHeight
this.textEditNode.style.transform = `translateY(${
-((lineHeight * fontSize - fontSize) / 2) * scale
(((noneRichTextNodeLineHeight - 1) * fontSize) / 2) * scale
}px)`
} else {
this.textEditNode.style.lineHeight = 'normal'
}
this.showTextEdit = true
// 选中文本
@@ -280,6 +375,45 @@ export default class TextEdit {
this.cacheEditingText = ''
}
// 更新文本编辑框的大小和位置
updateTextEditNode() {
if (this.mindMap.richText) {
this.mindMap.richText.updateTextEditNode()
return
}
if (!this.showTextEdit || !this.currentNode) {
return
}
const rect = this.currentNode._textData.node.node.getBoundingClientRect()
this.textEditNode.style.minWidth =
rect.width + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight =
rect.height + this.textNodePaddingY * 2 + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
}
// 获取编辑区域的背景填充
getBackground(node) {
const gradientStyle = node.style.merge('gradientStyle')
// 当前使用的是渐变色背景
if (gradientStyle) {
const startColor = node.style.merge('startColor')
const endColor = node.style.merge('endColor')
return `linear-gradient(to right, ${startColor}, ${endColor})`
} else {
// 单色背景
const bgColor = node.style.merge('fillColor')
const color = node.style.merge('color')
// 默认使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
return bgColor === 'transparent'
? isWhite(color)
? getVisibleColorFromTheme(this.mindMap.themeConfig)
: '#fff'
: bgColor
}
}
// 删除文本编辑元素
removeTextEditEl() {
if (this.mindMap.richText) {
@@ -304,21 +438,8 @@ export default class TextEdit {
if (!this.showTextEdit) {
return
}
this.renderer.activeNodeList.forEach(node => {
let str = this.getEditText()
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
if (node.isGeneralization) {
// 概要节点
node.generalizationBelongNode.updateGeneralization()
}
this.mindMap.render()
})
this.mindMap.emit(
'hide_text_edit',
this.textEditNode,
this.renderer.activeNodeList,
this.currentNode
)
const currentNode = this.currentNode
const text = this.getEditText()
this.currentNode = null
this.textEditNode.style.display = 'none'
this.textEditNode.innerHTML = ''
@@ -327,6 +448,18 @@ export default class TextEdit {
this.textEditNode.style.fontWeight = 'normal'
this.textEditNode.style.transform = 'translateY(0)'
this.showTextEdit = false
this.mindMap.execCommand('SET_NODE_TEXT', currentNode, text)
// if (currentNode.isGeneralization) {
// // 概要节点
// currentNode.generalizationBelongNode.updateGeneralization()
// }
this.mindMap.render()
this.mindMap.emit(
'hide_text_edit',
this.textEditNode,
this.renderer.activeNodeList,
currentNode
)
}
// 获取当前正在编辑中的节点实例

View File

@@ -1,11 +1,12 @@
import Style from './Style'
import Shape from './Shape'
import { G, Rect, Text } from '@svgdotjs/svg.js'
import { G, Rect, Text, SVG } from '@svgdotjs/svg.js'
import nodeGeneralizationMethods from './nodeGeneralization'
import nodeExpandBtnMethods from './nodeExpandBtn'
import nodeCommandWrapsMethods from './nodeCommandWraps'
import nodeCreateContentsMethods from './nodeCreateContents'
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
import nodeModifyWidthMethods from './nodeModifyWidth'
import nodeCooperateMethods from './nodeCooperate'
import { CONSTANTS } from '../../../constants/constant'
import {
@@ -22,6 +23,8 @@ class MindMapNode {
this.opt = opt
// 节点数据
this.nodeData = this.handleData(opt.data || {})
// 保存本次更新时的节点数据快照
this.nodeDataSnapshot = ''
// uid
this.uid = opt.uid
// 控制实例
@@ -34,6 +37,8 @@ class MindMapNode {
this.lineDraw = this.mindMap.lineDraw
// 样式实例
this.style = new Style(this)
// 节点当前生效的全部样式
this.effectiveStyles = {}
// 形状实例
this.shapeInstance = new Shape(this)
this.shapePadding = {
@@ -52,6 +57,8 @@ class MindMapNode {
this.width = opt.width || 0
// 节点高
this.height = opt.height || 0
// 自定义文本的宽度
this.customTextWidth = opt.data.data.customTextWidth || undefined
// left
this._left = opt.left || 0
// top
@@ -82,7 +89,6 @@ class MindMapNode {
this.noteEl = null
this.noteContentIsShow = false
this._attachmentData = null
this._numberData = null
this._prefixData = null
this._postfixData = null
this._expandBtn = null
@@ -106,8 +112,6 @@ class MindMapNode {
// 概要节点的宽高
this._generalizationNodeWidth = 0
this._generalizationNodeHeight = 0
// 编号字符
this.number = opt.number || ''
// 各种文字信息的间距
this.textContentItemMargin = this.mindMap.opt.textContentMargin
// 图片和文字节点的间距
@@ -148,10 +152,15 @@ class MindMapNode {
proto[item] = nodeCooperateMethods[item]
})
}
// 拖拽调整节点宽度
Object.keys(nodeModifyWidthMethods).forEach(item => {
proto[item] = nodeModifyWidthMethods[item]
})
proto.bindEvent = true
}
// 初始化
this.getSize()
this.initDragHandle()
}
// 支持自定义位置
@@ -195,15 +204,50 @@ class MindMapNode {
}
// 创建节点的各个内容对象数据
createNodeData() {
// recreateTypes[] custom、image、icon、text、hyperlink、tag、note、attachment、numbers、prefix、postfix、checkbox
createNodeData(recreateTypes) {
// 自定义节点内容
let {
const {
isUseCustomNodeContent,
customCreateNodeContent,
createNodePrefixContent,
createNodePostfixContent
} = this.mindMap.opt
if (isUseCustomNodeContent && customCreateNodeContent) {
// 需要创建的内容类型
const typeList = [
'custom',
'image',
'icon',
'text',
'hyperlink',
'tag',
'note',
'attachment',
'prefix',
'postfix',
...this.mindMap.nodeInnerPrefixList.map(item => {
return item.name
})
]
const createTypes = {}
if (Array.isArray(recreateTypes)) {
// 重新创建指定的内容类型
typeList.forEach(item => {
if (recreateTypes.includes(item)) {
createTypes[item] = true
}
})
} else {
// 创建所有类型
typeList.forEach(item => {
createTypes[item] = true
})
}
if (
isUseCustomNodeContent &&
customCreateNodeContent &&
createTypes.custom
) {
this._customNodeContent = customCreateNodeContent(this)
}
// 如果没有返回内容,那么还是使用内置的节点内容
@@ -211,40 +255,51 @@ class MindMapNode {
addXmlns(this._customNodeContent)
return
}
this._imgData = this.createImgNode()
this._iconData = this.createIconNode()
this._textData = this.createTextNode()
this._hyperlinkData = this.createHyperlinkNode()
this._tagData = this.createTagNode()
this._noteData = this.createNoteNode()
this._attachmentData = this.createAttachmentNode()
if (this.mindMap.numbers) {
this._numberData = this.mindMap.numbers.createNumberContent(this)
if (createTypes.image) this._imgData = this.createImgNode()
if (createTypes.icon) this._iconData = this.createIconNode()
if (createTypes.text) this._textData = this.createTextNode()
if (createTypes.hyperlink) this._hyperlinkData = this.createHyperlinkNode()
if (createTypes.tag) this._tagData = this.createTagNode()
if (createTypes.note) this._noteData = this.createNoteNode()
if (createTypes.attachment)
this._attachmentData = this.createAttachmentNode()
this.mindMap.nodeInnerPrefixList.forEach(item => {
if (createTypes[item.name]) {
this[`_${item.name}Data`] = item.createContent(this)
}
})
if (createTypes.prefix) {
this._prefixData = createNodePrefixContent
? createNodePrefixContent(this)
: null
if (this._prefixData && this._prefixData.el) {
addXmlns(this._prefixData.el)
}
}
this._prefixData = createNodePrefixContent
? createNodePrefixContent(this)
: null
if (this._prefixData && this._prefixData.el) {
addXmlns(this._prefixData.el)
}
this._postfixData = createNodePostfixContent
? createNodePostfixContent(this)
: null
if (this._postfixData && this._postfixData.el) {
addXmlns(this._postfixData.el)
if (createTypes.postfix) {
this._postfixData = createNodePostfixContent
? createNodePostfixContent(this)
: null
if (this._postfixData && this._postfixData.el) {
addXmlns(this._postfixData.el)
}
}
}
// 计算节点的宽高
getSize() {
getSize(recreateTypes, opt = {}) {
const ignoreUpdateCustomTextWidth = opt.ignoreUpdateCustomTextWidth || false
if (!ignoreUpdateCustomTextWidth) {
this.customTextWidth = this.getData('customTextWidth') || undefined
}
this.customLeft = this.getData('customLeft') || undefined
this.customTop = this.getData('customTop') || undefined
// 这里不要更新概要,不然即使概要没修改,每次也会重新渲染
// this.updateGeneralization()
this.createNodeData()
let { width, height } = this.getNodeRect()
this.createNodeData(recreateTypes)
const { width, height } = this.getNodeRect()
// 判断节点尺寸是否有变化
let changed = this.width !== width || this.height !== height
const changed = this.width !== width || this.height !== height
this.width = width
this.height = height
return changed
@@ -254,9 +309,9 @@ class MindMapNode {
getNodeRect() {
// 自定义节点内容
if (this.isUseCustomNodeContent()) {
let rect = this.measureCustomNodeContentSize(this._customNodeContent)
const rect = this.measureCustomNodeContentSize(this._customNodeContent)
return {
width: rect.width,
width: this.hasCustomWidth() ? this.customTextWidth : rect.width,
height: rect.height
}
}
@@ -274,11 +329,14 @@ class MindMapNode {
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height
}
// 编号内容
if (this._numberData) {
textContentWidth += this._numberData.width
textContentHeight = Math.max(textContentHeight, this._numberData.height)
}
// 库前置内容
this.mindMap.nodeInnerPrefixList.forEach(item => {
const itemData = this[`_${item.name}Data`]
if (itemData) {
textContentWidth += itemData.width
textContentHeight = Math.max(textContentHeight, itemData.height)
}
})
// 自定义前置内容
if (this._prefixData) {
textContentWidth += this._prefixData.width
@@ -347,7 +405,7 @@ class MindMapNode {
imgContentHeight > 0 && textContentHeight > 0
? this.blockContentMargin
: 0
let { paddingX, paddingY } = this.getPaddingVale()
const { paddingX, paddingY } = this.getPaddingVale()
// 纯内容宽高
let _width = Math.max(imgContentWidth, textContentWidth)
let _height = imgContentHeight + textContentHeight
@@ -361,7 +419,7 @@ class MindMapNode {
_height += tagContentHeight
}
// 计算节点形状需要的附加内边距
let { paddingX: shapePaddingX, paddingY: shapePaddingY } =
const { paddingX: shapePaddingX, paddingY: shapePaddingY } =
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
this.shapePadding.paddingX = shapePaddingX
this.shapePadding.paddingY = shapePaddingY
@@ -378,7 +436,8 @@ class MindMapNode {
if (!this.group) return
// 清除之前的内容
this.group.clear()
const { hoverRectPadding, tagPosition } = this.mindMap.opt
const { hoverRectPadding, tagPosition, openRealtimeRenderOnNodeTextEdit } =
this.mindMap.opt
let { width, height, textContentItemMargin } = this
let { paddingY } = this.getPaddingVale()
const halfBorderWidth = this.getBorderWidth() / 2
@@ -430,14 +489,17 @@ class MindMapNode {
// 内容节点
let textContentNested = new G()
let textContentOffsetX = 0
// 编号内容
if (this._numberData) {
this._numberData.node
.x(textContentOffsetX)
.y((textContentHeight - this._numberData.height) / 2)
textContentNested.add(this._numberData.node)
textContentOffsetX += this._numberData.width + textContentItemMargin
}
// 库前置内容
this.mindMap.nodeInnerPrefixList.forEach(item => {
const itemData = this[`_${item.name}Data`]
if (itemData) {
itemData.node
.x(textContentOffsetX)
.y((textContentHeight - itemData.height) / 2)
textContentNested.add(itemData.node)
textContentOffsetX += itemData.width + textContentItemMargin
}
})
// 自定义前置内容
if (this._prefixData) {
const foreignObject = createForeignObjectNode({
@@ -474,6 +536,12 @@ class MindMapNode {
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
.x(textContentOffsetX)
.y((textContentHeight - this._textData.height) / 2)
// 如果开启了文本编辑实时渲染需要判断当前渲染的节点是否是正在编辑的节点是的话将透明度设置为0不显示
if (openRealtimeRenderOnNodeTextEdit) {
this._textData.node.opacity(
this.mindMap.renderer.textEdit.getCurrentEditNode() === this ? 0 : 1
)
}
textContentNested.add(this._textData.node)
textContentOffsetX += this._textData.width + textContentItemMargin
}
@@ -580,12 +648,15 @@ class MindMapNode {
this.active(e)
})
this.group.on('mousedown', e => {
e.preventDefault()
const {
readonly,
enableCtrlKeyNodeSelection,
useLeftKeySelectionRightKeyDrag
useLeftKeySelectionRightKeyDrag,
mousedownEventPreventDefault
} = this.mindMap.opt
if (mousedownEventPreventDefault) {
e.preventDefault()
}
// 只读模式不需要阻止冒泡
if (!readonly) {
if (this.isRoot) {
@@ -603,7 +674,7 @@ class MindMapNode {
// 多选和取消多选
if (!readonly && (e.ctrlKey || e.metaKey) && enableCtrlKeyNodeSelection) {
this.isMultipleChoice = true
let isActive = this.getData('isActive')
const isActive = this.getData('isActive')
if (!isActive)
this.mindMap.emit(
'before_node_active',
@@ -722,7 +793,7 @@ class MindMapNode {
this.renderExpandBtn()
}
} else {
let { isActive, expand } = this.getData()
const { isActive, expand } = this.getData()
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
if (childrenLength <= 0) {
this.removeExpandBtn()
@@ -733,23 +804,28 @@ class MindMapNode {
}
}
}
// 更新拖拽手柄的显示与否
this.updateDragHandle()
// 更新概要
this.renderGeneralization(forceRender)
// 更新协同头像
if (this.updateUserListNode) this.updateUserListNode()
// 更新节点位置
let t = this.group.transform()
// 如果节点位置没有变化,则返回
if (this.left === t.translateX && this.top === t.translateY) return
this.group.translate(this.left - t.translateX, this.top - t.translateY)
const t = this.group.transform()
// 保存一份当前节点数据快照
this.nodeDataSnapshot = JSON.stringify(this.getData())
// 节点位置变化才更新,因为即使值没有变化属性设置操作也是耗时的
if (this.left !== t.translateX || this.top !== t.translateY) {
this.group.translate(this.left - t.translateX, this.top - t.translateY)
}
}
// 获取节点相当于画布的位置
getNodePosInClient(_left, _top) {
let drawTransform = this.mindMap.draw.transform()
let { scaleX, scaleY, translateX, translateY } = drawTransform
let left = _left * scaleX + translateX
let top = _top * scaleY + translateY
const drawTransform = this.mindMap.draw.transform()
const { scaleX, scaleY, translateX, translateY } = drawTransform
const left = _left * scaleX + translateX
const top = _top * scaleY + translateY
return {
left,
top
@@ -768,8 +844,8 @@ class MindMapNode {
}
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
reRender() {
let sizeChange = this.getSize()
reRender(recreateTypes, opt) {
const sizeChange = this.getSize(recreateTypes, opt)
this.layout()
this.update()
return sizeChange
@@ -792,6 +868,7 @@ class MindMapNode {
this.hideExpandBtn()
}
this.updateNodeActiveClass()
this.updateDragHandle()
}
}
@@ -915,10 +992,10 @@ class MindMapNode {
// 隐藏节点
hide() {
this.group.hide()
if (this.group) this.group.hide()
this.hideGeneralization()
if (this.parent) {
let index = this.parent.children.indexOf(this)
const index = this.parent.children.indexOf(this)
this.parent._lines[index] && this.parent._lines[index].hide()
this._lines.forEach(item => {
item.hide()
@@ -940,7 +1017,7 @@ class MindMapNode {
this.group.show()
this.showGeneralization()
if (this.parent) {
let index = this.parent.children.indexOf(this)
const index = this.parent.children.indexOf(this)
this.parent._lines[index] && this.parent._lines[index].show()
this._lines.forEach(item => {
item.show()
@@ -958,7 +1035,7 @@ class MindMapNode {
// 包括连接线和下级节点
setOpacity(val) {
// 自身及连线
this.group.opacity(val)
if (this.group) this.group.opacity(val)
this._lines.forEach(line => {
line.opacity(val)
})
@@ -997,13 +1074,13 @@ class MindMapNode {
// 被拖拽中
startDrag() {
this.isDrag = true
this.group.addClass('smm-node-dragging')
if (this.group) this.group.addClass('smm-node-dragging')
}
// 拖拽结束
endDrag() {
this.isDrag = false
this.group.removeClass('smm-node-dragging')
if (this.group) this.group.removeClass('smm-node-dragging')
}
// 连线
@@ -1178,16 +1255,15 @@ class MindMapNode {
// 获取padding值
getPaddingVale() {
let { isActive } = this.getData()
return {
paddingX: this.getStyle('paddingX', true, isActive),
paddingY: this.getStyle('paddingY', true, isActive)
paddingX: this.getStyle('paddingX'),
paddingY: this.getStyle('paddingY')
}
}
// 获取某个样式
getStyle(prop, root) {
let v = this.style.merge(prop, root)
const v = this.style.merge(prop, root)
return v === undefined ? '' : v
}
@@ -1247,16 +1323,16 @@ class MindMapNode {
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置是相对于浏览器窗口左上角的位置
getRect() {
return this.group.rbox()
return this.group ? this.group.rbox() : null
}
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置信息相对于画布
getRectInSvg() {
let { scaleX, scaleY, translateX, translateY } =
const { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
let { left, top, width, height } = this
let right = (left + width) * scaleX + translateX
let bottom = (top + height) * scaleY + translateY
const right = (left + width) * scaleX + translateX
const bottom = (top + height) * scaleY + translateY
left = left * scaleX + translateX
top = top * scaleY + translateY
return {
@@ -1296,6 +1372,39 @@ class MindMapNode {
createSvgTextNode(text = '') {
return new Text().text(text)
}
// 获取SVG.js库的一些对象
getSvgObjects() {
return {
SVG,
G,
Rect
}
}
// 检查是否支持拖拽调整宽度
// 1.富文本模式
// 2.自定义节点内容
checkEnableDragModifyNodeWidth() {
const {
enableDragModifyNodeWidth,
isUseCustomNodeContent,
customCreateNodeContent
} = this.mindMap.opt
return (
enableDragModifyNodeWidth &&
(this.mindMap.richText ||
(isUseCustomNodeContent && customCreateNodeContent))
)
}
// 是否存在自定义宽度
hasCustomWidth() {
return (
this.checkEnableDragModifyNodeWidth() &&
this.customTextWidth !== undefined
)
}
}
export default MindMapNode

View File

@@ -1,6 +1,5 @@
import { checkIsNodeStyleDataKey } from '../../../utils/index'
const rootProp = ['paddingX', 'paddingY']
const backgroundStyleProps = [
'backgroundColor',
'backgroundImage',
@@ -13,6 +12,7 @@ const backgroundStyleProps = [
class Style {
// 设置背景样式
static setBackgroundStyle(el, themeConfig) {
if (!el) return
// 缓存容器元素原本的样式
if (!Style.cacheStyle) {
Style.cacheStyle = {}
@@ -62,10 +62,11 @@ class Style {
// 合并样式
merge(prop, root) {
let themeConfig = this.ctx.mindMap.themeConfig
// 三级及以下节点
let defaultConfig = themeConfig.node
if (root || rootProp.includes(prop)) {
// 直接使用最外层样式
let defaultConfig = null
let useRoot = false
if (root) {
// 使用最外层样式
useRoot = true
defaultConfig = themeConfig
} else if (this.ctx.isGeneralization) {
// 概要节点
@@ -76,11 +77,27 @@ class Style {
} else if (this.ctx.layerIndex === 1) {
// 二级节点
defaultConfig = themeConfig.second
} else {
// 三级及以下节点
defaultConfig = themeConfig.node
}
let value = ''
// 优先使用节点本身的样式
return this.getSelfStyle(prop) !== undefined
? this.getSelfStyle(prop)
: defaultConfig[prop]
if (this.getSelfStyle(prop) !== undefined) {
value = this.getSelfStyle(prop)
} else if (defaultConfig[prop] !== undefined) {
// 否则使用对应层级的样式
value = defaultConfig[prop]
} else {
// 否则使用最外层样式
value = themeConfig[prop]
}
if (!useRoot) {
this.addToEffectiveStyles({
[prop]: value
})
}
return value
}
// 获取某个样式值
@@ -93,6 +110,14 @@ class Style {
return this.ctx.getData(prop)
}
// 更新当前节点生效的样式数据
addToEffectiveStyles(styles) {
this.ctx.effectiveStyles = {
...this.ctx.effectiveStyles,
...styles
}
}
// 矩形
rect(node) {
this.shape(node)
@@ -101,18 +126,30 @@ class Style {
// 形状
shape(node) {
if (this.merge('gradientStyle')) {
const styles = {
gradientStyle: this.merge('gradientStyle'),
startColor: this.merge('startColor'),
endColor: this.merge('endColor'),
startDir: this.merge('startDir'),
endDir: this.merge('endDir'),
fillColor: this.merge('fillColor'),
borderColor: this.merge('borderColor'),
borderWidth: this.merge('borderWidth'),
borderDasharray: this.merge('borderDasharray')
}
if (styles.gradientStyle) {
if (!this._gradient) {
this._gradient = this.ctx.nodeDraw.gradient('linear')
}
this._gradient.update(add => {
add.stop(0, this.merge('startColor'))
add.stop(1, this.merge('endColor'))
add.stop(0, styles.startColor)
add.stop(1, styles.endColor)
})
this._gradient.from(...styles.startDir).to(...styles.endDir)
node.fill(this._gradient)
} else {
node.fill({
color: this.merge('fillColor')
color: styles.fillColor
})
}
// 节点使用横线样式,不需要渲染非激活状态的边框样式
@@ -125,56 +162,90 @@ class Style {
// return
// }
node.stroke({
color: this.merge('borderColor'),
width: this.merge('borderWidth'),
dasharray: this.merge('borderDasharray')
color: styles.borderColor,
width: styles.borderWidth,
dasharray: styles.borderDasharray
})
}
// 文字
text(node) {
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')
}
node
.fill({
color: this.merge('color')
color: styles.color
})
.css({
'font-family': this.merge('fontFamily'),
'font-size': this.merge('fontSize'),
'font-weight': this.merge('fontWeight'),
'font-style': this.merge('fontStyle'),
'text-decoration': this.merge('textDecoration')
'font-family': styles.fontFamily,
'font-size': styles.fontSize + 'px',
'font-weight': styles.fontWeight,
'font-style': styles.fontStyle,
'text-decoration': styles.textDecoration
})
}
// 生成内联样式
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'),
...customStyle
}
return `
color: ${this.merge('color')};
font-family: ${this.merge('fontFamily')};
font-size: ${this.merge('fontSize') + 'px'};
font-weight: ${this.merge('fontWeight')};
font-style: ${this.merge('fontStyle')};
text-decoration: ${this.merge('textDecoration')}
color: ${styles.color};
font-family: ${styles.fontFamily};
font-size: ${styles.fontSize + 'px'};
font-weight: ${styles.fontWeight};
font-style: ${styles.fontStyle};
text-decoration: ${styles.textDecoration}
`
}
// 获取文本样式
getTextFontStyle() {
return {
italic: this.merge('fontStyle') === 'italic',
bold: this.merge('fontWeight'),
const styles = {
color: this.merge('color'),
fontFamily: this.merge('fontFamily'),
fontSize: this.merge('fontSize'),
fontFamily: this.merge('fontFamily')
fontWeight: this.merge('fontWeight'),
fontStyle: this.merge('fontStyle'),
textDecoration: this.merge('textDecoration')
}
return {
italic: styles.fontStyle === 'italic',
bold: styles.fontWeight,
fontSize: styles.fontSize,
fontFamily: styles.fontFamily
}
}
// html文字节点
domText(node, fontSizeScale = 1, isMultiLine) {
node.style.fontFamily = this.merge('fontFamily')
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
node.style.fontWeight = this.merge('fontWeight') || 'normal'
node.style.lineHeight = !isMultiLine ? 'normal' : this.merge('lineHeight')
node.style.fontStyle = this.merge('fontStyle')
domText(node, fontSizeScale = 1) {
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')
}
node.style.color = styles.color
node.style.textDecoration = styles.textDecoration
node.style.fontFamily = styles.fontFamily
node.style.fontSize = styles.fontSize * fontSizeScale + 'px'
node.style.fontWeight = styles.fontWeight || 'normal'
node.style.fontStyle = styles.fontStyle
}
// 标签文字
@@ -207,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) {
@@ -269,7 +344,7 @@ class Style {
node2.fill({ color: color })
fillNode.fill({ color: fill })
if (this.ctx.mindMap.opt.isShowExpandNum) {
node.attr({ 'font-size': fontSize, 'font-color': fontColor })
node.attr({ 'font-size': fontSize + 'px', 'font-color': fontColor })
}
}
@@ -284,10 +359,23 @@ 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 } = this.ctx.mindMap.opt
node.radius(5).fill('none').stroke({
const hoverRectColor =
this.merge('hoverRectColor') || this.ctx.mindMap.opt.hoverRectColor
const hoverRectRadius = this.merge('hoverRectRadius')
node.radius(hoverRectRadius).fill('none').stroke({
color: hoverRectColor
})
}

View File

@@ -28,7 +28,7 @@ function createTextAvatar(item) {
color: '#fff'
})
.css({
'font-size': fontSize
'font-size': fontSize + 'px'
})
.dx(-fontSize / 2)
.dy((avatarSize - fontSize) / 2)

View File

@@ -1,5 +1,4 @@
import {
measureText,
resizeImgSize,
removeHtmlStyle,
addHtmlStyle,
@@ -11,7 +10,19 @@ import {
} from '../../../utils'
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
import iconsSvg from '../../../svg/icons'
import { CONSTANTS } from '../../../constants/constant'
import {
CONSTANTS,
noneRichTextNodeLineHeight
} from '../../../constants/constant'
// 测量svg文本宽高
const measureText = (text, style) => {
const g = new G()
const node = new Text().text(text)
style.text(node)
g.add(node)
return g.bbox()
}
// 标签默认的样式
const defaultTagStyle = {
@@ -113,9 +124,27 @@ 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() {
const { textAutoWrapWidth } = this.mindMap.opt
function createRichTextNode(specifyText) {
const hasCustomWidth = this.hasCustomWidth()
let text =
typeof specifyText === 'string' ? specifyText : this.getData('text')
let { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
textAutoWrapWidth = hasCustomWidth ? this.customTextWidth : textAutoWrapWidth
let g = new G()
// 重新设置富文本节点内容
let recoverText = false
@@ -125,26 +154,22 @@ function createRichTextNode() {
}
if ([CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
// 如果自定义过样式则不允许覆盖
if (!this.hasCustomStyle()) {
recoverText = true
}
// if (!this.hasCustomStyle() ) {
recoverText = true
// }
}
let text = this.getData('text')
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>`
@@ -153,7 +178,7 @@ function createRichTextNode() {
text: text
})
}
let html = `<div>${this.getData('text')}</div>`
let html = `<div>${text}</div>`
if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) {
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl =
document.createElement('div')
@@ -171,10 +196,15 @@ function createRichTextNode() {
el.classList.add('smm-richtext-node-wrap')
addXmlns(el)
el.style.maxWidth = textAutoWrapWidth + 'px'
if (hasCustomWidth) {
el.style.width = this.customTextWidth + 'px'
} else {
el.style.width = ''
}
let { width, height } = el.getBoundingClientRect()
// 如果文本为空,那么需要计算一个默认高度
if (height <= 0) {
div.innerHTML = '<p>abc123我和你</p>'
div.innerHTML = `<p>${emptyTextMeasureHeightText}</p>`
let elTmp = div.children[0]
elTmp.classList.add('smm-richtext-node-wrap')
height = elTmp.getBoundingClientRect().height
@@ -199,24 +229,25 @@ function createRichTextNode() {
}
// 创建文本节点
function createTextNode() {
function createTextNode(specifyText) {
if (this.getData('richText')) {
return this.createRichTextNode()
return this.createRichTextNode(specifyText)
}
const text =
typeof specifyText === 'string' ? specifyText : this.getData('text')
if (this.getData('resetRichText')) {
delete this.nodeData.data.resetRichText
}
let g = new G()
let fontSize = this.getStyle('fontSize', false)
let lineHeight = this.getStyle('lineHeight', false)
// 文本超长自动换行
let textStyle = this.style.getTextFontStyle()
let textArr = []
if (!isUndef(this.getData('text'))) {
textArr = String(this.getData('text')).split(/\n/gim)
if (!isUndef(text)) {
textArr = String(text).split(/\n/gim)
}
let maxWidth = this.mindMap.opt.textAutoWrapWidth
let isMultiLine = false
const { textAutoWrapWidth: maxWidth, emptyTextMeasureHeightText } =
this.mindMap.opt
let isMultiLine = textArr.length > 1
textArr.forEach((item, index) => {
let arr = item.split('')
let lines = []
@@ -224,7 +255,7 @@ function createTextNode() {
while (arr.length) {
let str = arr.shift()
let text = [...line, str].join('')
if (measureText(text, textStyle).width <= maxWidth) {
if (measureText(text, this.style).width <= maxWidth) {
line.push(str)
} else {
lines.push(line.join(''))
@@ -239,14 +270,30 @@ function createTextNode() {
}
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 * lineHeight * index)
node.y(
fontSize * noneRichTextNodeLineHeight * index +
((noneRichTextNodeLineHeight - 1) * fontSize) / 2
)
g.add(node)
})
let { width, height } = g.bbox()
// 如果文本为空,那么需要计算一个默认高度
if (height <= 0) {
const tmpNode = new Text().text(emptyTextMeasureHeightText)
this.style.text(tmpNode)
const tmpBbox = tmpNode.bbox()
height = tmpBbox.height
}
width = Math.min(Math.ceil(width), maxWidth)
height = Math.ceil(height)
g.attr('data-width', width)
@@ -423,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,
@@ -506,6 +556,7 @@ export default {
createImgNode,
getImgShowSize,
createIconNode,
tryAddHtmlStyle,
createRichTextNode,
createTextNode,
createHyperlinkNode,

View File

@@ -1,5 +1,6 @@
import btnsSvg from '../../../svg/btns'
import { SVG, Circle, G, Text } from '@svgdotjs/svg.js'
import { isUndef } from '../../../utils'
// 创建展开收起按钮的内容节点
function createExpandNodeContent() {
@@ -78,7 +79,12 @@ function updateExpandBtnNode() {
})
// 计算子节点数量
let count = this.sumNode(this.nodeData.children)
count = expandBtnNumHandler(count)
if (typeof expandBtnNumHandler === 'function') {
const res = expandBtnNumHandler(count, this)
if (!isUndef(res)) {
count = res
}
}
node.text(String(count))
} else {
this._fillExpandNode.stroke('none')

View File

@@ -186,15 +186,29 @@ function handleGeneralizationMouseenter() {
const list = belongNode.formatGetGeneralization()
const index = belongNode.getGeneralizationNodeIndex(this)
const generalizationData = list[index]
// 如果主题中设置了hoverRectColor颜色那么使用该颜色
// 否则使用hoverRectColor实例化选项的颜色
// 兜底使用highlightNode方法的默认颜色
const hoverRectColor = this.getStyle('hoverRectColor')
const color = hoverRectColor || this.mindMap.opt.hoverRectColor
const style = color
? {
stroke: color
}
: null
// 区间概要,框子节点
if (
Array.isArray(generalizationData.range) &&
generalizationData.range.length > 0
) {
this.mindMap.renderer.highlightNode(belongNode, generalizationData.range)
this.mindMap.renderer.highlightNode(
belongNode,
generalizationData.range,
style
)
} else {
// 否则框自己
this.mindMap.renderer.highlightNode(belongNode)
this.mindMap.renderer.highlightNode(belongNode, null, style)
}
}

View File

@@ -0,0 +1,153 @@
import { Rect } from '@svgdotjs/svg.js'
// 初始化拖拽
function initDragHandle() {
if (!this.checkEnableDragModifyNodeWidth()) {
return
}
// 拖拽手柄元素
this._dragHandleNodes = null
// 手柄元素的宽度
this.dragHandleWidth = 4
// 鼠标按下时的x坐标
this.dragHandleMousedownX = 0
// 鼠标是否处于按下状态
this.isDragHandleMousedown = false
// 当前拖拽的手柄序号
this.dragHandleIndex = 0
// 鼠标按下时记录当前的customTextWidth值
this.dragHandleMousedownCustomTextWidth = 0
// 鼠标按下时记录当前的手型样式
this.dragHandleMousedownBodyCursor = ''
// 鼠标按下时记录当前节点的left值
this.dragHandleMousedownLeft = 0
this.onDragMousemoveHandle = this.onDragMousemoveHandle.bind(this)
window.addEventListener('mousemove', this.onDragMousemoveHandle)
this.onDragMouseupHandle = this.onDragMouseupHandle.bind(this)
window.addEventListener('mouseup', this.onDragMouseupHandle)
this.mindMap.on('node_mouseup', this.onDragMouseupHandle)
}
// 鼠标移动事件
function onDragMousemoveHandle(e) {
if (!this.isDragHandleMousedown) return
e.stopPropagation()
e.preventDefault()
let {
minNodeTextModifyWidth,
maxNodeTextModifyWidth,
isUseCustomNodeContent,
customCreateNodeContent
} = this.mindMap.opt
const useCustomContent =
isUseCustomNodeContent && customCreateNodeContent && this._customNodeContent
document.body.style.cursor = 'ew-resize'
this.group.css({
cursor: 'ew-resize'
})
const { scaleX } = this.mindMap.draw.transform()
const ox = e.clientX - this.dragHandleMousedownX
let newWidth =
this.dragHandleMousedownCustomTextWidth +
(this.dragHandleIndex === 0 ? -ox : ox) / scaleX
newWidth = Math.max(newWidth, minNodeTextModifyWidth)
if (maxNodeTextModifyWidth !== -1) {
newWidth = Math.min(newWidth, maxNodeTextModifyWidth)
}
// 如果存在图片,那么最小值需要考虑图片宽度
if (!useCustomContent && this.getData('image')) {
const imgSize = this.getImgShowSize()
if (
this._rectInfo.textContentWidth - this.customTextWidth + newWidth <=
imgSize[0]
) {
newWidth =
imgSize[0] + this.customTextWidth - this._rectInfo.textContentWidth
}
}
this.customTextWidth = newWidth
if (this.dragHandleIndex === 0) {
this.left = this.dragHandleMousedownLeft + ox / scaleX
}
// 自定义内容不重新渲染,交给开发者
this.reRender(useCustomContent ? [] : ['text'], {
ignoreUpdateCustomTextWidth: true
})
}
// 鼠标松开事件
function onDragMouseupHandle() {
if (!this.isDragHandleMousedown) return
document.body.style.cursor = this.dragHandleMousedownBodyCursor
this.group.css({
cursor: 'default'
})
this.isDragHandleMousedown = false
this.dragHandleMousedownX = 0
this.dragHandleIndex = 0
this.dragHandleMousedownCustomTextWidth = 0
this.setData({
customTextWidth: this.customTextWidth
})
this.mindMap.render()
this.mindMap.emit('dragModifyNodeWidthEnd', this)
}
// 插件拖拽手柄元素
function createDragHandleNode() {
const list = [new Rect(), new Rect()]
list.forEach((node, index) => {
node
.size(this.dragHandleWidth, this.height)
.fill({
color: 'transparent'
})
.css({
cursor: 'ew-resize'
})
node.on('mousedown', e => {
e.stopPropagation()
e.preventDefault()
this.dragHandleMousedownX = e.clientX
this.dragHandleIndex = index
this.dragHandleMousedownCustomTextWidth =
this.customTextWidth === undefined
? this._textData
? this._textData.width
: this.width
: this.customTextWidth
this.dragHandleMousedownBodyCursor = document.body.style.cursor
this.dragHandleMousedownLeft = this.left
this.isDragHandleMousedown = true
})
})
return list
}
// 更新拖拽按钮的显隐和位置尺寸
function updateDragHandle() {
if (!this.checkEnableDragModifyNodeWidth()) return
if (!this._dragHandleNodes) {
this._dragHandleNodes = this.createDragHandleNode()
}
if (this.getData('isActive')) {
this._dragHandleNodes.forEach(node => {
node.height(this.height)
this.group.add(node)
})
this._dragHandleNodes[1].x(this.width - this.dragHandleWidth)
} else {
this._dragHandleNodes.forEach(node => {
node.remove()
})
}
}
export default {
initDragHandle,
onDragMousemoveHandle,
onDragMouseupHandle,
createDragHandleNode,
updateDragHandle
}

View File

@@ -30,8 +30,11 @@ class View {
})
// 拖动视图
this.mindMap.event.on('mousedown', e => {
if (this.mindMap.opt.isDisableDrag) return
e.preventDefault()
const { isDisableDrag, mousedownEventPreventDefault } = this.mindMap.opt
if (isDisableDrag) return
if (mousedownEventPreventDefault) {
e.preventDefault()
}
this.sx = this.x
this.sy = this.y
})
@@ -63,7 +66,8 @@ class View {
mouseScaleCenterUseMousePosition,
mousewheelMoveStep,
mousewheelZoomActionReverse,
disableMouseWheelZoom
disableMouseWheelZoom,
translateRatio
} = this.mindMap.opt
// 是否自定义鼠标滚轮事件
if (
@@ -111,26 +115,34 @@ class View {
}
} else {
// 2.鼠标滚轮事件控制画布移动
const step = isTouchPad ? 10 : mousewheelMoveStep
let stepX = 0
let stepY = 0
if (isTouchPad) {
// 如果是触控板,那么直接使用触控板滑动距离
stepX = Math.abs(e.wheelDeltaX)
stepY = Math.abs(e.wheelDeltaY)
} else {
stepX = stepY = mousewheelMoveStep
}
let mx = 0
let my = 0
// 上移
if (dirs.includes(CONSTANTS.DIR.DOWN)) {
my = -step
my = -stepY
}
// 下移
if (dirs.includes(CONSTANTS.DIR.UP)) {
my = step
my = stepY
}
// 右移
if (dirs.includes(CONSTANTS.DIR.LEFT)) {
mx = step
mx = stepX
}
// 左移
if (dirs.includes(CONSTANTS.DIR.RIGHT)) {
mx = -step
mx = -stepX
}
this.translateXY(mx, my)
this.translateXY(mx * translateRatio, my * translateRatio)
}
})
this.mindMap.on('resize', () => {
@@ -238,8 +250,9 @@ class View {
// 缩小
narrow(cx, cy, isTouchPad) {
const scaleRatio = this.mindMap.opt.scaleRatio / (isTouchPad ? 5 : 1)
const scale = Math.max(this.scale - scaleRatio, 0.1)
let { scaleRatio, minZoomRatio } = this.mindMap.opt
scaleRatio = scaleRatio / (isTouchPad ? 5 : 1)
const scale = Math.max(this.scale - scaleRatio, minZoomRatio / 100)
this.scaleInCenter(scale, cx, cy)
this.transform()
this.emitEvent('scale')
@@ -247,8 +260,14 @@ class View {
// 放大
enlarge(cx, cy, isTouchPad) {
const scaleRatio = this.mindMap.opt.scaleRatio / (isTouchPad ? 5 : 1)
const scale = this.scale + scaleRatio
let { scaleRatio, maxZoomRatio } = this.mindMap.opt
scaleRatio = scaleRatio / (isTouchPad ? 5 : 1)
let scale = 0
if (maxZoomRatio === -1) {
scale = this.scale + scaleRatio
} else {
scale = Math.min(this.scale + scaleRatio, maxZoomRatio / 100)
}
this.scaleInCenter(scale, cx, cy)
this.transform()
this.emitEvent('scale')
@@ -335,6 +354,10 @@ class View {
// 判断是否需要将思维导图限制在画布内
checkNeedMindMapInCanvas() {
// 如果当前在演示模式,那么不需要限制
if (this.mindMap.demonstrate && this.mindMap.demonstrate.isInDemonstrate) {
return false
}
const { isLimitMindMapInCanvasWhenHasScrollbar, isLimitMindMapInCanvas } =
this.mindMap.opt
// 如果注册了滚动条插件那么使用isLimitMindMapInCanvasWhenHasScrollbar配置

View File

@@ -69,43 +69,41 @@ class Base {
}
}
// 获取节点编号信息
getNumberInfo({ parent, ancestors, layerIndex, index }) {
// 编号
const hasNumberPlugin = !!this.mindMap.numbers
const parentNumberStr =
hasNumberPlugin && parent && parent._node.number
? parent._node.number
: ''
const newNumberStr = hasNumberPlugin
? this.mindMap.numbers.getNodeNumberStr({
ancestors,
layerIndex,
num: index + 1,
parentNumberStr
})
: ''
return {
hasNumberPlugin,
newNumberStr
// 节点节点数据是否发生了改变
checkIsNodeDataChange(lastData, curData) {
if (lastData) {
// 对比忽略激活状态和展开收起状态
lastData = typeof lastData === 'string' ? JSON.parse(lastData) : lastData
lastData.isActive = curData.isActive
lastData.expand = curData.expand
lastData = JSON.stringify(lastData)
}
return lastData !== JSON.stringify(curData)
}
// 创建节点实例
createNode(data, parent, isRoot, layerIndex, index, ancestors) {
// 编号
const { hasNumberPlugin, newNumberStr } = this.getNumberInfo({
parent,
ancestors,
layerIndex,
index
})
// 创建节点
// 库前置内容数据
const nodeInnerPrefixData = {}
this.mindMap.nodeInnerPrefixList.forEach(item => {
if (item.createNodeData) {
const [key, value] = item.createNodeData({
data,
parent,
ancestors,
layerIndex,
index
})
nodeInnerPrefixData[key] = value
}
})
const uid = data.data.uid
let newNode = null
// 数据上保存了节点引用,那么直接复用节点
if (data && data._node && !this.renderer.reRender) {
newNode = data._node
// 节点层级改变了
const isLayerTypeChange = this.checkIsLayerTypeChange(
newNode.layerIndex,
layerIndex
@@ -119,25 +117,35 @@ class Base {
}
this.cacheNode(data._node.uid, newNode)
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
// 判断编号是否改变
let isNumberChange = false
if (hasNumberPlugin) {
isNumberChange = this.mindMap.numbers.updateNumber(
newNode,
newNumberStr
)
}
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
// 库前置内容是否改变
let isNodeInnerPrefixChange = false
this.mindMap.nodeInnerPrefixList.forEach(item => {
if (item.updateNodeData) {
const isChange = item.updateNodeData(newNode, nodeInnerPrefixData)
if (isChange) {
isNodeInnerPrefixChange = isChange
}
}
})
// 主题或主题配置改变了
const isResizeSource = this.checkIsNeedResizeSources()
// 节点数据改变了
const isNodeDataChange = this.checkIsNodeDataChange(
data._node.nodeDataSnapshot,
data.data
)
// 重新计算节点大小和布局
if (
this.checkIsNeedResizeSources() ||
isResizeSource ||
isNodeDataChange ||
isLayerTypeChange ||
newNode.getData('resetRichText') ||
isNumberChange
isNodeInnerPrefixChange
) {
newNode.getSize()
newNode.needLayout = true
}
this.checkGetGeneralizationChange(newNode)
this.checkGetGeneralizationChange(newNode, isResizeSource)
} else if (
(this.lru.has(uid) || this.renderer.lastNodeCache[uid]) &&
!this.renderer.reRender
@@ -149,6 +157,7 @@ class Base {
newNode = this.lru.get(uid) || this.renderer.lastNodeCache[uid]
// 保存该节点上一次的数据
const lastData = JSON.stringify(newNode.getData())
// 节点层级改变了
const isLayerTypeChange = this.checkIsLayerTypeChange(
newNode.layerIndex,
layerIndex
@@ -166,27 +175,30 @@ class Base {
data._node = newNode
// 主题或主题配置改变了需要重新计算节点大小和布局
const isResizeSource = this.checkIsNeedResizeSources()
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本,节点数据改变了等情况需要重新计算节点大小和布局
const isNodeDataChange = lastData !== JSON.stringify(data.data)
// 判断编号是否改变
let isNumberChange = false
if (hasNumberPlugin) {
isNumberChange = this.mindMap.numbers.updateNumber(
newNode,
newNumberStr
)
}
// 点数据改变了
const isNodeDataChange = this.checkIsNodeDataChange(lastData, data.data)
// 库前置内容是否改变
let isNodeInnerPrefixChange = false
this.mindMap.nodeInnerPrefixList.forEach(item => {
if (item.updateNodeData) {
const isChange = item.updateNodeData(newNode, nodeInnerPrefixData)
if (isChange) {
isNodeInnerPrefixChange = isChange
}
}
})
// 重新计算节点大小和布局
if (
isResizeSource ||
isNodeDataChange ||
isLayerTypeChange ||
newNode.getData('resetRichText') ||
isNumberChange
isNodeInnerPrefixChange
) {
newNode.getSize()
newNode.needLayout = true
}
this.checkGetGeneralizationChange(newNode)
this.checkGetGeneralizationChange(newNode, isResizeSource)
} else {
// 创建新节点
const newUid = uid || createUid()
@@ -199,7 +211,7 @@ class Base {
layerIndex,
isRoot,
parent: !isRoot ? parent._node : null,
number: newNumberStr
...nodeInnerPrefixData
})
// uid保存到数据上为了节点复用
data.data.uid = newUid
@@ -228,7 +240,7 @@ class Base {
}
// 检查概要节点是否需要更新
checkGetGeneralizationChange(node) {
checkGetGeneralizationChange(node, isResizeSource) {
const generalizationList = node.getData('generalization')
if (
generalizationList &&
@@ -239,8 +251,13 @@ class Base {
const gNode = item.generalizationNode
const oldData = gNode.getData()
const newData = generalizationList[index]
if (newData && JSON.stringify(oldData) !== JSON.stringify(newData)) {
gNode.nodeData.data = newData
if (
isResizeSource ||
(newData && JSON.stringify(oldData) !== JSON.stringify(newData))
) {
if (newData) {
gNode.nodeData.data = newData
}
gNode.getSize()
gNode.needLayout = true
}
@@ -371,18 +388,32 @@ class Base {
}
// 二次贝塞尔曲线
quadraticCurvePath(x1, y1, x2, y2) {
let cx = x1 + (x2 - x1) * 0.2
let cy = y1 + (y2 - y1) * 0.8
quadraticCurvePath(x1, y1, x2, y2, v = false) {
let cx, cy
if (v) {
cx = x1 + (x2 - x1) * 0.8
cy = y1 + (y2 - y1) * 0.2
} else {
cx = x1 + (x2 - x1) * 0.2
cy = y1 + (y2 - y1) * 0.8
}
return `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
}
// 三次贝塞尔曲线
cubicBezierPath(x1, y1, x2, y2) {
let cx1 = x1 + (x2 - x1) / 2
let cy1 = y1
let cx2 = cx1
let cy2 = y2
cubicBezierPath(x1, y1, x2, y2, v = false) {
let cx1, cy1, cx2, cy2
if (v) {
cx1 = x1
cy1 = y1 + (y2 - y1) / 2
cx2 = x2
cy2 = cy1
} else {
cx1 = x1 + (x2 - x1) / 2
cy1 = y1
cx2 = cx1
cy2 = y2
}
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
}
@@ -421,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

@@ -34,7 +34,14 @@ class OrganizationStructure 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)
@@ -148,13 +155,56 @@ class OrganizationStructure extends Base {
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'direct') {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
// 曲线风格连线
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
const { alwaysShowExpandBtn, notShowExpandBtn } = this.mindMap.opt
if (!alwaysShowExpandBtn || notShowExpandBtn) {
expandBtnSize = 0
}
const {
nodeUseLineStyle,
rootLineStartPositionKeepSameInCurve,
rootLineKeepSameInCurve
} = this.mindMap.themeConfig
node.children.forEach((item, index) => {
if (node.layerIndex === 0) {
expandBtnSize = 0
}
let x1 = left + width / 2
let y1 =
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
? top + height / 2
: top + height + expandBtnSize
let x2 = item.left + item.width / 2
let y2 = item.top
let path = ''
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
if (node.isRoot && !rootLineKeepSameInCurve) {
path =
this.quadraticCurvePath(x1, y1, x2, y2, true) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2, true) + nodeUseLineStylePath
}
this.setLineStyle(style, lines[index], path, item)
})
}
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {

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)
}
// 取消创建关联线
@@ -446,6 +555,12 @@ class AssociativeLine {
// 完成创建连接线
completeCreateLine(node) {
if (this.creatingStartNode.uid === node.uid) return
const { beforeAssociativeLineConnection } = this.mindMap.opt
let stop = false
if (typeof beforeAssociativeLineConnection === 'function') {
stop = beforeAssociativeLineConnection(node)
}
if (stop) return
this.addLine(this.creatingStartNode, node)
if (this.overlapNode && this.overlapNode.getData('isActive')) {
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, false)
@@ -523,7 +638,8 @@ class AssociativeLine {
associativeLineTargets,
associativeLinePoint,
associativeLineTargetControlOffsets,
associativeLineText
associativeLineText,
associativeLineStyle
} = node.getData()
associativeLinePoint = associativeLinePoint || []
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
@@ -536,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) => {
@@ -552,7 +677,9 @@ class AssociativeLine {
})
: [],
// 文本
associativeLineText: newAssociativeLineText
associativeLineText: newAssociativeLineText,
// 样式
associativeLineStyle: newAssociativeLineStyle
})
}
@@ -572,6 +699,7 @@ class AssociativeLine {
this.activeLine = null
this.removeControls()
this.back()
this.mindMap.emit('associative_line_deactivate')
}
}

View File

@@ -93,7 +93,6 @@ class Drag extends Base {
) {
return
}
e.preventDefault()
this.isMousedown = true
// 记录鼠标按下时的节点
this.mousedownNode = node

View File

@@ -129,6 +129,7 @@ class Export {
// svg转png
svgToPng(svgSrc, transparent, clipData = null) {
const { maxCanvasSize, minExportImgCanvasScale } = this.mindMap.opt
return new Promise((resolve, reject) => {
const img = new Image()
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
@@ -136,10 +137,7 @@ class Export {
img.onload = async () => {
try {
const canvas = document.createElement('canvas')
const dpr = Math.max(
window.devicePixelRatio,
this.mindMap.opt.minExportImgCanvasScale
)
const dpr = Math.max(window.devicePixelRatio, minExportImgCanvasScale)
let imgWidth = img.width
let imgHeight = img.height
// 如果是裁减操作的话,那么需要手动添加内边距,及调整图片大小为实际的裁减区域的大小,不要忘了内边距哦
@@ -152,29 +150,40 @@ class Export {
imgHeight = clipData.height + paddingY * 2
}
// 检查是否超出canvas支持的像素上限
const maxSize = 16384 / dpr
const maxArea = maxSize * maxSize
if (imgWidth * imgHeight > maxArea) {
// canvas大小需要乘以dpr
let canvasWidth = imgWidth * dpr
let canvasHeight = imgHeight * dpr
if (canvasWidth > maxCanvasSize || canvasHeight > maxCanvasSize) {
let newWidth = null
let newHeight = null
if (imgWidth > maxSize) {
newWidth = maxArea / imgHeight
} else if (imgHeight > maxSize) {
newHeight = maxArea / imgWidth
if (canvasWidth > maxCanvasSize) {
// 如果宽度超出限制,那么调整为上限值
newWidth = maxCanvasSize
} else if (canvasHeight > maxCanvasSize) {
// 高度同理
newHeight = maxCanvasSize
}
const res = resizeImgSize(imgWidth, imgHeight, newWidth, newHeight)
imgWidth = res[0]
imgHeight = res[1]
// 计算缩放后的宽高
const res = resizeImgSize(
canvasWidth,
canvasHeight,
newWidth,
newHeight
)
canvasWidth = res[0]
canvasHeight = res[1]
}
canvas.width = imgWidth * dpr
canvas.height = imgHeight * dpr
canvas.style.width = imgWidth + 'px'
canvas.style.height = imgHeight + 'px'
canvas.width = canvasWidth
canvas.height = canvasHeight
const styleWidth = canvasWidth / dpr
const styleHeight = canvasHeight / dpr
canvas.style.width = styleWidth + 'px'
canvas.style.height = styleHeight + 'px'
const ctx = canvas.getContext('2d')
ctx.scale(dpr, dpr)
// 绘制背景
if (!transparent) {
await this.drawBackgroundToCanvas(ctx, imgWidth, imgHeight)
await this.drawBackgroundToCanvas(ctx, styleWidth, styleHeight)
}
// 图片绘制到canvas里
// 如果有裁减数据,那么需要进行裁减
@@ -191,7 +200,7 @@ class Export {
clipData.height
)
} else {
ctx.drawImage(img, 0, 0, imgWidth, imgHeight)
ctx.drawImage(img, 0, 0, styleWidth, styleHeight)
}
resolve(canvas.toDataURL())
} catch (error) {

View File

@@ -1,8 +1,11 @@
import katex from 'katex'
import Quill from 'quill'
import { getChromeVersion } from '../utils/index'
import { getChromeVersion, htmlEscape } from '../utils/index'
import { getBaseStyleText, getFontStyleText } from './FormulaStyle'
let extended = false
const QuillFormula = Quill.import('formats/formula')
// 数学公式支持插件
// 该插件在富文本模式下可用
class Formula {
@@ -16,6 +19,18 @@ class Formula {
this.cssEl = null
this.addStyle()
this.extendQuill()
this.onDestroy = this.onDestroy.bind(this)
this.mindMap.on('beforeDestroy', this.onDestroy)
}
onDestroy() {
const instanceCount = Object.getPrototypeOf(this.mindMap).constructor
.instanceCount
// 如果思维导图实例数量变成0了那么就恢复成默认的
if (instanceCount <= 1) {
extended = false
Quill.register('formats/formula', QuillFormula, true)
}
}
init() {
@@ -50,7 +65,9 @@ class Formula {
// 修改formula格式工具
extendQuill() {
const QuillFormula = Quill.import('formats/formula')
if (extended) return
extended = true
const self = this
class CustomFormulaBlot extends QuillFormula {
@@ -58,7 +75,7 @@ class Formula {
let node = super.create(value)
if (typeof value === 'string') {
katex.render(value, node, self.config)
node.setAttribute('data-value', value)
node.setAttribute('data-value', htmlEscape(value))
}
return node
}
@@ -110,11 +127,7 @@ class Formula {
for (const el of els)
nodeText = nodeText.replace(
el.outerHTML,
`\$${el
.getAttribute('data-value')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')}\$`
`\$${el.getAttribute('data-value')}\$`
)
}
return nodeText
@@ -172,11 +185,13 @@ class Formula {
// 插件被移除前做的事情
beforePluginRemove() {
this.removeStyle()
this.mindMap.off('beforeDestroy', this.onDestroy)
}
// 插件被卸载前做的事情
beforePluginDestroy() {
this.removeStyle()
this.mindMap.off('beforeDestroy', this.onDestroy)
}
}

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

@@ -6,13 +6,18 @@ class NodeImgAdjust {
// 构造函数
constructor({ mindMap }) {
this.mindMap = mindMap
this.resizeBtnSize = 26 // 调整按钮的大小
this.handleEl = null // 自定义元素,用来渲染临时图片、调整按钮
this.isShowHandleEl = false // 自定义元素是否在显示中
this.node = null // 当前节点实例
this.img = null // 当前节点的图片节点
this.rect = null // 当前图片节点的尺寸信息
this.isMousedown = false // 当前是否是按住调整按钮状态
this.mousedownDrawTransform = null //鼠标按下时对当前画布的变换
this.mousedownOffset = {
// 鼠标按下时位置和图片右下角相差的距离
x: 0,
y: 0
}
this.currentImgWidth = 0 // 当前拖拽实时图片的大小
this.currentImgHeight = 0
this.isAdjusted = false // 是否是拖拽结束后的渲染期间
@@ -78,6 +83,7 @@ class NodeImgAdjust {
// 显示自定义元素
showHandleEl() {
if (this.isShowHandleEl) return
if (!this.handleEl) {
this.createResizeBtnEl()
}
@@ -116,6 +122,7 @@ class NodeImgAdjust {
// 创建调整按钮元素
createResizeBtnEl() {
const { imgResizeBtnSize } = this.mindMap.opt
// 容器元素
this.handleEl = document.createElement('div')
this.handleEl.style.cssText = `
@@ -134,8 +141,8 @@ class NodeImgAdjust {
bottom: 0;
pointer-events: auto;
background-color: rgba(0, 0, 0, 0.3);
width: ${this.resizeBtnSize}px;
height: ${this.resizeBtnSize}px;
width: ${imgResizeBtnSize}px;
height: ${imgResizeBtnSize}px;
display: flex;
justify-content: center;
align-items: center;
@@ -178,8 +185,8 @@ class NodeImgAdjust {
right: 0;top:0;color:#fff;
pointer-events: auto;
background-color: rgba(0, 0, 0, 0.3);
width: ${this.resizeBtnSize}px;
height: ${this.resizeBtnSize}px;
width: ${imgResizeBtnSize}px;
height: ${imgResizeBtnSize}px;
display: flex;
justify-content: center;
align-items: center;
@@ -192,8 +199,14 @@ class NodeImgAdjust {
if (this.isMousedown) return
this.hideHandleEl()
})
btnRemove.addEventListener('click', e => {
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null })
btnRemove.addEventListener('click', async e => {
let stop = false
if (typeof this.mindMap.opt.beforeDeleteNodeImg === 'function') {
stop = await this.mindMap.opt.beforeDeleteNodeImg(this.node)
}
if (!stop) {
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null })
}
})
// 添加元素到页面
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
@@ -201,10 +214,13 @@ class NodeImgAdjust {
}
// 鼠标按钮按下事件
onMousedown() {
onMousedown(e) {
this.isMousedown = true
this.mousedownDrawTransform = this.mindMap.draw.transform()
// 隐藏节点实际图片
this.hideNodeImage()
this.mousedownOffset.x = e.clientX - this.rect.x2
this.mousedownOffset.y = e.clientY - this.rect.y2
// 将节点图片渲染到自定义元素上
this.handleEl.style.backgroundImage = `url(${this.node.getData('image')})`
}
@@ -213,13 +229,48 @@ class NodeImgAdjust {
onMousemove(e) {
if (!this.isMousedown) return
e.preventDefault()
// 计算当前拖拽位置对应的图片的实时大小
let { width: imageOriginWidth, height: imageOriginHeight } =
const { scaleX, scaleY } = this.mousedownDrawTransform
// 图片原始大小
const { width: imageOriginWidth, height: imageOriginHeight } =
this.node.getData('imageSize')
let newWidth = e.clientX - this.rect.x
let newHeight = e.clientY - this.rect.y
if (newWidth <= 0 || newHeight <= 0) return
let [actWidth, actHeight] = resizeImgSizeByOriginRatio(
let {
minImgResizeWidth,
minImgResizeHeight,
maxImgResizeWidthInheritTheme,
maxImgResizeWidth,
maxImgResizeHeight
} = this.mindMap.opt
// 主题设置的最小图片宽高
const minRatio = minImgResizeWidth / minImgResizeHeight
const oRatio = imageOriginWidth / imageOriginHeight
if (minRatio > oRatio) {
// 如果最小值比例大于图片原始比例,那么要调整高度最小值
minImgResizeHeight = minImgResizeWidth / oRatio
} else {
// 否则调整宽度最小值
minImgResizeWidth = minImgResizeHeight * oRatio
}
// 主题设置的最大图片宽高
let imgMaxWidth, imgMaxHeight
if (maxImgResizeWidthInheritTheme) {
imgMaxWidth = this.mindMap.getThemeConfig('imgMaxWidth')
imgMaxHeight = this.mindMap.getThemeConfig('imgMaxHeight')
} else {
imgMaxWidth = maxImgResizeWidth
imgMaxHeight = maxImgResizeHeight
}
imgMaxWidth = imgMaxWidth * scaleX
imgMaxHeight = imgMaxHeight * scaleY
// 计算当前拖拽位置对应的图片的实时大小
let newWidth = Math.abs(e.clientX - this.rect.x - this.mousedownOffset.x)
let newHeight = Math.abs(e.clientY - this.rect.y - this.mousedownOffset.y)
// 限制最小值
if (newWidth < minImgResizeWidth) newWidth = minImgResizeWidth
if (newHeight < minImgResizeHeight) newHeight = minImgResizeHeight
// 限制最大值
if (newWidth > imgMaxWidth) newWidth = imgMaxWidth
if (newHeight > imgMaxHeight) newHeight = imgMaxHeight
const [actWidth, actHeight] = resizeImgSizeByOriginRatio(
imageOriginWidth,
imageOriginHeight,
newWidth,
@@ -238,17 +289,27 @@ class NodeImgAdjust {
// 隐藏自定义元素
this.hideHandleEl()
// 更新节点图片为新的大小
let { image, imageTitle } = this.node.getData()
let { scaleX, scaleY } = this.mindMap.draw.transform()
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, {
url: image,
title: imageTitle,
width: this.currentImgWidth / scaleX,
height: this.currentImgHeight / scaleY,
custom: true // 代表自定义了图片大小
})
this.isAdjusted = true
const { image, imageTitle } = this.node.getData()
const { scaleX, scaleY } = this.mousedownDrawTransform
const newWidth = this.currentImgWidth / scaleX
const newHeight = this.currentImgHeight / scaleY
if (
Math.abs(newWidth - this.rect.width) > 1 ||
Math.abs(newHeight - this.rect.height) > 1
) {
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, {
url: image,
title: imageTitle,
width: newWidth,
height: newHeight,
custom: true // 代表自定义了图片大小
})
this.isAdjusted = true
}
this.isMousedown = false
this.mousedownDrawTransform = null
this.mousedownOffset.x = 0
this.mousedownOffset.y = 0
}
// 渲染完成事件

View File

@@ -53,17 +53,22 @@ class Painter {
node.uid === this.painterNode.uid
)
return
const style = {}
let style = {}
// 格式刷节点所有生效的样式
if (!this.mindMap.opt.onlyPainterNodeCustomStyles) {
style = {
...this.painterNode.effectiveStyles
}
}
const painterNodeData = this.painterNode.getData()
Object.keys(painterNodeData).forEach(key => {
if (checkIsNodeStyleDataKey(key)) {
style[key] = painterNodeData[key]
}
})
// 先去除目标节点的样式
this.mindMap.renderer._handleRemoveCustomStyles(node.getData())
node.setStyles(style)
if (painterNodeData.activeStyle) {
node.setStyles(painterNodeData.activeStyle, true)
}
}
// 插件被移除前做的事情

View File

@@ -4,8 +4,6 @@ import 'quill/dist/quill.snow.css'
import {
walk,
getTextFromHtml,
isWhite,
getVisibleColorFromTheme,
isUndef,
checkSmmFormatData,
removeHtmlNodeByClass,
@@ -57,6 +55,16 @@ class RichText {
this.cacheEditingText = ''
this.lostStyle = false
this.isCompositing = false
this.textNodePaddingX = 6
this.textNodePaddingY = 4
this.supportStyleProps = [
'fontFamily',
'fontSize',
'fontWeight',
'fontStyle',
'textDecoration',
'color'
]
this.initOpt()
this.extendQuill()
this.appendCss()
@@ -71,21 +79,42 @@ class RichText {
// 绑定事件
bindEvent() {
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)
}
// 解绑事件
unbindEvent() {
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)
}
// 插入样式
appendCss() {
this.mindMap.appendCss(
'richText',
`
.smm-richtext-node-wrap {
word-break: break-all;
user-select: none;
}
.smm-richtext-node-wrap p {
font-family: auto;
}
`
)
let cssText = `
.ql-editor {
.${CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP} {
overflow: hidden;
padding: 0;
height: auto;
@@ -102,15 +131,6 @@ class RichText {
border: none;
}
.smm-richtext-node-wrap {
word-break: break-all;
}
.smm-richtext-node-wrap p {
font-family: auto;
}
.smm-richtext-node-edit-wrap p {
font-family: auto;
}
@@ -175,14 +195,18 @@ class RichText {
if (this.showTextEdit) {
return
}
const {
richTextEditFakeInPlace,
let {
customInnerElsAppendTo,
nodeTextEditZIndex,
textAutoWrapWidth,
selectTextOnEnterEditText,
transformRichTextOnEnterEdit
transformRichTextOnEnterEdit,
openRealtimeRenderOnNodeTextEdit,
autoEmptyTextWhenKeydownEnterEdit
} = this.mindMap.opt
textAutoWrapWidth = node.hasCustomWidth()
? node.customTextWidth
: textAutoWrapWidth
this.node = node
this.isInserting = isInserting
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
@@ -195,26 +219,24 @@ 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 = 6
let paddingY = 4
if (richTextEditFakeInPlace) {
let paddingValue = node.getPaddingVale()
paddingX = paddingValue.paddingX
paddingY = paddingValue.paddingY
}
let paddingX = this.textNodePaddingX
let paddingY = this.textNodePaddingY
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;
box-shadow: 0 0 20px rgba(0,0,0,.5);
outline: none;
word-break:
break-all;
position:fixed;
box-sizing: border-box;
${
openRealtimeRenderOnNodeTextEdit
? ''
: 'box-shadow: 0 0 20px rgba(0,0,0,.5);'
}
outline: none;
word-break: break-all;
padding: ${paddingY}px ${paddingX}px;
`
this.textEditNode.addEventListener('click', e => {
@@ -234,7 +256,10 @@ class RichText {
this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px`
this.textEditNode.style.marginTop = `-${paddingY * scaleY}px`
this.textEditNode.style.zIndex = nodeTextEditZIndex
this.textEditNode.style.background = this.getBackground(node)
if (!openRealtimeRenderOnNodeTextEdit) {
this.textEditNode.style.background =
this.mindMap.renderer.textEdit.getBackground(node)
}
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
this.textEditNode.style.minHeight = originHeight + 'px'
this.textEditNode.style.left = rect.left + 'px'
@@ -243,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') {
@@ -263,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>`
@@ -273,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事件进入的节点编辑也不需要全选
@@ -287,6 +310,35 @@ class RichText {
this.cacheEditingText = ''
}
// 当openRealtimeRenderOnNodeTextEdit配置更新后需要更新编辑框样式
onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
openRealtimeRenderOnNodeTextEdit
) {
if (!this.textEditNode) return
this.textEditNode.style.background = openRealtimeRenderOnNodeTextEdit
? 'transparent'
: this.node
? this.mindMap.renderer.textEdit.getBackground(this.node)
: ''
this.textEditNode.style.boxShadow = openRealtimeRenderOnNodeTextEdit
? 'none'
: '0 0 20px rgba(0,0,0,.5)'
}
// 更新文本编辑框的大小和位置
updateTextEditNode() {
if (!this.node) return
const g = this.node._textData.node
const rect = g.node.getBoundingClientRect()
const originWidth = g.attr('data-width')
const originHeight = g.attr('data-height')
this.textEditNode.style.minWidth =
originWidth + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight = originHeight + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
}
// 删除文本编辑框元素
removeTextEditEl() {
if (!this.textEditNode) return
@@ -294,27 +346,6 @@ class RichText {
targetNode.removeChild(this.textEditNode)
}
// 获取编辑区域的背景填充
getBackground(node) {
const gradientStyle = node.style.merge('gradientStyle')
// 当前使用的是渐变色背景
if (gradientStyle) {
const startColor = node.style.merge('startColor')
const endColor = node.style.merge('endColor')
return `linear-gradient(to right, ${startColor}, ${endColor})`
} else {
// 单色背景
const bgColor = node.style.merge('fillColor')
const color = node.style.merge('color')
// 默认使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
return bgColor === 'transparent'
? isWhite(color)
? getVisibleColorFromTheme(this.mindMap.themeConfig)
: '#fff'
: bgColor
}
}
// 如果是非富文本的情况,需要手动应用文本样式
setTextStyleIfNotRichText(node) {
let style = {
@@ -331,13 +362,28 @@ 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字符串中的节点样式按样式名首字母排序
sortHtmlNodeStyles(html) {
return html.replace(/(<[^<>]+\s+style=")([^"]+)("\s*>)/g, (_, a, b, c) => {
let arr = b.match(/[^:]+:[^:]+;/g) || []
arr = arr.map(item => {
return item.trim()
})
arr.sort()
return a + arr.join('') + c
})
}
// 隐藏文本编辑控件,即完成编辑
@@ -350,8 +396,14 @@ class RichText {
beforeHideRichTextEdit(this)
}
let html = this.getEditText()
let list =
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
html = this.sortHtmlNodeStyles(html)
const list = nodes && nodes.length > 0 ? nodes : [this.node]
const node = this.node
this.textEditNode.style.display = 'none'
this.showTextEdit = false
this.mindMap.emit('rich_text_selection_change', false)
this.node = null
this.isInserting = false
list.forEach(node => {
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
// if (node.isGeneralization) {
@@ -360,12 +412,7 @@ class RichText {
// }
this.mindMap.render()
})
this.mindMap.emit('hide_text_edit', this.textEditNode, list, this.node)
this.textEditNode.style.display = 'none'
this.showTextEdit = false
this.mindMap.emit('rich_text_selection_change', false)
this.node = null
this.isInserting = false
this.mindMap.emit('hide_text_edit', this.textEditNode, list, node)
}
// 初始化Quill富文本编辑器
@@ -426,6 +473,17 @@ class RichText {
}
}
},
formats: [
'bold',
'italic',
'underline',
'strike',
'color',
'background',
'font',
'size',
'formula'
], // 明确指定允许的格式,不包含有序列表,无序列表等
theme: 'snow'
})
// 拦截复制事件即Ctrl + c去除多余的空行
@@ -490,12 +548,18 @@ class RichText {
this.setTextStyleIfNotRichText(this.node)
this.lostStyle = false
}
this.mindMap.emit('node_text_edit_change', {
node: this.node,
text: this.getEditText(),
richText: true
})
})
// 拦截粘贴,只允许粘贴纯文本
// this.quill.clipboard.addMatcher(Node.TEXT_NODE, node => {
// let style = this.getPasteTextStyle()
// return new Delta().insert(this.formatPasteText(node.data), style)
// })
// 剪贴板里只要存在文本就会走这里,所以当剪贴板里是纯文本,或文本+图片都可以监听到和拦截,但是只有纯图片时不会走这里,所以无法拦截
this.quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
let ops = []
let style = this.getPasteTextStyle()
@@ -511,6 +575,20 @@ class RichText {
delta.ops = ops
return delta
})
// 拦截图片的粘贴,当剪贴板里是纯图片,或文本+图片都可以拦截到,但是带来的问题是文本+图片时里面的文本也无法粘贴
this.quill.root.addEventListener(
'paste',
e => {
if (
e.clipboardData &&
e.clipboardData.files &&
e.clipboardData.files.length
) {
e.preventDefault()
}
},
true
)
}
// 获取粘贴的文本的样式
@@ -544,6 +622,16 @@ class RichText {
this.isCompositing = true
}
// 中文输入中
onCompositionUpdate() {
if (!this.showTextEdit || !this.node) return
this.mindMap.emit('node_text_edit_change', {
node: this.node,
text: this.getEditText(),
richText: true
})
}
// 中文输入结束
onCompositionEnd() {
if (!this.showTextEdit) {
@@ -588,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)
})
}
@@ -626,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 {
@@ -708,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)
@@ -720,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
}
}
@@ -798,6 +876,7 @@ class RichText {
this.transformAllNodesToNormalNode()
document.head.removeChild(this.styleEl)
this.unbindEvent()
this.mindMap.removeAppendCss('richText')
}
// 插件被卸载前做的事情

View File

@@ -224,9 +224,15 @@ class Search {
replaceText = String(replaceText)
let currentNode = this.matchNodeList[this.currentIndex]
if (!currentNode) return
let text = this.getReplacedText(currentNode, this.searchText, replaceText)
// 如果当前搜索文本是替换文本的子串,那么该节点还是符合搜索结果的
const keep = replaceText.includes(this.searchText)
const text = this.getReplacedText(currentNode, this.searchText, replaceText)
this.notResetSearchText = true
currentNode.setText(text, currentNode.getData('richText'), true)
if (keep) {
this.updateMatchNodeList(this.matchNodeList)
return
}
const newList = this.matchNodeList.filter(node => {
return currentNode !== node
})
@@ -249,25 +255,30 @@ class Search {
)
return
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)
if (this.isNodeInstance(node)) {
this.mindMap.renderer.setNodeDataRender(
node,
{
text,
resetRichText: !!node.getData('richText')
},
true
)
const data = {
text
}
if (hasRichTextPlugin) data.resetRichText = !!node.getData('richText')
this.mindMap.renderer.setNodeDataRender(node, data, true)
} else {
node.data.text = text
node.data.resetRichText = !!node.data.richText
if (hasRichTextPlugin) node.data.resetRichText = !!node.data.richText
}
})
this.mindMap.render()
this.mindMap.command.addHistory()
this.endSearch()
if (keep) {
this.updateMatchNodeList(this.matchNodeList)
} else {
this.endSearch()
}
}
// 获取某个节点替换后的文本

View File

@@ -41,7 +41,8 @@ class Select {
// 鼠标按下
onMousedown(e) {
if (this.mindMap.opt.readonly) {
const { readonly, mousedownEventPreventDefault } = this.mindMap.opt
if (readonly) {
return
}
let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
@@ -51,7 +52,9 @@ class Select {
) {
return
}
e.preventDefault()
if (mousedownEventPreventDefault) {
e.preventDefault()
}
this.isMousedown = true
this.cacheActiveList = [...this.mindMap.renderer.activeNodeList]
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)

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,35 +139,41 @@ 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
})
.css({
'font-family': associativeLineTextFontFamily,
'font-size': associativeLineTextFontSize
'font-size': associativeLineTextFontSize + 'px'
})
}

View File

@@ -1,11 +1,10 @@
// 默认主题
export default {
// 节点内边距
paddingX: 15,
paddingY: 5,
// 图片显示的最大宽度
imgMaxWidth: 100,
imgMaxWidth: 200,
// 图片显示的最大高度
imgMaxHeight: 100,
// icon的大小
@@ -16,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两种结构
@@ -73,7 +78,6 @@ export default {
fontSize: 16,
fontWeight: 'bold',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: 'transparent',
borderWidth: 0,
borderDasharray: 'none',
@@ -82,8 +86,24 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
startDir: [0, 0],
endDir: [1, 0],
// 连线标记的位置start头部、end尾部该配置在showLineMarker配置为true时生效
lineMarkerDir: 'end'
lineMarkerDir: 'end',
// 节点鼠标hover和激活时显示的矩形边框的颜色主题里不设置默认会取hoverRectColor实例化选项的值
hoverRectColor: '',
// 点鼠标hover和激活时显示的矩形边框的圆角大小
hoverRectRadius: 5
// 下列样式也支持给节点设置,用于覆盖最外层的设置
// paddingX,
// paddingY,
// lineWidth,
// lineColor,
// lineDasharray,
// lineFlow,
// lineFlowDuration,
// lineFlowForward
// 关联线的所有样式
},
// 二级节点样式
second: {
@@ -94,9 +114,8 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
fontWeight: 'noraml',
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
borderWidth: 1,
borderDasharray: 'none',
@@ -105,7 +124,11 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
lineMarkerDir: 'end'
startDir: [0, 0],
endDir: [1, 0],
lineMarkerDir: 'end',
hoverRectColor: '',
hoverRectRadius: 5
},
// 三级及以下节点样式
node: {
@@ -116,9 +139,8 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#6a6d6c',
fontSize: 14,
fontWeight: 'noraml',
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: 'transparent',
borderWidth: 0,
borderRadius: 5,
@@ -127,7 +149,11 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
lineMarkerDir: 'end'
startDir: [0, 0],
endDir: [1, 0],
lineMarkerDir: 'end',
hoverRectColor: '',
hoverRectRadius: 5
},
// 概要节点样式
generalization: {
@@ -138,9 +164,8 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
fontWeight: 'noraml',
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
borderWidth: 1,
borderDasharray: 'none',
@@ -148,7 +173,11 @@ export default {
textDecoration: 'none',
gradientStyle: false,
startColor: '#549688',
endColor: '#fff'
endColor: '#fff',
startDir: [0, 0],
endDir: [1, 0],
hoverRectColor: '',
hoverRectRadius: 5
}
}
@@ -176,10 +205,12 @@ const nodeSizeIndependenceList = [
'rootLineKeepSameInCurve',
'rootLineStartPositionKeepSameInCurve',
'showLineMarker',
'gradientStyle',
'lineRadius',
'startColor',
'endColor'
'hoverRectColor',
'hoverRectRadius',
'lineFlow',
'lineFlowDuration',
'lineFlowForward'
]
export const checkIsNodeSizeIndependenceConfig = config => {
let keys = Object.keys(config)
@@ -195,9 +226,13 @@ export const checkIsNodeSizeIndependenceConfig = config => {
return true
}
// 连线的样式
export const lineStyleProps = [
'lineColor',
'lineDasharray',
'lineWidth',
'lineMarkerDir'
'lineMarkerDir',
'lineFlow',
'lineFlowDuration',
'lineFlowForward'
]

View File

@@ -0,0 +1,5 @@
import defaultTheme from './default'
export default {
default: defaultTheme
}

View File

@@ -1,44 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 秋天
export default merge(defaultTheme, {
// 背景颜色
backgroundColor: '#fff2df',
// 连线的颜色
lineColor: '#b0bc47',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#b0bc47',
// 根节点样式
root: {
fillColor: '#e68112',
color: '#fff',
borderColor: '#e68112',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: '#ffd683',
color: '#8c5416',
borderColor: '#b0bc47',
borderWidth: 2,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#8c5416'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: '#ffd683',
borderColor: '#b0bc47',
borderWidth: 2,
color: '#8c5416'
}
})

View File

@@ -1,44 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 牛油果
export default merge(defaultTheme, {
// 背景颜色
backgroundColor: '#e6f1de',
// 连线的颜色
lineColor: '#f5ffad',
lineWidth: 4,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#749336',
// 根节点样式
root: {
fillColor: '#94c143',
color: '#fff',
borderColor: '#94c143',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: '#cee498',
color: '#749336',
borderColor: '#aec668',
borderWidth: 2,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#749336'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: '#cee498',
borderColor: '#aec668',
borderWidth: 2,
color: '#749336'
}
})

View File

@@ -1,44 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 黑金
export default merge(defaultTheme, {
// 背景颜色
backgroundColor: 'rgb(18, 20, 20)',
// 连线的颜色
lineColor: 'rgb(205, 186, 156)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(245, 224, 191)',
// 根节点样式
root: {
fillColor: 'rgb(255, 208, 124)',
color: 'rgb(111, 61, 6)',
borderColor: '',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(66, 57, 46)',
color: 'rgb(225, 201, 158)',
borderColor: 'rgb(245, 224, 191)',
borderWidth: 2,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(231, 203, 155)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: 'rgb(56, 45, 34)',
borderColor: 'rgb(104, 84, 61)',
borderWidth: 2,
color: 'rgb(242, 216, 176)'
}
})

View File

@@ -1,44 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 黑色幽默
export default merge(defaultTheme, {
// 背景颜色
backgroundColor: 'rgb(27, 31, 34)',
// 连线的颜色
lineColor: 'rgb(75, 81, 78)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(255, 119, 34)',
// 根节点样式
root: {
fillColor: 'rgb(36, 179, 96)',
color: '#fff',
borderColor: '',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(254, 199, 13)',
color: 'rgb(0, 0, 0)',
borderColor: '',
borderWidth: 0,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(204, 204, 204)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: 'rgb(27, 31, 34)',
borderColor: 'rgb(255, 119, 34)',
borderWidth: 2,
color: 'rgb(204, 204, 204)'
}
})

View File

@@ -1,37 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 天空蓝
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(115, 161, 191)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(115, 161, 191)'
},
// 二级节点样式
second: {
fillColor: 'rgb(238, 243, 246)',
color: '#333',
borderColor: 'rgb(115, 161, 191)',
borderWidth: 1,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333'
}
})

View File

@@ -1,37 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 脑残粉
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 148)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(191, 115, 148)'
},
// 二级节点样式
second: {
fillColor: 'rgb(246, 238, 242)',
color: '#333',
borderColor: 'rgb(191, 115, 148)',
borderWidth: 1,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333'
}
})

View File

@@ -1,49 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 脑图经典
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#fff',
// 连线的粗细
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 背景颜色
backgroundColor: 'rgb(58, 65, 68)',
// 背景图片
backgroundImage:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQzg5QTQ0NDhENzgxMUUzOENGREE4QTg0RDgzRTZDNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQzg5QTQ0NThENzgxMUUzOENGREE4QTg0RDgzRTZDNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkMwOEQ1NDRGOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkMwOEQ1NDUwOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+e9P33AAAACVJREFUeNpisXJ0YUACTAyoAMr/+eM7EGGRZ4FQ7BycEAZAgAEAHbEGtkoQm/wAAAAASUVORK5CYII=',
// 背景重复
backgroundRepeat: 'repeat',
backgroundSize: 'auto',
// 根节点样式
root: {
fillColor: 'rgb(233, 223, 152)',
color: '#333',
fontSize: 24,
borderRadius: 21
},
// 二级节点样式
second: {
fillColor: 'rgb(164, 197, 192)',
borderColor: 'transparent',
color: '#333',
fontSize: 16,
borderRadius: 10
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#fff',
fontWeight: 'bold'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'transparent',
color: '#333'
}
})

View File

@@ -1,43 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典2
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(51, 51, 51)',
// 背景颜色
backgroundColor: '#fff',
// 根节点样式
root: {
fillColor: 'rgb(18, 187, 55)',
color: '#fff',
fontSize: 24,
borderRadius: 10
},
// 二级节点样式
second: {
fillColor: 'rgb(241, 242, 241)',
borderColor: 'transparent',
color: '#1a1a1a',
fontSize: 18,
borderRadius: 10
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#1a1a1a'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'rgb(51, 51, 51)',
borderWidth: 2,
color: '#1a1a1a'
}
})

View File

@@ -1,46 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典3
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(94, 202, 110)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#1a1a1a',
// 背景颜色
backgroundColor: 'rgb(241, 241, 241)',
// 根节点样式
root: {
fillColor: 'rgb(255, 245, 214)',
color: '#1a1a1a',
fontSize: 24,
borderRadius: 10,
borderColor: 'rgb(249, 199, 84)',
borderWidth: 1
},
// 二级节点样式
second: {
fillColor: 'rgb(255, 245, 214)',
borderColor: 'rgb(249, 199, 84)',
borderWidth: 1,
color: '#1a1a1a',
fontSize: 18,
borderRadius: 10
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#1a1a1a'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#1a1a1a',
color: '#1a1a1a',
borderWidth: 2
}
})

View File

@@ -1,49 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典4
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(30, 53, 86)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: 'rgb(56, 123, 233)',
// 背景颜色
backgroundColor: 'rgb(241, 241, 241)',
// 根节点样式
root: {
fillColor: 'rgb(30, 53, 86)',
color: '#fff',
fontSize: 24,
borderRadius: 10,
borderColor: 'rgb(189, 197, 201)',
borderWidth: 2
},
// 二级节点样式
second: {
fillColor: 'rgb(169, 218, 218)',
borderColor: 'rgb(30, 53, 86)',
borderWidth: 2,
color: '#fff',
fontSize: 18,
borderRadius: 10
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(30, 53, 86)',
borderColor: 'rgb(30, 53, 86)',
borderWidth: 1,
marginY: 20
},
// 概要节点样式
generalization: {
fillColor: 'rgb(56, 123, 233)',
borderColor: 'rgb(56, 123, 233)',
color: '#fff',
borderWidth: 0
}
})

View File

@@ -1,40 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典蓝
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: 'rgb(51, 51, 51)',
// 背景颜色
backgroundColor: 'rgb(239, 248, 250)',
// 根节点样式
root: {
fillColor: 'rgb(255, 255, 255)',
color: '#222'
},
// 二级节点样式
second: {
fillColor: 'rgb(255, 255, 255)',
color: '#222',
borderColor: 'rgb(255, 255, 255)',
borderWidth: 1,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'rgb(51, 51, 51)',
color: '#333'
}
})

View File

@@ -1,39 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 199, 120)',
// 背景颜色
backgroundColor: 'rgb(236, 245, 231)',
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: 'rgb(123, 199, 120)',
// 根节点样式
root: {
fillColor: 'rgb(253, 244, 217)',
color: '#222'
},
// 二级节点样式
second: {
fillColor: 'rgb(253, 244, 217)',
color: '#222',
borderColor: 'rgb(242, 200, 104)',
borderWidth: 1,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333'
},
// 概要节点样式
generalization: {
fillColor: 'rgb(123, 199, 120)',
borderColor: 'transparent',
borderWidth: 2,
color: '#fff'
}
})

View File

@@ -1,42 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 咖啡
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(173, 123, 91)',
lineWidth: 4,
// 概要连线的粗细
generalizationLineWidth: 4,
// 概要连线的颜色
generalizationLineColor: 'rgb(173, 123, 91)',
// 根节点样式
root: {
fillColor: 'rgb(202, 117, 79)',
color: '#fff',
borderColor: '',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(245, 231, 216)',
color: 'rgb(125, 86, 42)',
borderColor: '',
borderWidth: 0,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(96, 71, 47)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: 'rgb(255, 249, 239)',
borderColor: 'rgb(173, 123, 91)',
borderWidth: 2,
color: 'rgb(122, 83, 44)'
}
})

View File

@@ -1,42 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 课程绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(113, 195, 169)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(113, 195, 169)',
// 根节点样式
root: {
fillColor: 'rgb(16, 160, 121)',
color: '#fff',
borderColor: '',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(240, 252, 249)',
color: 'rgb(50, 113, 96)',
borderColor: 'rgb(113, 195, 169)',
borderWidth: 2,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(10, 59, 43)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: 'rgb(246, 238, 211)',
borderColor: '',
borderWidth: 0,
color: 'rgb(173, 91, 12)'
}
})

View File

@@ -1,42 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 暗色
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(17, 68, 23)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 背景颜色
backgroundColor: 'rgb(15, 16, 17)',
// 根节点样式
root: {
fillColor: 'rgb(28, 178, 43)',
color: '#fff',
fontSize: 24,
borderRadius: 10
},
// 二级节点样式
second: {
fillColor: 'rgb(55, 56, 58)',
color: 'rgb(147,148,149)',
fontSize: 18,
borderRadius: 10,
borderWidth: 0
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(147, 148, 149)'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'transparent',
color: '#333'
}
})

View File

@@ -1,42 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 暗色2
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(75, 81, 78)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(255, 119, 34)',
// 背景颜色
backgroundColor: 'rgb(27, 31, 34)',
// 根节点样式
root: {
fillColor: 'rgb(36, 179, 96)',
color: '#fff',
borderColor: '',
borderWidth: 0
},
// 二级节点样式
second: {
fillColor: 'rgb(254, 199, 13)',
color: 'rgb(0, 0, 0)',
borderColor: '',
borderWidth: 0,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: 'rgb(204, 204, 204)'
},
// 概要节点样式
generalization: {
fillColor: 'transparent',
borderColor: 'rgb(255, 119, 34)',
borderWidth: 2,
color: 'rgb(204, 204, 204)'
}
})

View File

@@ -1,37 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 泥土黄
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 147, 115)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(191, 147, 115)'
},
// 二级节点样式
second: {
fillColor: 'rgb(246, 242, 238)',
color: '#333',
borderColor: 'rgb(191, 147, 115)',
borderWidth: 1,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333'
}
})

View File

@@ -1,31 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 清新绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#333',
// 背景颜色
backgroundColor: '#d1f6ec',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: '#1fb27d'
},
// 二级节点样式
second: {
fillColor: '#fff',
color: '#565656',
borderColor: 'transparent',
borderWidth: 0
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333'
}
})

View File

@@ -1,37 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 清新红
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 115)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(191, 115, 115)'
},
// 二级节点样式
second: {
fillColor: 'rgb(246, 238, 238)',
color: '#333',
borderColor: 'rgb(191, 115, 115)',
borderWidth: 1,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333'
}
})

View File

@@ -1,41 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 金色vip
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 56, 62)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(127, 93, 64)',
// 背景颜色
backgroundColor: '#fff',
// 根节点样式
root: {
fillColor: 'rgb(51, 56, 62)',
color: 'rgb(247, 208, 160)',
borderColor: '',
borderWidth: 0
},
// 二级节点样式
second: {
fillColor: 'rgb(239, 209, 176)',
color: 'rgb(81, 58, 42)',
borderColor: '',
borderWidth: 0,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222'
},
// 概要节点样式
generalization: {
fillColor: 'rgb(127, 93, 64)',
borderColor: 'transparent',
color: 'rgb(255, 214, 175)'
}
})

View File

@@ -1,42 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 绿叶
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(40, 193, 84)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(251, 158, 0)',
// 背景颜色
backgroundColor: 'rgb(238, 255, 243)',
// 根节点样式
root: {
fillColor: 'rgb(25, 193, 73)',
color: '#fff',
borderColor: '',
borderWidth: 0
},
// 二级节点样式
second: {
fillColor: '#fff',
color: 'rgb(69, 149, 96)',
borderColor: '',
borderWidth: 0,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'rgb(251, 158, 0)',
borderWidth: 2,
color: 'rgb(51, 51, 51)'
}
})

View File

@@ -1,67 +0,0 @@
import defaultTheme from './default'
import freshGreen from './freshGreen'
import blueSky from './blueSky'
import brainImpairedPink from './brainImpairedPink'
import romanticPurple from './romanticPurple'
import freshRed from './freshRed'
import earthYellow from './earthYellow'
import classic from './classic'
import classic2 from './classic2'
import classic3 from './classic3'
import classic4 from './classic4'
import dark from './dark'
import classicGreen from './classicGreen'
import classicBlue from './classicBlue'
import minions from './minions'
import pinkGrape from './pinkGrape'
import mint from './mint'
import gold from './gold'
import vitalityOrange from './vitalityOrange'
import greenLeaf from './greenLeaf'
import dark2 from './dark2'
import skyGreen from './skyGreen'
import simpleBlack from './simpleBlack'
import courseGreen from './courseGreen'
import coffee from './coffee'
import redSpirit from './redSpirit'
import blackHumour from './blackHumour'
import lateNightOffice from './lateNightOffice'
import blackGold from './blackGold'
import avocado from './avocado'
import autumn from './autumn'
import orangeJuice from './orangeJuice'
export default {
default: defaultTheme,
freshGreen,
blueSky,
brainImpairedPink,
romanticPurple,
freshRed,
earthYellow,
classic,
classic2,
classic3,
classic4,
dark,
classicGreen,
classicBlue,
minions,
pinkGrape,
mint,
gold,
vitalityOrange,
greenLeaf,
dark2,
skyGreen,
simpleBlack,
courseGreen,
coffee,
redSpirit,
blackHumour,
lateNightOffice,
blackGold,
avocado,
autumn,
orangeJuice
}

View File

@@ -1,44 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 深夜办公室
export default merge(defaultTheme, {
// 背景颜色
backgroundColor: 'rgb(32, 37, 49)',
// 连线的颜色
lineColor: 'rgb(137, 167, 196)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(255, 119, 34)',
// 根节点样式
root: {
fillColor: 'rgb(23, 153, 243)',
color: 'rgb(255, 255, 255)',
borderColor: '',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(70, 78, 94)',
color: 'rgb(209, 210, 210)',
borderColor: '',
borderWidth: 0,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(204, 204, 204)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: 'rgb(255, 119, 34)',
borderColor: '',
borderWidth: 2,
color: '#fff'
}
})

View File

@@ -1,40 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 小黄人
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#222',
// 背景颜色
backgroundColor: 'rgb(248, 215, 49)',
// 根节点样式
root: {
fillColor: 'rgb(55, 165, 255)',
borderColor: 'rgb(51, 51, 51)',
borderWidth: 3
},
// 二级节点样式
second: {
fillColor: 'rgb(255, 160, 36)',
color: '#222',
borderColor: 'rgb(51, 51, 51)',
borderWidth: 3,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222'
},
// 概要节点样式
generalization: {
borderColor: '#222',
borderWidth: 3,
color: '#222'
}
})

View File

@@ -1,40 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 薄荷
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(104, 204, 202)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(90, 206, 241)',
// 背景颜色
backgroundColor: 'rgb(239, 255, 255)',
// 根节点样式
root: {
fillColor: 'rgb(0, 192, 184)',
borderColor: '',
borderWidth: 0
},
// 二级节点样式
second: {
fillColor: '#fff',
color: '#222',
borderColor: 'rgb(184, 235, 233)',
borderWidth: 2,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222'
},
// 概要节点样式
generalization: {
fillColor: 'rgb(90, 206, 241)',
borderColor: 'transparent',
color: '#fff'
}
})

View File

@@ -1,44 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 橙汁
export default merge(defaultTheme, {
// 背景颜色
backgroundColor: '#070616',
// 连线的颜色
lineColor: '#fff',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 根节点样式
root: {
fillColor: '#ff6811',
color: '#110501',
borderColor: '#ff6811',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: '#070616',
color: '#a9a4a9',
borderColor: '#ff6811',
borderWidth: 2,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#a9a4a9'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: '',
borderColor: '#ff6811',
borderWidth: 2,
color: '#a9a4a9'
}
})

View File

@@ -1,40 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 粉红葡萄
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(166, 101, 106)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 背景颜色
backgroundColor: 'rgb(255, 208, 211)',
// 根节点样式
root: {
fillColor: 'rgb(139, 109, 225)',
borderColor: '',
borderWidth: 0
},
// 二级节点样式
second: {
fillColor: 'rgb(243, 104, 138)',
color: '#fff',
borderColor: '',
borderWidth: 0,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'transparent',
color: '#222'
}
})

View File

@@ -1,44 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 红色精神
export default merge(defaultTheme, {
// 背景颜色
backgroundColor: 'rgb(255, 238, 228)',
// 连线的颜色
lineColor: 'rgb(230, 138, 131)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(222, 101, 85)',
// 根节点样式
root: {
fillColor: 'rgb(207, 44, 44)',
color: 'rgb(255, 233, 157)',
borderColor: '',
borderWidth: 0,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(255, 255, 255)',
color: 'rgb(211, 58, 21)',
borderColor: 'rgb(222, 101, 85)',
borderWidth: 2,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(144, 71, 43)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: 'rgb(255, 247, 211)',
borderColor: 'rgb(255, 202, 162)',
borderWidth: 2,
color: 'rgb(187, 101, 69)'
}
})

View File

@@ -1,37 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 浪漫紫
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 115, 191)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(123, 115, 191)'
},
// 二级节点样式
second: {
fillColor: 'rgb(239, 238, 246)',
color: '#333',
borderColor: 'rgb(123, 115, 191)',
borderWidth: 1,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333'
}
})

View File

@@ -1,42 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 简约黑
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(34, 34, 34)',
lineWidth: 4,
// 概要连线的粗细
generalizationLineWidth: 4,
// 概要连线的颜色
generalizationLineColor: 'rgb(34, 34, 34)',
// 根节点样式
root: {
fillColor: '#fff',
color: 'rgb(34, 34, 34)',
borderColor: 'rgb(34, 34, 34)',
borderWidth: 3,
fontSize: 24
},
// 二级节点样式
second: {
fillColor: 'rgb(241, 246, 248)',
color: 'rgb(34, 34, 34)',
borderColor: 'rgb(34, 34, 34)',
borderWidth: 3,
fontSize: 18
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(34, 34, 34)'
},
// 概要节点样式
generalization: {
fontSize: 14,
fillColor: 'transparent',
borderColor: 'rgb(34, 34, 34)',
borderWidth: 2,
color: 'rgb(34, 34, 34)'
}
})

View File

@@ -1,41 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 天清绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#fff',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 背景颜色
backgroundColor: 'rgb(80, 156, 170)',
// 根节点样式
root: {
fillColor: '#fff',
borderColor: '',
borderWidth: 0,
color: 'rgb(65, 89, 158)'
},
// 二级节点样式
second: {
fillColor: 'rgb(251, 227, 188)',
color: 'rgb(65, 89, 158)',
borderColor: '',
borderWidth: 0,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: 'rgb(65, 89, 158)'
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'transparent',
color: 'rgb(65, 89, 158)'
}
})

View File

@@ -1,41 +0,0 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 活力橙
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(254, 146, 0)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(255, 222, 69)',
// 背景颜色
backgroundColor: 'rgb(255, 246, 243)',
// 根节点样式
root: {
fillColor: 'rgb(255, 112, 52)',
color: '#fff',
borderColor: '',
borderWidth: 0
},
// 二级节点样式
second: {
fillColor: '#fff',
color: 'rgb(51, 51, 51)',
borderColor: '',
borderWidth: 0,
fontSize: 14
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222'
},
// 概要节点样式
generalization: {
fillColor: 'rgb(255, 222, 69)',
borderColor: 'transparent',
color: 'rgb(51, 51, 51)'
}
})

View File

@@ -5,6 +5,8 @@ import {
} from '../constants/constant'
import MersenneTwister from './mersenneTwister'
import { ForeignObject } from '@svgdotjs/svg.js'
import merge from 'deepmerge'
import { lineStyleProps } from '../theme/default'
// 深度优先遍历树
export const walk = (
@@ -75,11 +77,11 @@ export const resizeImgSizeByOriginRatio = (
let nRatio = width / height
let mRatio = newWidth / newHeight
if (nRatio > mRatio) {
// 固定高度
arr = [nRatio * newHeight, newHeight]
} else {
// 固定宽度
arr = [newWidth, newWidth / nRatio]
} else {
// 固定高度
arr = [nRatio * newHeight, newHeight]
}
return arr
}
@@ -94,11 +96,11 @@ export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
} else {
let mRatio = maxWidth / maxHeight
if (nRatio > mRatio) {
// 固定高度
arr = [nRatio * maxHeight, maxHeight]
} else {
// 固定宽度
arr = [maxWidth, maxWidth / nRatio]
} else {
// 固定高度
arr = [nRatio * maxHeight, maxHeight]
}
}
} else if (maxWidth) {
@@ -506,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)
@@ -789,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' }] },
@@ -1068,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))
}
}
@@ -1079,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) {
@@ -1184,9 +1204,18 @@ export const handleInputPasteText = (e, text) => {
// 去除格式
text = getTextFromHtml(text)
// 去除换行
text = text.replaceAll(/\n/g, '')
const node = document.createTextNode(text)
selection.getRangeAt(0).insertNode(node)
// text = text.replaceAll(/\n/g, '')
const textArr = text.split(/\n/g)
const fragment = document.createDocumentFragment()
textArr.forEach((item, index) => {
const node = document.createTextNode(item)
fragment.appendChild(node)
if (index < textArr.length - 1) {
const br = document.createElement('br')
fragment.appendChild(br)
}
})
selection.getRangeAt(0).insertNode(fragment)
selection.collapseToEnd()
}
@@ -1601,3 +1630,12 @@ export const sortNodeList = nodeList => {
})
return nodeList
}
// 合并主题配置
export const mergeTheme = (dest, source) => {
return merge(dest, source, {
arrayMerge: (destinationArray, sourceArray) => {
return sourceArray
}
})
}

63
web/package-lock.json generated
View File

@@ -14,6 +14,7 @@
"element-ui": "^2.15.1",
"highlight.js": "^10.7.3",
"katex": "^0.16.9",
"simple-mind-map-plugin-themes": "^1.0.0",
"v-viewer": "^1.6.4",
"vue": "^2.6.11",
"vue-i18n": "^8.27.2",
@@ -1812,7 +1813,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
@@ -1828,7 +1828,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
@@ -1844,7 +1843,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
@@ -1860,7 +1858,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@@ -1876,7 +1873,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@@ -1892,7 +1888,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
@@ -1908,7 +1903,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
@@ -1924,7 +1918,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1940,7 +1933,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1956,7 +1948,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1972,7 +1963,6 @@
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -1988,7 +1978,6 @@
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -2004,7 +1993,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -2020,7 +2008,6 @@
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -2036,7 +2023,6 @@
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -2052,7 +2038,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@@ -2068,7 +2053,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
@@ -2084,7 +2068,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
@@ -2100,7 +2083,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
@@ -2116,7 +2098,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@@ -2132,7 +2113,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
@@ -2148,7 +2128,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@@ -6847,7 +6826,6 @@
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
"integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@@ -13616,6 +13594,14 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"node_modules/simple-mind-map-plugin-themes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-mind-map-plugin-themes/-/simple-mind-map-plugin-themes-1.0.0.tgz",
"integrity": "sha512-v+L28HfRK1B+qw+76ueRGbbAxnKmGhVNuHL+oCWTE0qbdvaqSAszI+yOxqeTNXJmBgzrjeJXpJ831/gDiWUKaQ==",
"dependencies": {
"esbuild": "^0.17.15"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@@ -18560,154 +18546,132 @@
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
"integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
"dev": true,
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
"integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
"dev": true,
"optional": true
},
"@esbuild/android-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
"integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
"dev": true,
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
"integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
"dev": true,
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
"integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
"integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
"integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
"integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
"integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
"dev": true,
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
"integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
"integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
"integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
"dev": true,
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
"integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
"dev": true,
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
"integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
"dev": true,
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
"integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
"dev": true,
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
"integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
"integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
"integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
"dev": true,
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
"integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
"dev": true,
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
"integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
"dev": true,
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
"integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
"dev": true,
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
"integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
"dev": true,
"optional": true
},
"@hapi/address": {
@@ -22487,7 +22451,6 @@
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
"integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
"dev": true,
"requires": {
"@esbuild/android-arm": "0.17.19",
"@esbuild/android-arm64": "0.17.19",
@@ -27888,6 +27851,14 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"simple-mind-map-plugin-themes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-mind-map-plugin-themes/-/simple-mind-map-plugin-themes-1.0.0.tgz",
"integrity": "sha512-v+L28HfRK1B+qw+76ueRGbbAxnKmGhVNuHL+oCWTE0qbdvaqSAszI+yOxqeTNXJmBgzrjeJXpJ831/gDiWUKaQ==",
"requires": {
"esbuild": "^0.17.15"
}
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",

View File

@@ -17,6 +17,7 @@
"element-ui": "^2.15.1",
"highlight.js": "^10.7.3",
"katex": "^0.16.9",
"simple-mind-map-plugin-themes": "^1.0.0",
"v-viewer": "^1.6.4",
"vue": "^2.6.11",
"vue-i18n": "^8.27.2",

View File

@@ -1,33 +0,0 @@
const path = require('path')
const fs = require('fs')
const hljs = require('highlight.js')
const md = require('markdown-it')({
html: true,
xhtmlOut: true,
highlight: function(str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return (
'<pre class="hljs"><code>' +
hljs.highlight(str, {
language: lang,
ignoreIllegals: true
}).value +
'</code></pre>'
)
} catch (__) {}
}
return (
'<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'
)
}
}).use(require('markdown-it-checkbox'))
const templatePath = path.join(__dirname, '../src/pages/Doc/Template.vue')
exports.transformMdToVue = (content) => {
let result = md.render(content)
let template = fs.readFileSync(templatePath, 'utf-8')
return template.replace('$$$$', result)
}

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: 50 KiB

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