Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98f0d5e0fc | ||
|
|
6f3a02d39e | ||
|
|
5cfc313f8e | ||
|
|
d93825dd57 | ||
|
|
85171db778 | ||
|
|
7d7ab9291a | ||
|
|
bb2502501e | ||
|
|
d60f30d97e | ||
|
|
706b2ee65d | ||
|
|
e939b6132f | ||
|
|
b69a0b620d | ||
|
|
d3d92a6e70 | ||
|
|
6751897309 | ||
|
|
07a3f65911 | ||
|
|
924b2660e1 | ||
|
|
98d28a7b67 | ||
|
|
a93518dee0 | ||
|
|
809c2c5666 | ||
|
|
cd90089b91 | ||
|
|
4396c53d79 | ||
|
|
4bea7d5e2b | ||
|
|
cc62f98a9f | ||
|
|
244f2755a1 | ||
|
|
23d38d9301 | ||
|
|
73a61f81f8 | ||
|
|
6539a87cb2 | ||
|
|
75635ef2bb | ||
|
|
20fae6270d | ||
|
|
6b40358f65 | ||
|
|
e072dcb170 | ||
|
|
6878d92ebe | ||
|
|
e9352a4f6c | ||
|
|
6b9eee7fc6 | ||
|
|
c1217f1532 | ||
|
|
d73225f787 | ||
|
|
11b3270314 | ||
|
|
5730a7aed5 | ||
|
|
aeda3924a0 | ||
|
|
d9300395ff | ||
|
|
5bff885c00 | ||
|
|
088fd398a9 | ||
|
|
487aa38216 | ||
|
|
e2403ae433 | ||
|
|
7f0202e16e | ||
|
|
2b8d4ae225 | ||
|
|
513a1c342c | ||
|
|
d641b7e2ef | ||
|
|
c769d4d203 | ||
|
|
a36b9085bf | ||
|
|
42c934cb6d | ||
|
|
728b1e1503 | ||
|
|
1949d86abb | ||
|
|
a7c68816f9 | ||
|
|
ac3ad1681f | ||
|
|
92894d0341 | ||
|
|
58dc232ebf | ||
|
|
5abf09b560 | ||
|
|
6694dffa06 | ||
|
|
3673c6aafe | ||
|
|
979299f2e2 | ||
|
|
c0f69e038a | ||
|
|
80727b759d | ||
|
|
57fe315345 | ||
|
|
231dbc00bc | ||
|
|
02957e1fcf | ||
|
|
38576a4860 | ||
|
|
9b26ca9290 | ||
|
|
d36ff55335 | ||
|
|
1ca6a34edf | ||
|
|
c6f8f38648 | ||
|
|
614aa1ec30 | ||
|
|
f0c08c7953 | ||
|
|
b2d5a626c7 | ||
|
|
102cbeb821 | ||
|
|
bff683cb5c | ||
|
|
bf9cb99441 | ||
|
|
e966e5d57c | ||
|
|
740c898bb1 | ||
|
|
3243e366b0 | ||
|
|
7c6b67e8fb | ||
|
|
3b4195acc5 | ||
|
|
8b68b1fc48 | ||
|
|
4614a87bdd | ||
|
|
c87c169dab | ||
|
|
bc6bf2f8f9 | ||
|
|
2e5d17de16 | ||
|
|
a12e72117e | ||
|
|
b3d16a60b8 | ||
|
|
2f91ea7199 | ||
|
|
1085473463 | ||
|
|
4c7dafe94e | ||
|
|
f52e39eb16 | ||
|
|
27d3e977db | ||
|
|
3e59fa6ade | ||
|
|
c80916d0f2 | ||
|
|
7bd73ba157 | ||
|
|
6055a04ec5 | ||
|
|
a114631a66 | ||
|
|
e22f67a831 | ||
|
|
792811f39e | ||
|
|
ea29dad6fd | ||
|
|
660ec00ca7 | ||
|
|
2baa500c17 | ||
|
|
70b6b0052f | ||
|
|
798591f6f9 | ||
|
|
f0b73d635e | ||
|
|
0b049c5294 | ||
|
|
4bf43ff338 | ||
|
|
58a3faae74 | ||
|
|
f3fe2dbc7b | ||
|
|
a72d2e6748 | ||
|
|
8c07209cea | ||
|
|
280afa6a73 | ||
|
|
fd85085cb7 | ||
|
|
95d7a3ac41 | ||
|
|
a295d257d7 | ||
|
|
460d4ea558 | ||
|
|
8b90557f70 | ||
|
|
c0fea992a9 | ||
|
|
8bdb59c3ea | ||
|
|
c4a846a195 | ||
|
|
7e3a1e405e | ||
|
|
952472a977 | ||
|
|
403aae4b3d | ||
|
|
7999b5c260 | ||
|
|
cdc5c7aa81 | ||
|
|
9a8cd1dd24 | ||
|
|
c308cc7d44 | ||
|
|
1c0fe5ac8d | ||
|
|
44413b00fd | ||
|
|
8487e148ea | ||
|
|
3effff95fa | ||
|
|
dd52873106 | ||
|
|
a2cd6e0864 |
5
Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM nginx
|
||||
RUN mkdir /app
|
||||
COPY ./index.html /app/
|
||||
COPY ./dist /app/dist/
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
123
README.md
@@ -11,19 +11,17 @@
|
||||
|
||||
本项目包含两部分:
|
||||
|
||||
1.一个 js 思维导图库,不依赖任何框架,你可以使用它来快速完成 Web 思维导图产品的开发。
|
||||
1.一个 js 思维导图库,不依赖任何框架,可以使用它来快速完成 Web 思维导图产品的开发。
|
||||
|
||||
开发文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)。
|
||||
|
||||
2.一个 Web 思维导图,基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,所以你可以直接把它当做一个在线版思维导图应用使用,如果觉得 github 的响应速度慢,你也可以部署到你的服务器上。
|
||||
2.一个 Web 思维导图,基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,可以当做一个在线版思维导图应用使用,也可以自部署和二次开发。
|
||||
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)。
|
||||
|
||||
另外也提供了客户端可供下载使用,支持`Windows`、`Mac`及`Linux`,下载地址:
|
||||
此外也提供了客户端可供下载使用,支持`Windows`、`Mac`及`Linux`,下载地址:
|
||||
|
||||
Github:[releases](https://github.com/wanglin2/mind-map/releases)。
|
||||
|
||||
百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
|
||||
Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
|
||||
|
||||
> 客户端版本会落后于在线版本,尝试最新功能请优先使用在线版。
|
||||
|
||||
@@ -33,13 +31,26 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴(横向、竖向)、鱼骨图等结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 节点内容支持文本(普通文本、富文本)、图片、图标、超链接、备注、标签、概要、数学公式
|
||||
- [x] 节点支持拖拽(拖拽移动、自由调整)、多种节点形状,支持使用 DDM 完全自定义节点内容
|
||||
- [x] 节点支持拖拽(拖拽移动、自由调整)、多种节点形状;支持扩展节点内容、支持使用 DDM 完全自定义节点内容
|
||||
- [x] 支持画布拖动、缩放
|
||||
- [x] 支持鼠标按键拖动选择和 Ctrl+左键两种多选节点方式
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`、`xmind`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`、`xmind`、`txt`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条、手绘风格、彩虹线条
|
||||
- [x] 提供丰富的配置,满足各种场景各种使用习惯
|
||||
- [x] 支持协同编辑
|
||||
- [x] 支持演示模式
|
||||
|
||||
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
|
||||
|
||||
> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、HandDrawnLikeStyle(手绘风格插件)[收费]
|
||||
|
||||
本项目不会实现的特性:
|
||||
|
||||
> 1.自由节点,即多个根节点;
|
||||
>
|
||||
> 2.概要节点后面继续添加节点;
|
||||
>
|
||||
> 如果你需要以上特性,那么本库可能无法满足你的需求。
|
||||
|
||||
# 安装
|
||||
|
||||
@@ -86,20 +97,22 @@ const mindMap = new MindMap({
|
||||
|
||||
# License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
保留`mind-map`版权声明的情况下可随意商用。
|
||||
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用。如不想保留可联系作者。
|
||||
|
||||
# 微信交流群
|
||||
|
||||
群聊人数较多,无法通过二维码入群,可以微信添加`wanglinguanfang`拉你入群。
|
||||
群聊人数较多,无法通过二维码入群,可以微信添加`wanglinguanfang`拉你入群。思维导图相关问题皆可在群里提问,不必私聊作者。
|
||||
|
||||
# star
|
||||
|
||||
如果喜欢本项目,欢迎点个 star,这对我们很重要。
|
||||
|
||||
[](https://star-history.com/#wanglin2/mind-map&Date)
|
||||
|
||||
# 请作者喝杯咖啡
|
||||
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~
|
||||
|
||||
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
|
||||
|
||||
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
|
||||
|
||||
<p>
|
||||
@@ -272,4 +285,84 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/慕智打印-兰兰.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>慕智打印-兰兰</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>锦冰</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/旭东.png" style="width: 50px;height: 50px;" />
|
||||
<span>旭东</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/俊奇.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>俊奇</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/橘半.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>橘半</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/pluvet.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>pluvet</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/皇登攀.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>皇登攀</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/风格.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>风格</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>SR</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/逆水行舟.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>逆水行舟</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>LiuJL</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/L.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>L</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>sunniberg</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/在下青铜五.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>在下青铜五</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/木星二号.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>木星二号</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/阿晨.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>阿晨</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>铁</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/庆国.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>庆国</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/孟照星.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>孟照星</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/子豪.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>子豪</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/宏涛.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>宏涛</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
1
copy.js
@@ -13,3 +13,4 @@ if (fs.existsSync(src)) {
|
||||
fs.unlinkSync(src)
|
||||
}
|
||||
|
||||
console.warn('请检查手绘风格选项是否开启!!!')
|
||||
2
dist/css/app.css
vendored
6
dist/css/chunk-vendors.css
vendored
BIN
dist/fonts/iconfont.ttf
vendored
BIN
dist/fonts/iconfont.woff
vendored
BIN
dist/fonts/iconfont.woff2
vendored
BIN
dist/img/L.jpg
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
dist/img/pluvet.jpg
vendored
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
dist/img/俊奇.jpg
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
dist/img/在下青铜五.jpg
vendored
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
dist/img/子豪.jpg
vendored
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
dist/img/孟照星.jpg
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
dist/img/宏涛.jpg
vendored
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
dist/img/庆国.jpg
vendored
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
dist/img/木星二号.jpg
vendored
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
dist/img/橘半.jpg
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
dist/img/皇登攀.jpg
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
dist/img/逆水行舟.jpg
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
dist/img/阿晨.jpg
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
dist/img/风格.jpg
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
2
dist/js/app.js
vendored
1
dist/js/chunk-02258ed0.js
vendored
1
dist/js/chunk-16bd07ee.js
vendored
Normal file
1
dist/js/chunk-1ba6eabc.js
vendored
Normal file
1
dist/js/chunk-2d0a4b03.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0a4b03"],{"0805":function(t,s,_){"use strict";_.r(s);var a=function(){var t=this;t._self._c;return t._m(0)},v=[function(){var t=this,s=t._self._c;return s("div",[s("h1",[t._v("快捷键操作如何传递自定义参数")]),s("p",[t._v("库提供了很多命令,比如插入子节点的"),s("code",[t._v("INSERT_CHILD_NODE")]),t._v("等,这些命令大多可以接收一定参数,比如在插入节点时我想指定初始文本和节点uid,那么可以这样调用:")]),s("pre",{staticClass:"hljs"},[s("code",[t._v("mindMap.execCommand("),s("span",{staticClass:"hljs-string"},[t._v("'INSERT_CHILD_NODE'")]),t._v(", "),s("span",{staticClass:"hljs-literal"},[t._v("true")]),t._v(", [], {\n "),s("span",{staticClass:"hljs-attr"},[t._v("text")]),t._v(": "),s("span",{staticClass:"hljs-string"},[t._v("'初始文本'")]),t._v(",\n "),s("span",{staticClass:"hljs-attr"},[t._v("uid")]),t._v(": "),s("span",{staticClass:"hljs-string"},[t._v("'xxx'")]),t._v("\n})\n")])]),s("p",[t._v("但是同时库内部也默认注册了很多快捷键,比如插入下级节点的"),s("code",[t._v("Tab")]),t._v("快捷键,很遗憾,目前快捷键操作无法让你传入自定义的参数,那么该怎么办呢,可以这样处理,首先确定你要给什么快捷键传入参数,比如"),s("code",[t._v("Tab")]),t._v(",那么首先可以调用如下方法删除库默认注册的快捷键:")]),s("pre",{staticClass:"hljs"},[s("code",[s("span",{staticClass:"hljs-keyword"},[t._v("const")]),t._v(" keyName = "),s("span",{staticClass:"hljs-string"},[t._v("'Tab'")]),t._v("\nmindMap.keyCommand.removeShortcut(keyName)\n")])]),s("p",[t._v("然后再重新注册即可:")]),s("pre",{staticClass:"hljs"},[s("code",[t._v("mindMap.keyCommand.addShortcut(keyName, "),s("span",{staticClass:"hljs-function"},[t._v("() =>")]),t._v(" {\n mindMap.execCommand("),s("span",{staticClass:"hljs-string"},[t._v("'INSERT_CHILD_NODE'")]),t._v(", "),s("span",{staticClass:"hljs-literal"},[t._v("true")]),t._v(", [], {\n "),s("span",{staticClass:"hljs-attr"},[t._v("text")]),t._v(": "),s("span",{staticClass:"hljs-string"},[t._v("'初始文本'")]),t._v(",\n "),s("span",{staticClass:"hljs-attr"},[t._v("uid")]),t._v(": "),s("span",{staticClass:"hljs-string"},[t._v("'xxx'")]),t._v("\n })\n})\n")])]),s("p",[t._v("库内部默认注册的快捷键对应的命令一览:")]),s("table",[s("thead",[s("tr",[s("th",[t._v("快捷键")]),s("th",[t._v("命令")])])]),s("tbody",[s("tr",[s("td",[t._v("Control+z")]),s("td",[t._v("BACK")])]),s("tr",[s("td",[t._v("Control+y")]),s("td",[t._v("FORWARD")])]),s("tr",[s("td",[t._v("Tab")]),s("td",[t._v("INSERT_CHILD_NODE")])]),s("tr",[s("td",[t._v("Insert")]),s("td",[t._v("INSERT_CHILD_NODE")])]),s("tr",[s("td",[t._v("Enter")]),s("td",[t._v("INSERT_NODE")])]),s("tr",[s("td",[t._v("Shift+Tab")]),s("td",[t._v("INSERT_PARENT_NODE")])]),s("tr",[s("td",[t._v("Control+g")]),s("td",[t._v("ADD_GENERALIZATION")])]),s("tr",[s("td",[t._v("Del或Backspace")]),s("td",[t._v("REMOVE_NODE")])]),s("tr",[s("td",[t._v("Shift+Backspace")]),s("td",[t._v("REMOVE_CURRENT_NODE")])]),s("tr",[s("td",[t._v("Control+a")]),s("td",[t._v("SELECT_ALL")])]),s("tr",[s("td",[t._v("Control+l")]),s("td",[t._v("RESET_LAYOUT")])]),s("tr",[s("td",[t._v("Control+Up")]),s("td",[t._v("UP_NODE")])]),s("tr",[s("td",[t._v("Control+Down")]),s("td",[t._v("DOWN_NODE")])])])])])}],n={},l=n,r=_("2877"),d=Object(r["a"])(l,a,v,!1,null,null,null);s["default"]=d.exports}}]);
|
||||
2
dist/js/chunk-2d0aa978.js
vendored
2
dist/js/chunk-2d0ab10b.js
vendored
2
dist/js/chunk-2d0ab546.js
vendored
2
dist/js/chunk-2d0afe0d.js
vendored
1
dist/js/chunk-2d0b1be7.js
vendored
Normal file
2
dist/js/chunk-2d0c09f6.js
vendored
2
dist/js/chunk-2d0c0a44.js
vendored
2
dist/js/chunk-2d0c191e.js
vendored
1
dist/js/chunk-2d0c213a.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0c213a"],{4987:function(s,n,a){"use strict";a.r(n);var i=function(){var s=this;s._self._c;return s._m(0)},t=[function(){var s=this,n=s._self._c;return n("div",[n("h1",[s._v("RainbowLines插件")]),n("blockquote",[n("p",[s._v("v0.9.9+")])]),n("p",[s._v("该插件用于实现彩虹线条。")]),n("p",[s._v("开启彩虹线条及自定义颜色可以通过实例化选项"),n("code",[s._v("rainbowLinesConfig")]),s._v("设置。")]),n("p",[s._v("默认的颜色列表如下:")]),n("pre",{staticClass:"hljs"},[n("code",[s._v("[\n 'rgb(255, 213, 73)',\n 'rgb(255, 136, 126)',\n 'rgb(107, 225, 141)',\n 'rgb(151, 171, 255)',\n 'rgb(129, 220, 242)',\n 'rgb(255, 163, 125)',\n 'rgb(152, 132, 234)'\n]\n")])]),n("h2",[s._v("注册")]),n("pre",{staticClass:"hljs"},[n("code",[n("span",{staticClass:"hljs-keyword"},[s._v("import")]),s._v(" MindMap "),n("span",{staticClass:"hljs-keyword"},[s._v("from")]),s._v(" "),n("span",{staticClass:"hljs-string"},[s._v("'simple-mind-map'")]),s._v("\n"),n("span",{staticClass:"hljs-keyword"},[s._v("import")]),s._v(" RainbowLines "),n("span",{staticClass:"hljs-keyword"},[s._v("from")]),s._v(" "),n("span",{staticClass:"hljs-string"},[s._v("'simple-mind-map/src/plugins/RainbowLines.js'")]),s._v("\nMindMap.usePlugin(RainbowLines)\n")])]),n("p",[s._v("注册完且实例化"),n("code",[s._v("MindMap")]),s._v("后可通过"),n("code",[s._v("mindMap.rainbowLines")]),s._v("获取到该实例。")]),n("h2",[s._v("方法")]),n("h3",[s._v("updateRainLinesConfig(config = {})")]),n("p",[s._v("如果你在通过实例化选项"),n("code",[s._v("rainbowLinesConfig")]),s._v("设置了彩虹线条后想修改,那么可以使用该方法,参数"),n("code",[s._v("config")]),s._v("同"),n("code",[s._v("rainbowLinesConfig")]),s._v("。")]),n("pre",{staticClass:"hljs"},[n("code",[s._v("{\n "),n("span",{staticClass:"hljs-attr"},[s._v("open")]),s._v(": "),n("span",{staticClass:"hljs-literal"},[s._v("false")]),s._v(","),n("span",{staticClass:"hljs-comment"},[s._v("// 是否开启彩虹线条")]),s._v("\n "),n("span",{staticClass:"hljs-attr"},[s._v("colorsList")]),s._v(": []"),n("span",{staticClass:"hljs-comment"},[s._v("// 自定义彩虹线条的颜色列表,如果不设置,会使用默认颜色列表")]),s._v("\n}\n")])]),n("h3",[s._v("getColorsList()")]),n("p",[s._v("获取当前使用的彩虹线条颜色列表。")]),n("h3",[s._v("getNodeColor(node)")]),n("p",[s._v("获取指定的节点实例对应的彩虹线条颜色。")]),n("h3",[s._v("getSecondLayerAncestor(node)")]),n("p",[s._v("获取一个节点实例的第二层级的祖先节点实例。")])])}],v={},_=v,o=a("2877"),e=Object(o["a"])(_,i,t,!1,null,null,null);n["default"]=e.exports}}]);
|
||||
1
dist/js/chunk-2d0d36df.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d36df"],{"5d71":function(e,t,n){"use strict";n.r(t);var i=function(){var e=this;e._self._c;return e._m(0)},o=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("Demonstrate plugin")]),t("blockquote",[t("p",[e._v("v0.9.11+")])]),t("p",[e._v("The "),t("code",[e._v("Demonstrate")]),e._v(" plugin provides demonstration functionality.")]),t("p",[e._v("When entering demonstration mode, the container elements will be automatically displayed in full screen, and then default to the root node. You can switch between the previous and next steps by pressing the left and right arrow keys on the keyboard, and exit demonstration mode by pressing the 'Esc' key.")]),t("p",[e._v("After entering demonstration mode, all shortcut keys on the mind map will be unavailable, and the mouse will not be able to operate the mind map.")]),t("h2",[e._v("Register")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-keyword"},[e._v("import")]),e._v(" MindMap "),t("span",{staticClass:"hljs-keyword"},[e._v("from")]),e._v(" "),t("span",{staticClass:"hljs-string"},[e._v("'simple-mind-map'")]),e._v("\n"),t("span",{staticClass:"hljs-keyword"},[e._v("import")]),e._v(" Demonstrate "),t("span",{staticClass:"hljs-keyword"},[e._v("from")]),e._v(" "),t("span",{staticClass:"hljs-string"},[e._v("'simple-mind-map/src/plugins/Demonstrate.js'")]),e._v("\n\nMindMap.usePlugin(Demonstrate)\n")])]),t("p",[e._v("After registration and instantiation of "),t("code",[e._v("MindMap")]),e._v(", the instance can be obtained through "),t("code",[e._v("mindMap.demonstrate")]),e._v(".")]),t("h3",[e._v("Config")]),t("p",[e._v("This plugin provides some configuration items for configuration, which can be configured through the instantiation option 'demonstrateConfig'. Please refer to the 【Instantiation options】 section in the 【Constructor】 section for details.")]),t("h3",[e._v("Event")]),t("p",[e._v("The plugin will dispatch the following events:")]),t("p",[t("code",[e._v("exit_demonstrate")]),e._v(":Triggered when exiting the demonstration.")]),t("p",[t("code",[e._v("demonstrate_jump")]),e._v(":Triggered when jumping.")]),t("p",[e._v("Please refer to the 'on' function in the 【Instance methods】 section of the 【Constructor】 chapter for details.")]),t("h2",[e._v("Props")]),t("h3",[e._v("stepList")]),t("p",[e._v("List of all steps demonstrated. Available when the 'enter' method is called.")]),t("h3",[e._v("currentStepIndex")]),t("p",[e._v("The index of the steps currently played, counting from 0.")]),t("h3",[e._v("config")]),t("p",[e._v("The current configuration of the plugin.")]),t("h2",[e._v("Methods")]),t("h3",[e._v("enter()")]),t("p",[e._v("Entering demonstration mode will automatically display the container elements in full screen.")]),t("h3",[e._v("exit()")]),t("p",[e._v("Exit demonstration mode, which can also be exited by pressing the 'Esc' key.")]),t("h3",[e._v("prev()")]),t("p",[e._v("Previous step.")]),t("h3",[e._v("next()")]),t("p",[e._v("Next step.")]),t("h3",[e._v("jump(index)")]),t("ul",[t("li",[t("code",[e._v("index")]),e._v(":Number,To jump to a certain step, count from 0.")])]),t("p",[e._v("Jump to a certain step.")])])}],s={},a=s,r=n("2877"),l=Object(r["a"])(a,i,o,!1,null,null,null);t["default"]=l.exports}}]);
|
||||
2
dist/js/chunk-2d0d6590.js
vendored
2
dist/js/chunk-2d0d9fbc.js
vendored
2
dist/js/chunk-2d0da701.js
vendored
2
dist/js/chunk-2d0dad5f.js
vendored
2
dist/js/chunk-2d0db0f2.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0db0f2"],{"6df4":function(n,v,_){"use strict";_.r(v);var e=function(){var n=this;n._self._c;return n._m(0)},o=[function(){var n=this,v=n._self._c;return v("div",[v("h1",[n._v("Command实例")]),v("p",[v("code",[n._v("command")]),n._v("实例负责命令的添加及执行,内置了很多命令,也可以自行添加,命令指需要在历史堆栈数据里添加副本的操作。可通过"),v("code",[n._v("mindMap.command")]),n._v("获取到该实例")]),v("h2",[n._v("方法")]),v("h3",[n._v("add(name, fn)")]),v("p",[n._v("添加命令。")]),v("p",[v("code",[n._v("name")]),n._v(":命令名称")]),v("p",[v("code",[n._v("fn")]),n._v(":命令要执行的方法")]),v("h3",[n._v("remove(name, fn)")]),v("p",[n._v("移除命令。")]),v("p",[v("code",[n._v("name")]),n._v(":要移除的命令名称")]),v("p",[v("code",[n._v("fn")]),n._v(":要移除的方法,不传的话移除该命令所有的方法")]),v("h3",[n._v("getCopyData()")]),v("p",[n._v("获取渲染树数据副本")]),v("h3",[n._v("clearHistory()")]),v("p",[n._v("清空历史堆栈数据")])])}],a={},c=a,d=_("2877"),p=Object(d["a"])(c,e,o,!1,null,null,null);v["default"]=p.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0db0f2"],{"6df4":function(v,_,e){"use strict";e.r(_);var n=function(){var v=this;v._self._c;return v._m(0)},o=[function(){var v=this,_=v._self._c;return _("div",[_("h1",[v._v("Command 实例")]),_("p",[_("code",[v._v("command")]),v._v("实例负责命令的添加及执行,内置了很多命令,也可以自行添加,命令指需要在历史堆栈数据里添加副本的操作。可通过"),_("code",[v._v("mindMap.command")]),v._v("获取到该实例")]),_("h2",[v._v("方法")]),_("h3",[v._v("pause()")]),_("blockquote",[_("p",[v._v("v0.9.11+")])]),_("p",[v._v("暂停收集历史数据。")]),_("h3",[v._v("recovery()")]),_("blockquote",[_("p",[v._v("v0.9.11+")])]),_("p",[v._v("恢复收集历史数据。")]),_("h3",[v._v("add(name, fn)")]),_("p",[v._v("添加命令。")]),_("p",[_("code",[v._v("name")]),v._v(":命令名称")]),_("p",[_("code",[v._v("fn")]),v._v(":命令要执行的方法")]),_("h3",[v._v("remove(name, fn)")]),_("p",[v._v("移除命令。")]),_("p",[_("code",[v._v("name")]),v._v(":要移除的命令名称")]),_("p",[_("code",[v._v("fn")]),v._v(":要移除的方法,不传的话移除该命令所有的方法")]),_("h3",[v._v("getCopyData()")]),_("p",[v._v("获取渲染树数据副本")]),_("h3",[v._v("clearHistory()")]),_("p",[v._v("清空历史堆栈数据")])])}],c={},p=c,a=e("2877"),d=Object(a["a"])(p,n,o,!1,null,null,null);_["default"]=d.exports}}]);
|
||||
1
dist/js/chunk-2d0dd7d2.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0dd7d2"],{8235:function(n,s,t){"use strict";t.r(s);var i=function(){var n=this;n._self._c;return n._m(0)},e=[function(){var n=this,s=n._self._c;return s("div",[s("h1",[n._v("RainbowLines plugin")]),s("blockquote",[s("p",[n._v("v0.9.9+")])]),s("p",[n._v("This plugin is used to implement rainbow lines.")]),s("p",[n._v("Enabling rainbow lines and custom colors can be set through the instantiation option 'rainbowLinesConfig'.")]),s("p",[n._v("The default color list is as follows:")]),s("pre",{staticClass:"hljs"},[s("code",[n._v("[\n 'rgb(255, 213, 73)',\n 'rgb(255, 136, 126)',\n 'rgb(107, 225, 141)',\n 'rgb(151, 171, 255)',\n 'rgb(129, 220, 242)',\n 'rgb(255, 163, 125)',\n 'rgb(152, 132, 234)'\n]\n")])]),s("h2",[n._v("Register")]),s("pre",{staticClass:"hljs"},[s("code",[s("span",{staticClass:"hljs-keyword"},[n._v("import")]),n._v(" MindMap "),s("span",{staticClass:"hljs-keyword"},[n._v("from")]),n._v(" "),s("span",{staticClass:"hljs-string"},[n._v("'simple-mind-map'")]),n._v("\n"),s("span",{staticClass:"hljs-keyword"},[n._v("import")]),n._v(" RainbowLines "),s("span",{staticClass:"hljs-keyword"},[n._v("from")]),n._v(" "),s("span",{staticClass:"hljs-string"},[n._v("'simple-mind-map/src/plugins/RainbowLines.js'")]),n._v("\nMindMap.usePlugin(RainbowLines)\n")])]),s("p",[n._v("After registration and instantiation of "),s("code",[n._v("MindMap")]),n._v(", the instance can be obtained through "),s("code",[n._v("mindMap.rainbowLines")]),n._v(".")]),s("h2",[n._v("Method")]),s("h3",[n._v("updateRainLinesConfig(config = {})")]),s("p",[n._v("If you want to modify the rainbow lines after setting them through the instantiation option 'rainbowLinesConfig', you can use this method, option "),s("code",[n._v("config")]),n._v(" is same with "),s("code",[n._v("rainbowLinesConfig")]),n._v("。")]),s("pre",{staticClass:"hljs"},[s("code",[n._v("{\n "),s("span",{staticClass:"hljs-attr"},[n._v("open")]),n._v(": "),s("span",{staticClass:"hljs-literal"},[n._v("false")]),n._v(","),s("span",{staticClass:"hljs-comment"},[n._v("// Is turn on rainbow lines")]),n._v("\n "),s("span",{staticClass:"hljs-attr"},[n._v("colorsList")]),n._v(": []"),s("span",{staticClass:"hljs-comment"},[n._v("// Customize the color list for rainbow lines. If not set, the default color list will be used")]),n._v("\n}\n")])]),s("h3",[n._v("getColorsList()")]),s("p",[n._v("Get a list of currently used rainbow line colors.")]),s("h3",[n._v("getNodeColor(node)")]),s("p",[n._v("Retrieve the rainbow line color corresponding to the specified node instance.")]),s("h3",[n._v("getSecondLayerAncestor(node)")]),s("p",[n._v("Retrieve the second level ancestor node instance of a node instance.")])])}],o={},a=o,l=t("2877"),r=Object(l["a"])(a,i,e,!1,null,null,null);s["default"]=r.exports}}]);
|
||||
2
dist/js/chunk-2d0e5089.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e5089"],{9381:function(e,a,n){"use strict";n.r(a);var o=function(){var e=this;e._self._c;return e._m(0)},d=[function(){var e=this,a=e._self._c;return a("div",[a("h1",[e._v("command instance")]),a("p",[e._v("The "),a("code",[e._v("command")]),e._v(" instance is responsible for adding and executing commands. It includes many built-in commands and can also be added manually. A command refers to an operation that needs to add a copy to the history stack data. The "),a("code",[e._v("mindMap.command")]),e._v(' instance can be obtained through this."')]),a("h2",[e._v("Methods")]),a("h3",[e._v("add(name, fn)")]),a("p",[e._v("Add a command.")]),a("p",[a("code",[e._v("name")]),e._v(": Command name")]),a("p",[a("code",[e._v("fn")]),e._v(": Method to be executed by the command")]),a("h3",[e._v("remove(name, fn)")]),a("p",[e._v("Remove a command.")]),a("p",[a("code",[e._v("name")]),e._v(": Name of the command to be removed")]),a("p",[a("code",[e._v("fn")]),e._v(": Method to be removed, if not provided all methods for the command will be removed")]),a("h3",[e._v("getCopyData()")]),a("p",[e._v("Get a copy of the rendering tree data")]),a("h3",[e._v("clearHistory()")]),a("p",[e._v("Clear the history stack data")])])}],t={},c=t,m=n("2877"),v=Object(m["a"])(c,o,d,!1,null,null,null);a["default"]=v.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e5089"],{9381:function(e,o,a){"use strict";a.r(o);var n=function(){var e=this;e._self._c;return e._m(0)},t=[function(){var e=this,o=e._self._c;return o("div",[o("h1",[e._v("command instance")]),o("p",[e._v("The "),o("code",[e._v("command")]),e._v(" instance is responsible for adding and executing commands. It includes many built-in commands and can also be added manually. A command refers to an operation that needs to add a copy to the history stack data. The "),o("code",[e._v("mindMap.command")]),e._v(' instance can be obtained through this."')]),o("h2",[e._v("Methods")]),o("h3",[e._v("pause()")]),o("blockquote",[o("p",[e._v("v0.9.11+")])]),o("p",[e._v("Pause collecting historical data.")]),o("h3",[e._v("recovery()")]),o("blockquote",[o("p",[e._v("v0.9.11+")])]),o("p",[e._v("Restore the collection of historical data.")]),o("h3",[e._v("add(name, fn)")]),o("p",[e._v("Add a command.")]),o("p",[o("code",[e._v("name")]),e._v(": Command name")]),o("p",[o("code",[e._v("fn")]),e._v(": Method to be executed by the command")]),o("h3",[e._v("remove(name, fn)")]),o("p",[e._v("Remove a command.")]),o("p",[o("code",[e._v("name")]),e._v(": Name of the command to be removed")]),o("p",[o("code",[e._v("fn")]),e._v(": Method to be removed, if not provided all methods for the command will be removed")]),o("h3",[e._v("getCopyData()")]),o("p",[e._v("Get a copy of the rendering tree data")]),o("h3",[e._v("clearHistory()")]),o("p",[e._v("Clear the history stack data")])])}],d={},c=d,v=a("2877"),m=Object(v["a"])(c,n,t,!1,null,null,null);o["default"]=m.exports}}]);
|
||||
1
dist/js/chunk-2d0e96e3.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e96e3"],{"8e00":function(_,v,e){"use strict";e.r(v);var s=function(){var _=this;_._self._c;return _._m(0)},t=[function(){var _=this,v=_._self._c;return v("div",[v("h1",[_._v("Demonstrate 插件")]),v("blockquote",[v("p",[_._v("v0.9.11+")])]),v("p",[v("code",[_._v("Demonstrate")]),_._v("插件提供演示功能。")]),v("p",[_._v("进入演示模式时会自动将容器元素全屏,然后默认聚焦到根节点,可通过键盘方向键的左右来切换上一步和下一步,可通过"),v("code",[_._v("Esc")]),_._v("键退出演示模式。")]),v("p",[_._v("进入演示模式后思维导图所有的快捷键都将无法使用,鼠标也无法操作思维导图。")]),v("h2",[_._v("注册")]),v("pre",{staticClass:"hljs"},[v("code",[v("span",{staticClass:"hljs-keyword"},[_._v("import")]),_._v(" MindMap "),v("span",{staticClass:"hljs-keyword"},[_._v("from")]),_._v(" "),v("span",{staticClass:"hljs-string"},[_._v("'simple-mind-map'")]),_._v("\n"),v("span",{staticClass:"hljs-keyword"},[_._v("import")]),_._v(" Demonstrate "),v("span",{staticClass:"hljs-keyword"},[_._v("from")]),_._v(" "),v("span",{staticClass:"hljs-string"},[_._v("'simple-mind-map/src/plugins/Demonstrate.js'")]),_._v("\n\nMindMap.usePlugin(Demonstrate)\n")])]),v("p",[_._v("注册完且实例化"),v("code",[_._v("MindMap")]),_._v("后可通过"),v("code",[_._v("mindMap.demonstrate")]),_._v("获取到该实例。")]),v("h3",[_._v("配置")]),v("p",[_._v("该插件提供了一些配置项可供配置,可以通过实例化选项"),v("code",[_._v("demonstrateConfig")]),_._v("进行配置。详见【构造函数】篇章的【实例化选项】小节。")]),v("h3",[_._v("事件")]),v("p",[_._v("该插件会派发如下事件:")]),v("p",[v("code",[_._v("exit_demonstrate")]),_._v(":退出演示时触发。")]),v("p",[v("code",[_._v("demonstrate_jump")]),_._v(":跳转时触发。")]),v("p",[_._v("详见【构造函数】篇章的【实例方法】小节"),v("code",[_._v("on")]),_._v("函数。")]),v("h2",[_._v("属性")]),v("h3",[_._v("stepList")]),v("p",[_._v("演示的所有步骤列表。当调用了"),v("code",[_._v("enter")]),_._v("方法后可用。")]),v("h3",[_._v("currentStepIndex")]),v("p",[_._v("当前播放到的步骤索引,从0开始计数。")]),v("h3",[_._v("config")]),v("p",[_._v("插件当前的配置。")]),v("h2",[_._v("方法")]),v("h3",[_._v("enter()")]),v("p",[_._v("进入演示模式,会自动将容器元素全屏。")]),v("h3",[_._v("exit()")]),v("p",[_._v("退出演示模式,通过"),v("code",[_._v("Esc")]),_._v("键也可退出。")]),v("h3",[_._v("prev()")]),v("p",[_._v("上一步。")]),v("h3",[_._v("next()")]),v("p",[_._v("下一步。")]),v("h3",[_._v("jump(index)")]),v("ul",[v("li",[v("code",[_._v("index")]),_._v(":Number,要跳转到的某一步,从0开始计数。")])]),v("p",[_._v("跳转到某一步。")])])}],n={},p=n,a=e("2877"),o=Object(a["a"])(p,s,t,!1,null,null,null);v["default"]=o.exports}}]);
|
||||
2
dist/js/chunk-2d0e9742.js
vendored
2
dist/js/chunk-2d0e9802.js
vendored
2
dist/js/chunk-2d0f026c.js
vendored
2
dist/js/chunk-2d0f0784.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0f0784"],{"9d03":function(e,t,n){"use strict";n.r(t);var i=function(){var e=this;e._self._c;return e._m(0)},d=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit instance")]),t("p",[e._v("Node text editing instance. It can be obtained through "),t("code",[e._v("mindMap.renderer.textEdit")]),e._v(".")]),t("h2",[e._v("Methods")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("Get whether the current text editing box is in a display state, that is, whether it is in a text editing state.")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("Hiding the text editing box will set the content of the current text editing box as node text.")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("Register temporary shortcut keys, which means editing can be completed through the Enter and Tab keys.")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":Node instance to enter for editing")])]),t("p",[e._v("Manually enable node editing. By default, it will enter node editing when double clicking or pressing F2 on the node.")])])}],o={},h=o,r=n("2877"),s=Object(r["a"])(h,i,d,!1,null,null,null);t["default"]=s.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0f0784"],{"9d03":function(e,t,n){"use strict";n.r(t);var i=function(){var e=this;e._self._c;return e._m(0)},d=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit instance")]),t("p",[e._v("Node text editing instance. It can be obtained through "),t("code",[e._v("mindMap.renderer.textEdit")]),e._v(".")]),t("h2",[e._v("Methods")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("Get whether the current text editing box is in a display state, that is, whether it is in a text editing state.")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("Hiding the text editing box will set the content of the current text editing box as node text.")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("Register temporary shortcut keys, which means editing can be completed through the Enter and Tab keys.")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":Node instance to enter for editing")])]),t("p",[e._v("Manually enable node editing. By default, it will enter node editing when double clicking or pressing F2 on the node.")]),t("h3",[e._v("getCurrentEditNode()")]),t("blockquote",[t("p",[e._v("v0.9.8+")])]),t("p",[e._v("Get the node instance currently being edited.")])])}],o={},r=o,h=n("2877"),s=Object(h["a"])(r,i,d,!1,null,null,null);t["default"]=s.exports}}]);
|
||||
2
dist/js/chunk-2d207d0a.js
vendored
2
dist/js/chunk-2d208ffa.js
vendored
2
dist/js/chunk-2d20f137.js
vendored
2
dist/js/chunk-2d216004.js
vendored
2
dist/js/chunk-2d216037.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216037"],{c13f:function(s,t,n){"use strict";n.r(t);var a=function(){var s=this;s._self._c;return s._m(0)},e=[function(){var s=this,t=s._self._c;return t("div",[t("h1",[s._v("开启节点自由拖拽")]),t("blockquote",[t("p",[s._v("节点自由拖拽的连线可能不会符合你的预期,这个问题基本上不会改,所以自由拖拽功能不建议使用。")])]),t("p",[s._v("节点支持自由拖拽,也就是可以把它拖动到你指定的位置,默认是不开启的,如果需要开启可以在实例化"),t("code",[s._v("simple-mind-map")]),s._v("时传入开启的选项:")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-keyword"},[s._v("new")]),s._v(" MindMap({\n "),t("span",{staticClass:"hljs-comment"},[s._v("// ...")]),s._v("\n "),t("span",{staticClass:"hljs-attr"},[s._v("enableFreeDrag")]),s._v(": "),t("span",{staticClass:"hljs-literal"},[s._v("true")]),s._v("\n})\n")])]),t("p",[s._v("也可以动态切换是否开启:")]),t("pre",{staticClass:"hljs"},[t("code",[s._v("mindMap.updateConfig({\n "),t("span",{staticClass:"hljs-attr"},[s._v("enableFreeDrag")]),s._v(": "),t("span",{staticClass:"hljs-literal"},[s._v("true")]),t("span",{staticClass:"hljs-comment"},[s._v("// false")]),s._v("\n})\n")])]),t("p",[s._v("自由拖拽很容器将节点拖的乱七八糟,所以也可以通过命令回到默认的位置:")]),t("pre",{staticClass:"hljs"},[t("code",[s._v("mindMap.execCommand("),t("span",{staticClass:"hljs-string"},[s._v("'RESET_LAYOUT'")]),s._v(")\n")])]),t("p",[s._v("也可以使用快捷键"),t("code",[s._v("Ctrl + L")]),s._v("来恢复。")]),t("h2",[s._v("完整示例")]),t("iframe",{staticStyle:{width:"100%",height:"455px",border:"none"},attrs:{src:"https://wanglin2.github.io/playground/#eNrFVd1uG0UUfpVhEVoH2buuxJVxSksbJKSEolAuUDeqxrtje8rszLIzm8SyVgoF1EKIFASiFdxAVQFCQFWBEE2oeJmsnb4FZ3b2L44veldLa82c853znZnzM1PrchQ52wmxelZf+jGNFJJEJdFFj9MwErFCUxSTYRsJviESrkjQRnKMGRM7m2SIUjSMRYhs8GBXFhuUBxs4MirPkiBmpBOCtBPiyLM8jpDHGVFIyzRyFfGEMSN3XZQ9/Tr7/GD2397sr+P5d5+efnF7fvtJdvfe6Y+/eNwXXCqEfUW3yTsiIBKs64haN7ZWPF6iqLwWEf5WTMjVGI8ACEdpDTGTJAdpqrt3ZgcPZvcfZYc/Zf/uZYePTu/8Ov/m8Wz/29n+09KPEqMRIw0/rRW0ehFNdcRnOZxtzBICiJeWyTW+OLOTRAFW5IrgQzpq5Z4QIhwPap7eUt8QuEoh/LQ4wezjB9nDg2fH90//eJj980n2eK+MOiaQynU8EYk6G3IZAtkl/hURhpgHLXtz7b216zfXL39w7f3rdum/ynrrvLnOGtkps10dgfVQIPwkJFw5I6LWGNHLNydvA0dhCYdWmHIS2yttYwVXgXvGu/55lhZ4VkNkxIrsKi32rNkPT0xdmHrSv7RwpoH+mLIgJlyDb9Q+FtwtZVlkOjn6cn708yLZWcIlpFu1rol7QRGUy0JW2lFO1aYQSjfSu0JSRQUHS5uRobLbyPYhdZCmrQK+WJ95K2lVuvK66V6EoCTn33+VHf5m4jVdfHK0f3L8d7P2BG/ZHFhvmk4GspbetpH+X6dS1fWGmt1eNViJM/RQr/D1XTPDYHrBRhEYPNBjsEOoH9Bt5DMs5apnFUFcJaHwrFxdAGhQa6sqBUjfBW0TWHpSQrAB1hCj9FR/kCglOLrkM+p/mEOawwOQ0+niYHoD2dlnfz6793tz+NioB+JzM8lGadp3DUlBCjEtkjZ6HxjPz4gFD/X5ylXfbVwfbKWaMHOTl4ox71mOa2Z7MQIcIkPHl9KzqmJwGjddJnOHBmrcQxe63VdyHEJRVXkxAUZIda7Ii1Z/Ly9mpHRVG+KBFCxRxhAhXb891C12SkT15jz9mNDRGOCvdbvRbsm8nPfVkjnE8YgCb+k1wkFAObSEEVShO0WBPGfEF8oIiqCrPTiE4s5zYLUtkwH9mjq3pODweOfuvUIBGagmimfB22zGiOPC0olhntOQ6GR1BrHYkSQGJ55VtPiS91rbjpWKZM912e5HXE6kI6Ts+LwzIPQWHNvBjE4S7kvHF6ELnUGUXFIbmqY4TGql/wPpBfrv"}})])}],l={},r=l,i=n("2877"),p=Object(i["a"])(r,a,e,!1,null,null,null);t["default"]=p.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216037"],{c13f:function(s,t,a){"use strict";a.r(t);var n=function(){var s=this;s._self._c;return s._m(0)},e=[function(){var s=this,t=s._self._c;return t("div",[t("h1",[s._v("开启节点自由拖拽")]),t("blockquote",[t("p",[s._v("节点自由拖拽的连线可能不会符合你的预期,这个问题基本上不会改,所以自由拖拽功能不建议使用。")])]),t("blockquote",[t("p",[s._v("另外不要把节点拖拽和自由拖拽搞混,节点拖拽指拖动节点到其他节点上成为其子节点或兄弟节点,自由拖拽只是可以把节点放置在你拖动到的画布位置,并不能改变节点的层级。")])]),t("blockquote",[t("p",[s._v("最后要注意这两个功能都需要先注册Drag插件。")])]),t("p",[s._v("节点支持自由拖拽,也就是可以把它拖动到你指定的位置,默认是不开启的,如果需要开启可以在实例化"),t("code",[s._v("simple-mind-map")]),s._v("时传入开启的选项:")]),t("pre",{staticClass:"hljs"},[t("code",[t("span",{staticClass:"hljs-keyword"},[s._v("new")]),s._v(" MindMap({\n "),t("span",{staticClass:"hljs-comment"},[s._v("// ...")]),s._v("\n "),t("span",{staticClass:"hljs-attr"},[s._v("enableFreeDrag")]),s._v(": "),t("span",{staticClass:"hljs-literal"},[s._v("true")]),s._v("\n})\n")])]),t("p",[s._v("也可以动态切换是否开启:")]),t("pre",{staticClass:"hljs"},[t("code",[s._v("mindMap.updateConfig({\n "),t("span",{staticClass:"hljs-attr"},[s._v("enableFreeDrag")]),s._v(": "),t("span",{staticClass:"hljs-literal"},[s._v("true")]),t("span",{staticClass:"hljs-comment"},[s._v("// false")]),s._v("\n})\n")])]),t("p",[s._v("自由拖拽很容器将节点拖的乱七八糟,所以也可以通过命令回到默认的位置:")]),t("pre",{staticClass:"hljs"},[t("code",[s._v("mindMap.execCommand("),t("span",{staticClass:"hljs-string"},[s._v("'RESET_LAYOUT'")]),s._v(")\n")])]),t("p",[s._v("也可以使用快捷键"),t("code",[s._v("Ctrl + L")]),s._v("来恢复。")]),t("h2",[s._v("完整示例")]),t("iframe",{staticStyle:{width:"100%",height:"455px",border:"none"},attrs:{src:"https://wanglin2.github.io/playground/#eNrFVd1uG0UUfpVhEVoH2buuxJVxSksbJKSEolAuUDeqxrtje8rszLIzm8SyVgoF1EKIFASiFdxAVQFCQFWBEE2oeJmsnb4FZ3b2L44veldLa82c853znZnzM1PrchQ52wmxelZf+jGNFJJEJdFFj9MwErFCUxSTYRsJviESrkjQRnKMGRM7m2SIUjSMRYhs8GBXFhuUBxs4MirPkiBmpBOCtBPiyLM8jpDHGVFIyzRyFfGEMSN3XZQ9/Tr7/GD2397sr+P5d5+efnF7fvtJdvfe6Y+/eNwXXCqEfUW3yTsiIBKs64haN7ZWPF6iqLwWEf5WTMjVGI8ACEdpDTGTJAdpqrt3ZgcPZvcfZYc/Zf/uZYePTu/8Ov/m8Wz/29n+09KPEqMRIw0/rRW0ehFNdcRnOZxtzBICiJeWyTW+OLOTRAFW5IrgQzpq5Z4QIhwPap7eUt8QuEoh/LQ4wezjB9nDg2fH90//eJj980n2eK+MOiaQynU8EYk6G3IZAtkl/hURhpgHLXtz7b216zfXL39w7f3rdum/ynrrvLnOGtkps10dgfVQIPwkJFw5I6LWGNHLNydvA0dhCYdWmHIS2yttYwVXgXvGu/55lhZ4VkNkxIrsKi32rNkPT0xdmHrSv7RwpoH+mLIgJlyDb9Q+FtwtZVlkOjn6cn708yLZWcIlpFu1rol7QRGUy0JW2lFO1aYQSjfSu0JSRQUHS5uRobLbyPYhdZCmrQK+WJ95K2lVuvK66V6EoCTn33+VHf5m4jVdfHK0f3L8d7P2BG/ZHFhvmk4GspbetpH+X6dS1fWGmt1eNViJM/RQr/D1XTPDYHrBRhEYPNBjsEOoH9Bt5DMs5apnFUFcJaHwrFxdAGhQa6sqBUjfBW0TWHpSQrAB1hCj9FR/kCglOLrkM+p/mEOawwOQ0+niYHoD2dlnfz6793tz+NioB+JzM8lGadp3DUlBCjEtkjZ6HxjPz4gFD/X5ylXfbVwfbKWaMHOTl4ox71mOa2Z7MQIcIkPHl9KzqmJwGjddJnOHBmrcQxe63VdyHEJRVXkxAUZIda7Ii1Z/Ly9mpHRVG+KBFCxRxhAhXb891C12SkT15jz9mNDRGOCvdbvRbsm8nPfVkjnE8YgCb+k1wkFAObSEEVShO0WBPGfEF8oIiqCrPTiE4s5zYLUtkwH9mjq3pODweOfuvUIBGagmimfB22zGiOPC0olhntOQ6GR1BrHYkSQGJ55VtPiS91rbjpWKZM912e5HXE6kI6Ts+LwzIPQWHNvBjE4S7kvHF6ELnUGUXFIbmqY4TGql/wPpBfrv"}})])}],l={},r=l,i=a("2877"),p=Object(i["a"])(r,n,e,!1,null,null,null);t["default"]=p.exports}}]);
|
||||
2
dist/js/chunk-2d216f87.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216f87"],{c576:function(e,t,n){"use strict";n.r(t);var _=function(){var e=this;e._self._c;return e._m(0)},v=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit 实例")]),t("p",[e._v("节点文本编辑实例。可以通过"),t("code",[e._v("mindMap.renderer.textEdit")]),e._v("获取到。")]),t("h2",[e._v("方法")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("获取当前文本编辑框是否处于显示状态,也就是是否处在文本编辑状态。")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("隐藏文本编辑框,会将当前文本编辑框中的内容设置为节点文本。")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("注册临时快捷键,也就是可以通过 Enter 键和 Tab 键完成编辑。")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":要进入编辑的节点实例")])]),t("p",[e._v("手动开启节点编辑。默认会在节点双击、按 F2 时进入节点编辑。")])])}],i={},r=i,d=n("2877"),o=Object(d["a"])(r,_,v,!1,null,null,null);t["default"]=o.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216f87"],{c576:function(e,t,v){"use strict";v.r(t);var _=function(){var e=this;e._self._c;return e._m(0)},n=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit 实例")]),t("p",[e._v("节点文本编辑实例。可以通过"),t("code",[e._v("mindMap.renderer.textEdit")]),e._v("获取到。")]),t("h2",[e._v("方法")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("获取当前文本编辑框是否处于显示状态,也就是是否处在文本编辑状态。")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("隐藏文本编辑框,会将当前文本编辑框中的内容设置为节点文本。")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("注册临时快捷键,也就是可以通过 Enter 键和 Tab 键完成编辑。")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":要进入编辑的节点实例")])]),t("p",[e._v("手动开启节点编辑。默认会在节点双击、按 F2 时进入节点编辑。")]),t("h3",[e._v("getCurrentEditNode()")]),t("blockquote",[t("p",[e._v("v0.9.8+")])]),t("p",[e._v("获取当前正在编辑中的节点实例。")])])}],i={},o=i,r=v("2877"),d=Object(r["a"])(o,_,n,!1,null,null,null);t["default"]=d.exports}}]);
|
||||
2
dist/js/chunk-2d217907.js
vendored
1
dist/js/chunk-2d21f249.js
vendored
Normal file
2
dist/js/chunk-2d22c6c5.js
vendored
83
dist/js/chunk-5c17f390.js
vendored
1
dist/js/chunk-66b27c16.js
vendored
Normal file
1
dist/js/chunk-792bedf8.js
vendored
1
dist/js/chunk-7babbe51.js
vendored
76
dist/js/chunk-8bd0d5d4.js
vendored
Normal file
36
dist/js/chunk-vendors.js
vendored
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
|
||||
window.externalPublicPath = './dist/'
|
||||
// 接管应用
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?e76cdbdcf61c14ff4fc9" rel="stylesheet"><link href="dist/css/app.css?e76cdbdcf61c14ff4fc9" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?78d97b280dca5d10b8bc" rel="stylesheet"><link href="dist/css/app.css?78d97b280dca5d10b8bc" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
@@ -66,4 +66,4 @@
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}</script><script src="dist/js/chunk-vendors.js?e76cdbdcf61c14ff4fc9"></script><script src="dist/js/app.js?e76cdbdcf61c14ff4fc9"></script></body></html>
|
||||
}</script><script src="dist/js/chunk-vendors.js?78d97b280dca5d10b8bc"></script><script src="dist/js/app.js?78d97b280dca5d10b8bc"></script></body></html>
|
||||
30
nginx.conf
Normal file
@@ -0,0 +1,30 @@
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log main;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
location / {
|
||||
root /app;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import Search from './src/plugins/Search.js'
|
||||
import Painter from './src/plugins/Painter.js'
|
||||
import Scrollbar from './src/plugins/Scrollbar.js'
|
||||
import Formula from './src/plugins/Formula.js'
|
||||
import RainbowLines from './src/plugins/RainbowLines.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
@@ -28,7 +29,7 @@ MindMap.iconList = icons.nodeIconList
|
||||
MindMap.constants = constants
|
||||
MindMap.themes = themes
|
||||
MindMap.defaultTheme = defaultTheme
|
||||
MindMap.version = '0.9.7'
|
||||
MindMap.version = '0.9.12'
|
||||
|
||||
MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Watermark)
|
||||
@@ -46,5 +47,6 @@ MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Painter)
|
||||
.usePlugin(Scrollbar)
|
||||
.usePlugin(Formula)
|
||||
.usePlugin(RainbowLines)
|
||||
|
||||
export default MindMap
|
||||
|
||||
@@ -10,12 +10,17 @@ import BatchExecution from './src/utils/BatchExecution'
|
||||
import {
|
||||
layoutValueList,
|
||||
CONSTANTS,
|
||||
commonCaches,
|
||||
ERROR_TYPES,
|
||||
cssContent
|
||||
} from './src/constants/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone, getType, getObjectChangedProps } from './src/utils'
|
||||
import {
|
||||
simpleDeepClone,
|
||||
getObjectChangedProps,
|
||||
isUndef,
|
||||
handleGetSvgDataExtraContent,
|
||||
getNodeTreeBoundingRect
|
||||
} from './src/utils'
|
||||
import defaultTheme, {
|
||||
checkIsNodeSizeIndependenceConfig
|
||||
} from './src/themes/default'
|
||||
@@ -31,6 +36,8 @@ class MindMap {
|
||||
constructor(opt = {}) {
|
||||
// 合并选项
|
||||
this.opt = this.handleOpt(merge(defaultOpt, opt))
|
||||
// 预处理节点数据
|
||||
this.opt.data = this.handleData(this.opt.data)
|
||||
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
@@ -39,6 +46,10 @@ class MindMap {
|
||||
// 获取容器尺寸位置信息
|
||||
this.getElRectInfo()
|
||||
|
||||
// 画布初始大小
|
||||
this.initWidth = this.width
|
||||
this.initHeight = this.height
|
||||
|
||||
// 添加css
|
||||
this.cssEl = null
|
||||
this.addCss()
|
||||
@@ -88,14 +99,12 @@ class MindMap {
|
||||
// 初始渲染
|
||||
this.render(this.opt.fit ? () => this.view.fit() : () => {})
|
||||
setTimeout(() => {
|
||||
this.command.addHistory()
|
||||
if (this.opt.data) this.command.addHistory()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 配置参数处理
|
||||
handleOpt(opt) {
|
||||
// 深拷贝一份节点数据
|
||||
opt.data = simpleDeepClone(opt.data || {})
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(opt.layout)) {
|
||||
opt.layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
@@ -105,16 +114,32 @@ class MindMap {
|
||||
return opt
|
||||
}
|
||||
|
||||
// 预处理节点数据
|
||||
handleData(data) {
|
||||
if (isUndef(data) || Object.keys(data).length <= 0) return null
|
||||
data = simpleDeepClone(data || {})
|
||||
// 根节点不能收起
|
||||
if (data.data && !data.data.expand) {
|
||||
data.data.expand = true
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 创建容器元素
|
||||
initContainer() {
|
||||
const { associativeLineIsAlwaysAboveNode } = this.opt
|
||||
// 给容器元素添加一个类名
|
||||
this.el.classList.add('smm-mind-map-container')
|
||||
// 节点关联线容器
|
||||
const createAssociativeLineDraw = () => {
|
||||
this.associativeLineDraw = this.draw.group()
|
||||
this.associativeLineDraw.addClass('smm-associative-line-container')
|
||||
}
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.svg = SVG()
|
||||
.addTo(this.el)
|
||||
.size(this.width, this.height)
|
||||
|
||||
// 容器
|
||||
this.draw = this.svg.group()
|
||||
this.draw.addClass('smm-container')
|
||||
@@ -207,19 +232,10 @@ class MindMap {
|
||||
|
||||
// 初始化缓存数据
|
||||
initCache() {
|
||||
Object.keys(commonCaches).forEach(key => {
|
||||
let type = getType(commonCaches[key])
|
||||
let value = ''
|
||||
switch (type) {
|
||||
case 'Boolean':
|
||||
value = false
|
||||
break
|
||||
default:
|
||||
value = null
|
||||
break
|
||||
}
|
||||
commonCaches[key] = value
|
||||
})
|
||||
this.commonCaches = {
|
||||
measureCustomNodeContentSizeEl: null,
|
||||
measureRichtextNodeTextSizeEl: null
|
||||
}
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
@@ -302,9 +318,17 @@ class MindMap {
|
||||
this.command.exec(...args)
|
||||
}
|
||||
|
||||
// 更新画布数据,如果新的数据是在当前画布节点数据基础上增删改查后形成的,那么可以使用该方法来更新画布数据
|
||||
updateData(data) {
|
||||
this.renderer.setData(data)
|
||||
this.render()
|
||||
this.command.addHistory()
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,纯节点数据
|
||||
setData(data) {
|
||||
data = simpleDeepClone(data || {})
|
||||
data = this.handleData(data)
|
||||
this.opt.data = data
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.command.addHistory()
|
||||
@@ -386,7 +410,19 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false } = {}) {
|
||||
getSvgData({
|
||||
paddingX = 0,
|
||||
paddingY = 0,
|
||||
ignoreWatermark = false,
|
||||
addContentToHeader,
|
||||
addContentToFooter,
|
||||
node
|
||||
} = {}) {
|
||||
const { cssTextList, header, headerHeight, footer, footerHeight } =
|
||||
handleGetSvgDataExtraContent({
|
||||
addContentToHeader,
|
||||
addContentToFooter
|
||||
})
|
||||
const svg = this.svg
|
||||
const draw = this.draw
|
||||
// 保存原始信息
|
||||
@@ -398,9 +434,21 @@ class MindMap {
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox()
|
||||
// 需要裁减的区域
|
||||
let clipData = null
|
||||
if (node) {
|
||||
clipData = getNodeTreeBoundingRect(
|
||||
node,
|
||||
rect.x,
|
||||
rect.y,
|
||||
paddingX,
|
||||
paddingY
|
||||
)
|
||||
}
|
||||
// 内边距
|
||||
const fixHeight = 0
|
||||
rect.width += paddingX * 2
|
||||
rect.height += paddingY * 2
|
||||
rect.height += paddingY * 2 + fixHeight + headerHeight + footerHeight
|
||||
draw.translate(paddingX, paddingY)
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
@@ -438,7 +486,21 @@ class MindMap {
|
||||
this.watermark.isInExport = false
|
||||
}
|
||||
// 添加必要的样式
|
||||
clone.add(SVG(`<style>${cssContent}</style>`))
|
||||
;[cssContent, ...cssTextList].forEach(s => {
|
||||
clone.add(SVG(`<style>${s}</style>`))
|
||||
})
|
||||
// 附加内容
|
||||
if (header && headerHeight > 0) {
|
||||
clone.findOne('.smm-container').translate(0, headerHeight)
|
||||
header.width(rect.width)
|
||||
header.y(paddingY)
|
||||
clone.add(header, 0)
|
||||
}
|
||||
if (footer && footerHeight > 0) {
|
||||
footer.width(rect.width)
|
||||
footer.y(rect.height - paddingY - footerHeight)
|
||||
clone.add(footer)
|
||||
}
|
||||
// 修正defs里定义的元素的id,因为clone时defs里的元素的id会继续递增,导致和内容中引用的id对不上
|
||||
const defs = svg.find('defs')
|
||||
const defs2 = clone.find('defs')
|
||||
@@ -462,6 +524,7 @@ class MindMap {
|
||||
return {
|
||||
svg: clone, // 思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组)
|
||||
svgHTML: clone.svg(), // svg字符串
|
||||
clipData,
|
||||
rect: {
|
||||
...rect, // 思维导图图形未缩放时的位置尺寸等信息
|
||||
ratio: rect.width / rect.height // 思维导图图形的宽高比
|
||||
@@ -529,6 +592,8 @@ class MindMap {
|
||||
this.svg.remove()
|
||||
// 去除给容器元素设置的背景样式
|
||||
Style.removeBackgroundStyle(this.el)
|
||||
// 移除给容器元素添加的类名
|
||||
this.el.classList.remove('smm-mind-map-container')
|
||||
this.el.innerHTML = ''
|
||||
this.el = null
|
||||
this.removeCss()
|
||||
|
||||
1421
simple-mind-map/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.12",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -38,6 +38,7 @@
|
||||
"quill": "^1.3.6",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^7.5.9",
|
||||
"xml-js": "^1.6.11",
|
||||
"y-webrtc": "^10.2.5",
|
||||
"yjs": "^13.6.8"
|
||||
|
||||
@@ -312,15 +312,11 @@ export const nodeDataNoStylePropList = [
|
||||
'associativeLineTargets',
|
||||
'associativeLineTargetControlOffsets',
|
||||
'associativeLinePoint',
|
||||
'associativeLineText'
|
||||
'associativeLineText',
|
||||
'attachmentUrl',
|
||||
'attachmentName'
|
||||
]
|
||||
|
||||
// 数据缓存
|
||||
export const commonCaches = {
|
||||
measureCustomNodeContentSizeEl: null,
|
||||
measureRichtextNodeTextSizeEl: null
|
||||
}
|
||||
|
||||
// 错误类型
|
||||
export const ERROR_TYPES = {
|
||||
READ_CLIPBOARD_ERROR: 'read_clipboard_error',
|
||||
@@ -346,7 +342,7 @@ export const cssContent = `
|
||||
display: block;
|
||||
}
|
||||
|
||||
.smm-node.active .smm-hover-node{
|
||||
.smm-node.active .smm-hover-node, .smm-node-highlight .smm-hover-node{
|
||||
display: block;
|
||||
opacity: 1;
|
||||
stroke-width: 2;
|
||||
|
||||
@@ -275,5 +275,56 @@ export const defaultOpt = {
|
||||
customCreateNodePolygon: null,
|
||||
// 自定义转换节点连线路径的方法
|
||||
// 接收svg path字符串,返回转换后的svg path字符串
|
||||
customTransformNodeLinePath: null
|
||||
customTransformNodeLinePath: null,
|
||||
// 是否仅搜索当前渲染的节点,被收起的节点不会被搜索到
|
||||
isOnlySearchCurrentRenderNodes: false,
|
||||
// 协同编辑时,同一个节点不能同时被多人选中
|
||||
onlyOneEnableActiveNodeOnCooperate: false,
|
||||
// 协同编辑时,节点操作即将更新到其他客户端前的生命周期函数
|
||||
// 函数接收一个对象作为参数:
|
||||
/*
|
||||
{
|
||||
type: createOrUpdate(创建节点或更新节点)、delete(删除节点)
|
||||
data: 1.当type=createOrUpdate时,代表被创建或被更新的节点数据,即将同步到其他客户端,所以你可以修改该数据;2.当type=delete时,代表被删除的节点数据
|
||||
}
|
||||
*/
|
||||
beforeCooperateUpdate: null,
|
||||
// 快捷键操作即将执行前的生命周期函数,返回true可以阻止操作执行
|
||||
// 函数接收两个参数:key(快捷键)、activeNodeList(当前激活的节点列表)
|
||||
beforeShortcutRun: null,
|
||||
// 彩虹线条配置,需要先注册RainbowLines插件
|
||||
rainbowLinesConfig: {
|
||||
open: false, // 是否开启彩虹线条
|
||||
colorsList: [] // 自定义彩虹线条的颜色列表,如果不设置,会使用默认颜色列表
|
||||
/*
|
||||
[
|
||||
'rgb(255, 213, 73)',
|
||||
'rgb(255, 136, 126)',
|
||||
'rgb(107, 225, 141)',
|
||||
'rgb(151, 171, 255)',
|
||||
'rgb(129, 220, 242)',
|
||||
'rgb(255, 163, 125)',
|
||||
'rgb(152, 132, 234)'
|
||||
]
|
||||
*/
|
||||
},
|
||||
// 导出png、svg、pdf时在头部和尾部添加自定义内容
|
||||
// 可传递一个函数,这个函数可以返回null代表不添加内容,也可以返回如下数据:
|
||||
/*
|
||||
{
|
||||
el,// 要追加的自定义DOM节点,样式可内联
|
||||
cssText,// 可选,如果样式不想内联,可以传递该值,一个css字符串
|
||||
height: 50// 返回的DOM节点的高度,必须传递
|
||||
}
|
||||
*/
|
||||
addContentToHeader: null,
|
||||
addContentToFooter: null,
|
||||
// 演示插件配置
|
||||
demonstrateConfig: null,
|
||||
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
|
||||
resetScaleOnMoveNodeToCenter: false,
|
||||
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
|
||||
createNodePrefixContent: null,
|
||||
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
|
||||
createNodePostfixContent: null
|
||||
}
|
||||
|
||||
@@ -23,6 +23,18 @@ class Command {
|
||||
this.mindMap.opt.addHistoryTime,
|
||||
this
|
||||
)
|
||||
// 是否暂停收集历史数据
|
||||
this.isPause = false
|
||||
}
|
||||
|
||||
// 暂停收集历史数据
|
||||
pause() {
|
||||
this.isPause = true
|
||||
}
|
||||
|
||||
// 恢复收集历史数据
|
||||
recovery() {
|
||||
this.isPause = false
|
||||
}
|
||||
|
||||
// 清空历史数据
|
||||
@@ -88,13 +100,14 @@ class Command {
|
||||
|
||||
// 添加回退数据
|
||||
addHistory() {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
if (this.mindMap.opt.readonly || this.isPause) {
|
||||
return
|
||||
}
|
||||
const lastData =
|
||||
this.history.length > 0 ? this.history[this.history.length - 1] : null
|
||||
const data = this.getCopyData()
|
||||
// 此次数据和上次一样则不重复添加
|
||||
if (lastData === data) return
|
||||
if (lastData && JSON.stringify(lastData) === JSON.stringify(data)) {
|
||||
return
|
||||
}
|
||||
@@ -158,6 +171,7 @@ class Command {
|
||||
|
||||
// 获取渲染树数据副本
|
||||
getCopyData() {
|
||||
if (!this.mindMap.renderer.renderTree) return null
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -67,9 +67,10 @@ export default class KeyCommand {
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
const { enableShortcutOnlyWhenMouseInSvg, beforeShortcutRun } = this.mindMap.opt
|
||||
if (
|
||||
this.isPause ||
|
||||
(this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
|
||||
(enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -80,6 +81,10 @@ export default class KeyCommand {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
if (typeof beforeShortcutRun === 'function') {
|
||||
const isStop = beforeShortcutRun(key, [...this.mindMap.renderer.activeNodeList])
|
||||
if (isStop) return
|
||||
}
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
@@ -30,7 +30,8 @@ import {
|
||||
createSmmFormatData,
|
||||
checkSmmFormatData,
|
||||
checkIsNodeStyleDataKey,
|
||||
removeRichTextStyes
|
||||
removeRichTextStyes,
|
||||
formatGetNodeGeneralization
|
||||
} from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../themes/default'
|
||||
@@ -65,7 +66,9 @@ class Render {
|
||||
this.mindMap = opt.mindMap
|
||||
this.themeConfig = this.mindMap.themeConfig
|
||||
// 渲染树,操作过程中修改的都是这里的数据
|
||||
this.renderTree = merge({}, this.mindMap.opt.data || {})
|
||||
this.renderTree = this.mindMap.opt.data
|
||||
? merge({}, this.mindMap.opt.data)
|
||||
: null
|
||||
// 是否重新渲染
|
||||
this.reRender = false
|
||||
// 是否正在渲染中
|
||||
@@ -117,7 +120,7 @@ class Render {
|
||||
// 重新设置思维导图数据
|
||||
setData(data) {
|
||||
if (this.mindMap.richText) {
|
||||
this.renderTree = this.mindMap.richText.handleSetData(data)
|
||||
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
|
||||
} else {
|
||||
this.renderTree = data
|
||||
}
|
||||
@@ -246,6 +249,9 @@ class Render {
|
||||
// 设置节点备注
|
||||
this.setNodeNote = this.setNodeNote.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_NOTE', this.setNodeNote)
|
||||
// 设置节点附件
|
||||
this.setNodeAttachment = this.setNodeAttachment.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_ATTACHMENT', this.setNodeAttachment)
|
||||
// 设置节点标签
|
||||
this.setNodeTag = this.setNodeTag.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag)
|
||||
@@ -290,6 +296,10 @@ class Render {
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE')
|
||||
})
|
||||
// 插入下级节点
|
||||
this.mindMap.keyCommand.addShortcut('Insert', () => {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE')
|
||||
})
|
||||
// 插入同级节点
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.mindMap.execCommand('INSERT_NODE')
|
||||
@@ -326,7 +336,7 @@ class Render {
|
||||
})
|
||||
// 一键整理布局
|
||||
this.mindMap.keyCommand.addShortcut('Control+l', () => {
|
||||
this.mindMap.execCommand('RESET_LAYOUT', this.resetLayout)
|
||||
this.mindMap.execCommand('RESET_LAYOUT')
|
||||
})
|
||||
// 上移节点
|
||||
this.mindMap.keyCommand.addShortcut('Control+Up', () => {
|
||||
@@ -432,6 +442,12 @@ class Render {
|
||||
if (this.reRender) {
|
||||
this.clearActiveNodeList()
|
||||
}
|
||||
// 如果没有节点数据
|
||||
if (!this.renderTree) {
|
||||
this.isRendering = false
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
return
|
||||
}
|
||||
// 计算布局
|
||||
this.layout.doLayout(root => {
|
||||
// 删除本次渲染时不再需要的节点
|
||||
@@ -476,6 +492,7 @@ class Render {
|
||||
|
||||
// 给当前被收起来的节点数据添加文本复位标志
|
||||
resetUnExpandNodeStyle() {
|
||||
if (!this.renderTree) return
|
||||
walk(this.renderTree, null, node => {
|
||||
if (!node.data.expand) {
|
||||
walk(node, null, node2 => {
|
||||
@@ -504,9 +521,17 @@ class Render {
|
||||
}
|
||||
|
||||
// 添加节点到激活列表里
|
||||
addNodeToActiveList(node) {
|
||||
addNodeToActiveList(node, notEmitBeforeNodeActiveEvent = false) {
|
||||
if (
|
||||
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
|
||||
node.userList.length > 0
|
||||
)
|
||||
return
|
||||
const index = this.findActiveNodeIndex(node)
|
||||
if (index === -1) {
|
||||
if (!notEmitBeforeNodeActiveEvent) {
|
||||
this.mindMap.emit('before_node_active', node, this.activeNodeList)
|
||||
}
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', node, true)
|
||||
this.activeNodeList.push(node)
|
||||
}
|
||||
@@ -649,7 +674,7 @@ class Render {
|
||||
uid: createUid(),
|
||||
...(appointData || {})
|
||||
},
|
||||
children: [...createUidForAppointNodes(appointChildren, true)]
|
||||
children: [...createUidForAppointNodes(appointChildren)]
|
||||
}
|
||||
parent.nodeData.children.splice(index + 1, 0, newNodeData)
|
||||
})
|
||||
@@ -685,10 +710,7 @@ class Render {
|
||||
const parent = node.parent
|
||||
// 计算插入位置
|
||||
const index = getNodeDataIndex(node)
|
||||
const newNodeList = createUidForAppointNodes(
|
||||
simpleDeepClone(nodeList),
|
||||
true
|
||||
)
|
||||
const newNodeList = createUidForAppointNodes(simpleDeepClone(nodeList))
|
||||
parent.nodeData.children.splice(index + 1, 0, ...newNodeList)
|
||||
})
|
||||
if (focusNewNode) {
|
||||
@@ -748,7 +770,7 @@ class Render {
|
||||
...params,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: [...createUidForAppointNodes(appointChildren, true)]
|
||||
children: [...createUidForAppointNodes(appointChildren)]
|
||||
}
|
||||
node.nodeData.children.push(newNode)
|
||||
// 插入子节点时自动展开子节点
|
||||
@@ -788,7 +810,7 @@ class Render {
|
||||
if (!node.nodeData.children) {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
childList = createUidForAppointNodes(childList, true)
|
||||
childList = createUidForAppointNodes(childList)
|
||||
node.nodeData.children.push(...childList)
|
||||
// 插入子节点时自动展开子节点
|
||||
node.setData({
|
||||
@@ -843,6 +865,9 @@ class Render {
|
||||
},
|
||||
children: [node.nodeData]
|
||||
}
|
||||
node.setData({
|
||||
resetRichText: true
|
||||
})
|
||||
const parent = node.parent
|
||||
// 获取当前节点所在位置
|
||||
const index = getNodeDataIndex(node)
|
||||
@@ -963,12 +988,14 @@ class Render {
|
||||
})
|
||||
} else {
|
||||
// 否则遍历整棵树
|
||||
if (!this.renderTree) return
|
||||
walk(this.renderTree, null, node => {
|
||||
const _hasCustomStyles = this._handleRemoveCustomStyles(node.data)
|
||||
if (_hasCustomStyles) hasCustomStyles = true
|
||||
// 不要忘记概要节点
|
||||
if (node.data.generalization && node.data.generalization.length > 0) {
|
||||
node.data.generalization.forEach(generalizationData => {
|
||||
const generalizationList = formatGetNodeGeneralization(node.data)
|
||||
if (generalizationList.length > 0) {
|
||||
generalizationList.forEach(generalizationData => {
|
||||
const _hasCustomStyles =
|
||||
this._handleRemoveCustomStyles(generalizationData)
|
||||
if (_hasCustomStyles) hasCustomStyles = true
|
||||
@@ -1214,9 +1241,17 @@ class Render {
|
||||
root.nodeData.children = []
|
||||
} else {
|
||||
// 如果只选中了一个节点,删除后激活其兄弟节点或者父节点
|
||||
needActiveNode = this.getNextActiveNode()
|
||||
needActiveNode = this.getNextActiveNode(list)
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let node = list[i]
|
||||
const node = list[i]
|
||||
const currentEditNode = this.textEdit.getCurrentEditNode()
|
||||
if (
|
||||
currentEditNode &&
|
||||
currentEditNode.getData('uid') === node.getData('uid')
|
||||
) {
|
||||
// 如果当前节点正在编辑中,那么先完成编辑
|
||||
this.textEdit.hideEditTextBox()
|
||||
}
|
||||
if (isAppointNodes) list.splice(i, 1)
|
||||
if (node.isGeneralization) {
|
||||
this.deleteNodeGeneralization(node)
|
||||
@@ -1261,13 +1296,13 @@ class Render {
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
// 删除节点后需要激活的节点,如果只选中了一个节点,删除后激活其兄弟节点或者父节点
|
||||
let needActiveNode = this.getNextActiveNode()
|
||||
let isAppointNodes = appointNodes.length > 0
|
||||
let list = isAppointNodes ? appointNodes : this.activeNodeList
|
||||
list = list.filter(node => {
|
||||
return !node.isRoot
|
||||
})
|
||||
// 删除节点后需要激活的节点,如果只选中了一个节点,删除后激活其兄弟节点或者父节点
|
||||
let needActiveNode = this.getNextActiveNode(list)
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let node = list[i]
|
||||
if (node.isGeneralization) {
|
||||
@@ -1293,7 +1328,11 @@ class Render {
|
||||
}
|
||||
|
||||
// 计算下一个可激活的节点
|
||||
getNextActiveNode() {
|
||||
getNextActiveNode(deleteList) {
|
||||
// 删除多个节点不自动激活相邻节点
|
||||
if (deleteList.length !== 1) return null
|
||||
// 被删除的节点不在当前激活的节点列表里,不激活相邻节点
|
||||
if (this.findActiveNodeIndex(deleteList[0]) === -1) return null
|
||||
let needActiveNode = null
|
||||
if (
|
||||
this.activeNodeList.length === 1 &&
|
||||
@@ -1448,6 +1487,7 @@ class Render {
|
||||
|
||||
// 展开所有
|
||||
expandAllNode() {
|
||||
if (!this.renderTree) return
|
||||
walk(
|
||||
this.renderTree,
|
||||
null,
|
||||
@@ -1465,7 +1505,8 @@ class Render {
|
||||
}
|
||||
|
||||
// 收起所有
|
||||
unexpandAllNode() {
|
||||
unexpandAllNode(isSetRootNodeCenter = true) {
|
||||
if (!this.renderTree) return
|
||||
walk(
|
||||
this.renderTree,
|
||||
null,
|
||||
@@ -1480,12 +1521,15 @@ class Render {
|
||||
0
|
||||
)
|
||||
this.mindMap.render(() => {
|
||||
this.setRootNodeCenter()
|
||||
if (isSetRootNodeCenter) {
|
||||
this.setRootNodeCenter()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 展开到指定层级
|
||||
expandToLevel(level) {
|
||||
if (!this.renderTree) return
|
||||
walk(
|
||||
this.renderTree,
|
||||
null,
|
||||
@@ -1572,6 +1616,14 @@ class Render {
|
||||
})
|
||||
}
|
||||
|
||||
// 设置节点附件
|
||||
setNodeAttachment(node, url, name = '') {
|
||||
this.setNodeDataRender(node, {
|
||||
attachmentUrl: url,
|
||||
attachmentName: name
|
||||
})
|
||||
}
|
||||
|
||||
// 设置节点标签
|
||||
setNodeTag(node, tag) {
|
||||
this.setNodeDataRender(node, {
|
||||
@@ -1591,7 +1643,7 @@ class Render {
|
||||
}
|
||||
|
||||
// 添加节点概要
|
||||
addGeneralization(data) {
|
||||
addGeneralization(data, openEdit = true) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
return
|
||||
}
|
||||
@@ -1603,12 +1655,22 @@ class Render {
|
||||
)
|
||||
})
|
||||
const list = parseAddGeneralizationNodeList(nodeList)
|
||||
const isRichText = !!this.mindMap.richText
|
||||
const { focusNewNode, inserting } = this.getNewNodeBehavior(
|
||||
openEdit,
|
||||
list.length > 1
|
||||
)
|
||||
list.forEach(item => {
|
||||
const newData = {
|
||||
inserting,
|
||||
...(data || {
|
||||
text: this.mindMap.opt.defaultGeneralizationText
|
||||
}),
|
||||
range: item.range || null
|
||||
range: item.range || null,
|
||||
uid: createUid(),
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
isActive: focusNewNode
|
||||
}
|
||||
let generalization = item.node.getData('generalization')
|
||||
if (generalization) {
|
||||
@@ -1628,6 +1690,10 @@ class Render {
|
||||
expand: true
|
||||
})
|
||||
})
|
||||
// 需要清除原来激活的节点
|
||||
if (focusNewNode) {
|
||||
this.clearActiveNodeList()
|
||||
}
|
||||
this.mindMap.render(() => {
|
||||
// 修复祖先节点存在概要时位置未更新的问题
|
||||
// 修复同时给存在上下级关系的节点添加概要时重叠的问题
|
||||
@@ -1704,7 +1770,7 @@ class Render {
|
||||
if (targetNode) {
|
||||
targetNode.active()
|
||||
this.moveNodeToCenter(targetNode)
|
||||
callback()
|
||||
callback(targetNode)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1734,19 +1800,28 @@ class Render {
|
||||
|
||||
// 移动节点到画布中心
|
||||
moveNodeToCenter(node) {
|
||||
const { resetScaleOnMoveNodeToCenter } = this.mindMap.opt
|
||||
let { transform, state } = this.mindMap.view.getTransformData()
|
||||
let { left, top, width, height } = node
|
||||
if (!resetScaleOnMoveNodeToCenter) {
|
||||
left *= transform.scaleX
|
||||
top *= transform.scaleY
|
||||
width *= transform.scaleX
|
||||
height *= transform.scaleY
|
||||
}
|
||||
let halfWidth = this.mindMap.width / 2
|
||||
let halfHeight = this.mindMap.height / 2
|
||||
let { left, top, width, height } = node
|
||||
let nodeCenterX = left + width / 2
|
||||
let nodeCenterY = top + height / 2
|
||||
let { state } = this.mindMap.view.getTransformData()
|
||||
let targetX = halfWidth - state.x
|
||||
let targetY = halfHeight - state.y
|
||||
let offsetX = targetX - nodeCenterX
|
||||
let offsetY = targetY - nodeCenterY
|
||||
this.mindMap.view.translateX(offsetX)
|
||||
this.mindMap.view.translateY(offsetY)
|
||||
this.mindMap.view.setScale(1)
|
||||
if (resetScaleOnMoveNodeToCenter) {
|
||||
this.mindMap.view.setScale(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 回到中心主题,即设置根节点到画布中心
|
||||
@@ -1756,15 +1831,29 @@ class Render {
|
||||
|
||||
// 展开到指定uid的节点
|
||||
expandToNodeUid(uid, callback = () => {}) {
|
||||
if (!this.renderTree) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
let parentsList = []
|
||||
let isGeneralization = false
|
||||
const cache = {}
|
||||
bfsWalk(this.renderTree, (node, parent) => {
|
||||
if (node.data.uid === uid) {
|
||||
parentsList = parent ? [...cache[parent.data.uid], parent] : []
|
||||
return 'stop'
|
||||
} else {
|
||||
cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent] : []
|
||||
}
|
||||
const generalizationList = formatGetNodeGeneralization(node.data)
|
||||
generalizationList.forEach(item => {
|
||||
if (item.uid === uid) {
|
||||
parentsList = parent ? [...cache[parent.data.uid], parent] : []
|
||||
isGeneralization = true
|
||||
}
|
||||
})
|
||||
if (isGeneralization) {
|
||||
return 'stop'
|
||||
}
|
||||
cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent] : []
|
||||
})
|
||||
let needRender = false
|
||||
parentsList.forEach(node => {
|
||||
@@ -1773,6 +1862,18 @@ class Render {
|
||||
node.data.expand = true
|
||||
}
|
||||
})
|
||||
// 如果是展开到概要节点,那么父节点下的所有节点都需要开
|
||||
if (isGeneralization) {
|
||||
const lastNode = parentsList[parentsList.length - 1]
|
||||
if (lastNode) {
|
||||
walk(lastNode, null, node => {
|
||||
if (!node.data.expand) {
|
||||
needRender = true
|
||||
node.data.expand = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (needRender) {
|
||||
this.mindMap.render(callback)
|
||||
} else {
|
||||
@@ -1788,12 +1889,25 @@ class Render {
|
||||
res = node
|
||||
return true
|
||||
}
|
||||
// 概要节点
|
||||
let isGeneralization = false
|
||||
;(node._generalizationList || []).forEach(item => {
|
||||
if (item.generalizationNode.getData('uid') === uid) {
|
||||
res = item.generalizationNode
|
||||
isGeneralization = true
|
||||
}
|
||||
})
|
||||
if (isGeneralization) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 高亮节点或子节点
|
||||
highlightNode(node, range) {
|
||||
// 如果当前正在渲染,那么不进行高亮,因为节点位置可能不正确
|
||||
if (this.isRendering) return
|
||||
const { highlightNodeBoxStyle = {} } = this.mindMap.opt
|
||||
if (!this.highlightBoxNode) {
|
||||
this.highlightBoxNode = new Polygon()
|
||||
|
||||
@@ -315,4 +315,12 @@ export default class TextEdit {
|
||||
this.textEditNode.style.transform = 'translateY(0)'
|
||||
this.showTextEdit = false
|
||||
}
|
||||
|
||||
// 获取当前正在编辑中的节点实例
|
||||
getCurrentEditNode() {
|
||||
if (this.mindMap.richText) {
|
||||
return this.mindMap.richText.node
|
||||
}
|
||||
return this.currentNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { G, ForeignObject, Rect } from '@svgdotjs/svg.js'
|
||||
import { G, Rect } from '@svgdotjs/svg.js'
|
||||
import nodeGeneralizationMethods from './nodeGeneralization'
|
||||
import nodeExpandBtnMethods from './nodeExpandBtn'
|
||||
import nodeCommandWrapsMethods from './nodeCommandWraps'
|
||||
@@ -8,7 +8,7 @@ import nodeCreateContentsMethods from './nodeCreateContents'
|
||||
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
|
||||
import nodeCooperateMethods from './nodeCooperate'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
import { copyNodeTree } from '../../../utils/index'
|
||||
import { copyNodeTree, createForeignObjectNode } from '../../../utils/index'
|
||||
|
||||
// 节点类
|
||||
class Node {
|
||||
@@ -75,6 +75,9 @@ class Node {
|
||||
this._noteData = null
|
||||
this.noteEl = null
|
||||
this.noteContentIsShow = false
|
||||
this._attachmentData = null
|
||||
this._prefixData = null
|
||||
this._postfixData = null
|
||||
this._expandBtn = null
|
||||
this._lastExpandBtnType = null
|
||||
this._showExpandBtn = false
|
||||
@@ -181,7 +184,12 @@ class Node {
|
||||
// 创建节点的各个内容对象数据
|
||||
createNodeData() {
|
||||
// 自定义节点内容
|
||||
let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt
|
||||
let {
|
||||
isUseCustomNodeContent,
|
||||
customCreateNodeContent,
|
||||
createNodePrefixContent,
|
||||
createNodePostfixContent
|
||||
} = this.mindMap.opt
|
||||
if (isUseCustomNodeContent && customCreateNodeContent) {
|
||||
this._customNodeContent = customCreateNodeContent(this)
|
||||
}
|
||||
@@ -199,10 +207,19 @@ class Node {
|
||||
this._hyperlinkData = this.createHyperlinkNode()
|
||||
this._tagData = this.createTagNode()
|
||||
this._noteData = this.createNoteNode()
|
||||
this._attachmentData = this.createAttachmentNode()
|
||||
this._prefixData = createNodePrefixContent
|
||||
? createNodePrefixContent(this)
|
||||
: null
|
||||
this._postfixData = createNodePostfixContent
|
||||
? createNodePostfixContent(this)
|
||||
: null
|
||||
}
|
||||
|
||||
// 计算节点的宽高
|
||||
getSize() {
|
||||
this.customLeft = this.getData('customLeft') || undefined
|
||||
this.customTop = this.getData('customTop') || undefined
|
||||
this.updateGeneralization()
|
||||
this.createNodeData()
|
||||
let { width, height } = this.getNodeRect()
|
||||
@@ -233,6 +250,11 @@ class Node {
|
||||
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
|
||||
this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height
|
||||
}
|
||||
// 自定义前置内容
|
||||
if (this._prefixData) {
|
||||
textContentWidth += this._prefixData.width
|
||||
textContentHeight = Math.max(textContentHeight, this._prefixData.height)
|
||||
}
|
||||
// 图标
|
||||
if (this._iconData.length > 0) {
|
||||
textContentWidth += this._iconData.reduce((sum, cur) => {
|
||||
@@ -265,6 +287,19 @@ class Node {
|
||||
textContentWidth += this._noteData.width
|
||||
textContentHeight = Math.max(textContentHeight, this._noteData.height)
|
||||
}
|
||||
// 附件
|
||||
if (this._attachmentData) {
|
||||
textContentWidth += this._attachmentData.width
|
||||
textContentHeight = Math.max(
|
||||
textContentHeight,
|
||||
this._attachmentData.height
|
||||
)
|
||||
}
|
||||
// 自定义后置内容
|
||||
if (this._postfixData) {
|
||||
textContentWidth += this._postfixData.width
|
||||
textContentHeight = Math.max(textContentHeight, this._postfixData.height)
|
||||
}
|
||||
// 文字内容部分的尺寸
|
||||
this._rectInfo.textContentWidth = textContentWidth
|
||||
this._rectInfo.textContentHeight = textContentHeight
|
||||
@@ -325,10 +360,11 @@ class Node {
|
||||
}
|
||||
// 如果存在自定义节点内容,那么使用自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(this._customNodeContent)
|
||||
const foreignObject = createForeignObjectNode({
|
||||
el: this._customNodeContent,
|
||||
width,
|
||||
height
|
||||
})
|
||||
this.group.add(foreignObject)
|
||||
addHoverNode()
|
||||
return
|
||||
@@ -343,6 +379,19 @@ class Node {
|
||||
// 内容节点
|
||||
let textContentNested = new G()
|
||||
let textContentOffsetX = 0
|
||||
// 自定义前置内容
|
||||
if (this._prefixData) {
|
||||
const foreignObject = createForeignObjectNode({
|
||||
el: this._prefixData.el,
|
||||
width: this._prefixData.width,
|
||||
height: this._prefixData.height
|
||||
})
|
||||
foreignObject
|
||||
.x(textContentOffsetX)
|
||||
.y((this._rectInfo.textContentHeight - this._prefixData.height) / 2)
|
||||
textContentNested.add(foreignObject)
|
||||
textContentOffsetX += this._prefixData.width + textContentItemMargin
|
||||
}
|
||||
// icon
|
||||
let iconNested = new G()
|
||||
if (this._iconData && this._iconData.length > 0) {
|
||||
@@ -359,9 +408,11 @@ class Node {
|
||||
}
|
||||
// 文字
|
||||
if (this._textData) {
|
||||
const oldX = this._textData.node.attr('data-offsetx') || 0
|
||||
this._textData.node.attr('data-offsetx', textContentOffsetX)
|
||||
// 修复safari浏览器节点存在图标时文字位置不正确的问题
|
||||
;(this._textData.nodeContent || this._textData.node)
|
||||
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
|
||||
.x(textContentOffsetX)
|
||||
.y(0)
|
||||
textContentNested.add(this._textData.node)
|
||||
@@ -397,6 +448,27 @@ class Node {
|
||||
textContentNested.add(this._noteData.node)
|
||||
textContentOffsetX += this._noteData.width
|
||||
}
|
||||
// 附件
|
||||
if (this._attachmentData) {
|
||||
this._attachmentData.node
|
||||
.x(textContentOffsetX)
|
||||
.y((this._rectInfo.textContentHeight - this._attachmentData.height) / 2)
|
||||
textContentNested.add(this._attachmentData.node)
|
||||
textContentOffsetX += this._attachmentData.width
|
||||
}
|
||||
// 自定义后置内容
|
||||
if (this._postfixData) {
|
||||
const foreignObject = createForeignObjectNode({
|
||||
el: this._postfixData.el,
|
||||
width: this._postfixData.width,
|
||||
height: this._postfixData.height
|
||||
})
|
||||
foreignObject
|
||||
.x(textContentOffsetX)
|
||||
.y((this._rectInfo.textContentHeight - this._postfixData.height) / 2)
|
||||
textContentNested.add(foreignObject)
|
||||
textContentOffsetX += this._postfixData.width
|
||||
}
|
||||
// 文字内容整体
|
||||
textContentNested.translate(
|
||||
width / 2 - textContentNested.bbox().width / 2,
|
||||
@@ -420,6 +492,12 @@ class Node {
|
||||
this.isMultipleChoice = false
|
||||
return
|
||||
}
|
||||
if (
|
||||
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
|
||||
this.userList.length > 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.active(e)
|
||||
})
|
||||
this.group.on('mousedown', e => {
|
||||
@@ -443,7 +521,7 @@ class Node {
|
||||
}
|
||||
}
|
||||
// 多选和取消多选
|
||||
if (e.ctrlKey && enableCtrlKeyNodeSelection) {
|
||||
if ((e.ctrlKey || e.metaKey) && enableCtrlKeyNodeSelection) {
|
||||
this.isMultipleChoice = true
|
||||
let isActive = this.getData('isActive')
|
||||
if (!isActive)
|
||||
@@ -454,7 +532,7 @@ class Node {
|
||||
)
|
||||
this.mindMap.renderer[
|
||||
isActive ? 'removeNodeFromActiveList' : 'addNodeToActiveList'
|
||||
](this)
|
||||
](this, true)
|
||||
this.renderer.emitNodeActiveEvent(isActive ? null : this)
|
||||
}
|
||||
this.mindMap.emit('node_mousedown', this, e)
|
||||
@@ -486,10 +564,14 @@ class Node {
|
||||
})
|
||||
// 双击事件
|
||||
this.group.on('dblclick', e => {
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {
|
||||
const { readonly, onlyOneEnableActiveNodeOnCooperate } = this.mindMap.opt
|
||||
if (readonly || e.ctrlKey || e.metaKey) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
if (onlyOneEnableActiveNodeOnCooperate && this.userList.length > 0) {
|
||||
return
|
||||
}
|
||||
this.mindMap.emit('node_dblclick', this, e)
|
||||
})
|
||||
// 右键菜单事件
|
||||
@@ -531,10 +613,16 @@ class Node {
|
||||
}
|
||||
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
|
||||
this.renderer.clearActiveNodeList()
|
||||
this.renderer.addNodeToActiveList(this)
|
||||
this.renderer.addNodeToActiveList(this, true)
|
||||
this.renderer.emitNodeActiveEvent(this)
|
||||
}
|
||||
|
||||
// 取消激活该节点
|
||||
deactivate() {
|
||||
this.mindMap.renderer.removeNodeFromActiveList(this)
|
||||
this.mindMap.renderer.emitNodeActiveEvent()
|
||||
}
|
||||
|
||||
// 更新节点
|
||||
update() {
|
||||
if (!this.group) {
|
||||
@@ -542,9 +630,10 @@ class Node {
|
||||
}
|
||||
this.updateNodeActiveClass()
|
||||
let { alwaysShowExpandBtn } = this.mindMap.opt
|
||||
const childrenLength = this.nodeData.children.length
|
||||
if (alwaysShowExpandBtn) {
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
if (this._expandBtn && childrenLength <= 0) {
|
||||
this.removeExpandBtn()
|
||||
} else {
|
||||
// 更新展开收起按钮
|
||||
@@ -553,7 +642,9 @@ class Node {
|
||||
} else {
|
||||
let { isActive, expand } = this.getData()
|
||||
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
|
||||
if (expand && !isActive && !this._isMouseenter) {
|
||||
if (childrenLength <= 0) {
|
||||
this.removeExpandBtn()
|
||||
} else if (expand && !isActive && !this._isMouseenter) {
|
||||
this.hideExpandBtn()
|
||||
} else {
|
||||
this.showExpandBtn()
|
||||
@@ -705,6 +796,9 @@ class Node {
|
||||
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
|
||||
destroy() {
|
||||
if (!this.group) return
|
||||
if (this.emptyUser) {
|
||||
this.emptyUser()
|
||||
}
|
||||
this.resetWhenDelete()
|
||||
this.group.remove()
|
||||
this.removeGeneralization()
|
||||
@@ -901,6 +995,7 @@ class Node {
|
||||
childNode.getStyle('lineWidth', true)
|
||||
const color =
|
||||
childNode.getSelfInhertStyle('lineColor') ||
|
||||
this.getRainbowLineColor(childNode) ||
|
||||
childNode.getStyle('lineColor', true)
|
||||
const dasharray =
|
||||
childNode.getSelfInhertStyle('lineDasharray') ||
|
||||
@@ -917,6 +1012,13 @@ class Node {
|
||||
)
|
||||
}
|
||||
|
||||
// 获取彩虹线条颜色
|
||||
getRainbowLineColor(node) {
|
||||
return this.mindMap.rainbowLines
|
||||
? this.mindMap.rainbowLines.getNodeColor(node)
|
||||
: ''
|
||||
}
|
||||
|
||||
// 移除连线
|
||||
removeLine() {
|
||||
this._lines.forEach(line => {
|
||||
@@ -1024,6 +1126,17 @@ class Node {
|
||||
return copyNodeTree({}, this, removeActiveState, removeId)
|
||||
}
|
||||
|
||||
// 获取祖先节点列表
|
||||
getAncestorNodes() {
|
||||
const list = []
|
||||
let parent = this.parent
|
||||
while (parent) {
|
||||
list.unshift(parent)
|
||||
parent = parent.parent
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// 是否存在自定义样式
|
||||
hasCustomStyle() {
|
||||
return this.style.hasCustomStyle()
|
||||
@@ -1052,6 +1165,16 @@ class Node {
|
||||
height: height * scaleY
|
||||
}
|
||||
}
|
||||
|
||||
// 高亮节点
|
||||
highlight() {
|
||||
if (this.group) this.group.addClass('smm-node-highlight')
|
||||
}
|
||||
|
||||
// 取消高亮节点
|
||||
closeHighlight() {
|
||||
if (this.group) this.group.removeClass('smm-node-highlight')
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
|
||||
@@ -28,6 +28,11 @@ function setNote(note) {
|
||||
this.mindMap.execCommand('SET_NODE_NOTE', this, note)
|
||||
}
|
||||
|
||||
// 设置附件
|
||||
function setAttachment(url, name) {
|
||||
this.mindMap.execCommand('SET_NODE_ATTACHMENT', this, url, name)
|
||||
}
|
||||
|
||||
// 设置标签
|
||||
function setTag(tag) {
|
||||
this.mindMap.execCommand('SET_NODE_TAG', this, tag)
|
||||
@@ -55,6 +60,7 @@ export default {
|
||||
setIcon,
|
||||
setHyperlink,
|
||||
setNote,
|
||||
setAttachment,
|
||||
setTag,
|
||||
setShape,
|
||||
setStyle,
|
||||
|
||||
@@ -67,6 +67,15 @@ function updateUserListNode() {
|
||||
} else {
|
||||
node = this.createTextAvatar(item)
|
||||
}
|
||||
node.on('click', (e) => {
|
||||
this.mindMap.emit('node_cooperate_avatar_click', item, this, node, e)
|
||||
})
|
||||
node.on('mouseenter', (e) => {
|
||||
this.mindMap.emit('node_cooperate_avatar_mouseenter', item, this, node, e)
|
||||
})
|
||||
node.on('mouseleave', (e) => {
|
||||
this.mindMap.emit('node_cooperate_avatar_mouseleave', item, this, node, e)
|
||||
})
|
||||
node.x(index * avatarSize).cy(-avatarSize / 2)
|
||||
this._userListGroup.add(node)
|
||||
})
|
||||
@@ -94,11 +103,18 @@ function removeUser(userInfo) {
|
||||
this.updateUserListNode()
|
||||
}
|
||||
|
||||
// 清空用户
|
||||
function emptyUser() {
|
||||
this.userList = []
|
||||
this.updateUserListNode()
|
||||
}
|
||||
|
||||
export default {
|
||||
createUserListNode,
|
||||
updateUserListNode,
|
||||
createTextAvatar,
|
||||
createImageAvatar,
|
||||
addUser,
|
||||
removeUser
|
||||
removeUser,
|
||||
emptyUser
|
||||
}
|
||||
|
||||
@@ -4,19 +4,13 @@ import {
|
||||
removeHtmlStyle,
|
||||
addHtmlStyle,
|
||||
checkIsRichText,
|
||||
isUndef
|
||||
isUndef,
|
||||
createForeignObjectNode
|
||||
} from '../../../utils'
|
||||
import {
|
||||
Image as SVGImage,
|
||||
SVG,
|
||||
A,
|
||||
G,
|
||||
Rect,
|
||||
Text,
|
||||
ForeignObject
|
||||
} from '@svgdotjs/svg.js'
|
||||
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../../../svg/icons'
|
||||
import { CONSTANTS, commonCaches } from '../../../constants/constant'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
import { defenseXSS } from '../../../utils/xss'
|
||||
|
||||
// 创建图片节点
|
||||
function createImgNode() {
|
||||
@@ -92,7 +86,13 @@ function createIconNode() {
|
||||
}
|
||||
node.size(iconSize, iconSize)
|
||||
node.on('click', e => {
|
||||
this.mindMap.emit('node_icon_click', this, item, e)
|
||||
this.mindMap.emit('node_icon_click', this, item, e, node)
|
||||
})
|
||||
node.on('mouseenter', e => {
|
||||
this.mindMap.emit('node_icon_mouseenter', this, item, e, node)
|
||||
})
|
||||
node.on('mouseleave', e => {
|
||||
this.mindMap.emit('node_icon_mouseleave', this, item, e, node)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
@@ -142,14 +142,19 @@ function createRichTextNode() {
|
||||
text: text
|
||||
})
|
||||
}
|
||||
let html = `<div>${this.getData('text')}</div>`
|
||||
if (!commonCaches.measureRichtextNodeTextSizeEl) {
|
||||
commonCaches.measureRichtextNodeTextSizeEl = document.createElement('div')
|
||||
commonCaches.measureRichtextNodeTextSizeEl.style.position = 'fixed'
|
||||
commonCaches.measureRichtextNodeTextSizeEl.style.left = '-999999px'
|
||||
this.mindMap.el.appendChild(commonCaches.measureRichtextNodeTextSizeEl)
|
||||
let html = `<div>${defenseXSS(this.getData('text'))}</div>`
|
||||
if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) {
|
||||
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl =
|
||||
document.createElement('div')
|
||||
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl.style.position =
|
||||
'fixed'
|
||||
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl.style.left =
|
||||
'-999999px'
|
||||
this.mindMap.el.appendChild(
|
||||
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl
|
||||
)
|
||||
}
|
||||
let div = commonCaches.measureRichtextNodeTextSizeEl
|
||||
let div = this.mindMap.commonCaches.measureRichtextNodeTextSizeEl
|
||||
div.innerHTML = html
|
||||
let el = div.children[0]
|
||||
el.classList.add('smm-richtext-node-wrap')
|
||||
@@ -168,10 +173,11 @@ function createRichTextNode() {
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(div.children[0])
|
||||
const foreignObject = createForeignObjectNode({
|
||||
el: div.children[0],
|
||||
width,
|
||||
height
|
||||
})
|
||||
g.add(foreignObject)
|
||||
return {
|
||||
node: g,
|
||||
@@ -256,7 +262,7 @@ function createHyperlinkNode() {
|
||||
e.stopPropagation()
|
||||
})
|
||||
if (hyperlinkTitle) {
|
||||
a.attr('title', hyperlinkTitle)
|
||||
node.add(SVG(`<title>${hyperlinkTitle}</title>`))
|
||||
}
|
||||
// 添加一个透明的层,作为鼠标区域
|
||||
a.rect(iconSize, iconSize).fill({ color: 'transparent' })
|
||||
@@ -281,6 +287,9 @@ function createTagNode() {
|
||||
let nodes = []
|
||||
tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => {
|
||||
let tag = new G()
|
||||
tag.on('click', () => {
|
||||
this.mindMap.emit('node_tag_click', this, item)
|
||||
})
|
||||
// 标签文本
|
||||
let text = new Text().text(item).x(8).cy(8)
|
||||
this.style.tagText(text, index)
|
||||
@@ -307,7 +316,10 @@ function createNoteNode() {
|
||||
return null
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
let node = new SVG().attr('cursor', 'pointer').size(iconSize, iconSize)
|
||||
let node = new SVG()
|
||||
.attr('cursor', 'pointer')
|
||||
.addClass('smm-node-note')
|
||||
.size(iconSize, iconSize)
|
||||
// 透明的层,用来作为鼠标区域
|
||||
node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' }))
|
||||
// 备注图标
|
||||
@@ -362,6 +374,36 @@ function createNoteNode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建附件节点
|
||||
function createAttachmentNode() {
|
||||
const { attachmentUrl, attachmentName } = this.getData()
|
||||
if (!attachmentUrl) {
|
||||
return
|
||||
}
|
||||
const iconSize = this.mindMap.themeConfig.iconSize
|
||||
const node = new SVG().attr('cursor', 'pointer').size(iconSize, iconSize)
|
||||
if (attachmentName) {
|
||||
node.add(SVG(`<title>${attachmentName}</title>`))
|
||||
}
|
||||
// 透明的层,用来作为鼠标区域
|
||||
node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' }))
|
||||
// 备注图标
|
||||
const iconNode = SVG(iconsSvg.attachment).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
node.add(iconNode)
|
||||
node.on('click', e => {
|
||||
this.mindMap.emit('node_attachmentClick', this, e, node)
|
||||
})
|
||||
node.on('contextmenu', e => {
|
||||
this.mindMap.emit('node_attachmentContextmenu', this, e, node)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点备注显示位置
|
||||
function getNoteContentPosition() {
|
||||
const iconSize = this.mindMap.themeConfig.iconSize
|
||||
@@ -377,18 +419,22 @@ function getNoteContentPosition() {
|
||||
|
||||
// 测量自定义节点内容元素的宽高
|
||||
function measureCustomNodeContentSize(content) {
|
||||
if (!commonCaches.measureCustomNodeContentSizeEl) {
|
||||
commonCaches.measureCustomNodeContentSizeEl = document.createElement('div')
|
||||
commonCaches.measureCustomNodeContentSizeEl.style.cssText = `
|
||||
if (!this.mindMap.commonCaches.measureCustomNodeContentSizeEl) {
|
||||
this.mindMap.commonCaches.measureCustomNodeContentSizeEl =
|
||||
document.createElement('div')
|
||||
this.mindMap.commonCaches.measureCustomNodeContentSizeEl.style.cssText = `
|
||||
position: fixed;
|
||||
left: -99999px;
|
||||
top: -99999px;
|
||||
`
|
||||
this.mindMap.el.appendChild(commonCaches.measureCustomNodeContentSizeEl)
|
||||
this.mindMap.el.appendChild(
|
||||
this.mindMap.commonCaches.measureCustomNodeContentSizeEl
|
||||
)
|
||||
}
|
||||
commonCaches.measureCustomNodeContentSizeEl.innerHTML = ''
|
||||
commonCaches.measureCustomNodeContentSizeEl.appendChild(content)
|
||||
let rect = commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect()
|
||||
this.mindMap.commonCaches.measureCustomNodeContentSizeEl.innerHTML = ''
|
||||
this.mindMap.commonCaches.measureCustomNodeContentSizeEl.appendChild(content)
|
||||
let rect =
|
||||
this.mindMap.commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect()
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
@@ -409,6 +455,7 @@ export default {
|
||||
createHyperlinkNode,
|
||||
createTagNode,
|
||||
createNoteNode,
|
||||
createAttachmentNode,
|
||||
getNoteContentPosition,
|
||||
measureCustomNodeContentSize,
|
||||
isUseCustomNodeContent
|
||||
|
||||
@@ -51,6 +51,7 @@ function createGeneralizationNode() {
|
||||
if (!cur.generalizationNode) {
|
||||
cur.generalizationNode = new Node({
|
||||
data: {
|
||||
inserting: item.inserting,
|
||||
data: item
|
||||
},
|
||||
uid: createUid(),
|
||||
@@ -59,6 +60,7 @@ function createGeneralizationNode() {
|
||||
isGeneralization: true
|
||||
})
|
||||
}
|
||||
delete item.inserting
|
||||
// 关联所属节点
|
||||
cur.generalizationNode.generalizationBelongNode = this
|
||||
// 大小
|
||||
|
||||
@@ -37,7 +37,7 @@ class View {
|
||||
this.mindMap.event.on('drag', (e, event) => {
|
||||
// 按住ctrl键拖动为多选
|
||||
// 禁用拖拽
|
||||
if (e.ctrlKey || this.mindMap.opt.isDisableDrag) {
|
||||
if (e.ctrlKey || e.metaKey || this.mindMap.opt.isDisableDrag) {
|
||||
return
|
||||
}
|
||||
if (this.firstDrag) {
|
||||
@@ -72,7 +72,11 @@ class View {
|
||||
return customHandleMousewheel(e)
|
||||
}
|
||||
// 1.鼠标滚轮事件控制缩放
|
||||
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM || e.ctrlKey) {
|
||||
if (
|
||||
mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM ||
|
||||
e.ctrlKey ||
|
||||
e.metaKey
|
||||
) {
|
||||
if (disableMouseWheelZoom) return
|
||||
const { x: clientX, y: clientY } = this.mindMap.toPos(
|
||||
e.clientX,
|
||||
@@ -128,6 +132,10 @@ class View {
|
||||
this.translateXY(mx, my)
|
||||
}
|
||||
})
|
||||
this.mindMap.on('resize', () => {
|
||||
if (!this.checkNeedMindMapInCanvas()) return
|
||||
this.transform()
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当前变换状态数据
|
||||
@@ -154,7 +162,8 @@ class View {
|
||||
...viewData.transform
|
||||
})
|
||||
this.mindMap.emit('view_data_change', this.getTransformData())
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
this.emitEvent('scale')
|
||||
this.emitEvent('translate')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +173,7 @@ class View {
|
||||
this.x += x
|
||||
this.y += y
|
||||
this.transform()
|
||||
this.emitEvent('translate')
|
||||
}
|
||||
|
||||
// 平移x方向
|
||||
@@ -171,12 +181,14 @@ class View {
|
||||
if (step === 0) return
|
||||
this.x += step
|
||||
this.transform()
|
||||
this.emitEvent('translate')
|
||||
}
|
||||
|
||||
// 平移x方式到
|
||||
translateXTo(x) {
|
||||
this.x = x
|
||||
this.transform()
|
||||
this.emitEvent('translate')
|
||||
}
|
||||
|
||||
// 平移y方向
|
||||
@@ -184,12 +196,14 @@ class View {
|
||||
if (step === 0) return
|
||||
this.y += step
|
||||
this.transform()
|
||||
this.emitEvent('translate')
|
||||
}
|
||||
|
||||
// 平移y方向到
|
||||
translateYTo(y) {
|
||||
this.y = y
|
||||
this.transform()
|
||||
this.emitEvent('translate')
|
||||
}
|
||||
|
||||
// 应用变换
|
||||
@@ -207,13 +221,17 @@ class View {
|
||||
|
||||
// 恢复
|
||||
reset() {
|
||||
let scaleChange = this.scale !== 1
|
||||
const scaleChange = this.scale !== 1
|
||||
const translateChange = this.x !== 0 || this.y !== 0
|
||||
this.scale = 1
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.transform()
|
||||
if (scaleChange) {
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
this.emitEvent('scale')
|
||||
}
|
||||
if (translateChange) {
|
||||
this.emitEvent('translate')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +241,7 @@ class View {
|
||||
const scale = Math.max(this.scale - scaleRatio, 0.1)
|
||||
this.scaleInCenter(scale, cx, cy)
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
this.emitEvent('scale')
|
||||
}
|
||||
|
||||
// 放大
|
||||
@@ -232,7 +250,7 @@ class View {
|
||||
const scale = this.scale + scaleRatio
|
||||
this.scaleInCenter(scale, cx, cy)
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
this.emitEvent('scale')
|
||||
}
|
||||
|
||||
// 基于指定中心进行缩放,cx,cy 可不指定,此时会使用画布中心点
|
||||
@@ -258,15 +276,16 @@ class View {
|
||||
this.scale = scale
|
||||
}
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
this.emitEvent('scale')
|
||||
}
|
||||
|
||||
// 适应画布大小
|
||||
fit() {
|
||||
const { fitPadding } = this.mindMap.opt
|
||||
fit(getRbox = () => {}, enlarge = false, fitPadding) {
|
||||
fitPadding =
|
||||
fitPadding === undefined ? this.mindMap.opt.fitPadding : fitPadding
|
||||
const draw = this.mindMap.draw
|
||||
const origTransform = draw.transform()
|
||||
const rect = draw.rbox()
|
||||
const rect = getRbox() || draw.rbox()
|
||||
const drawWidth = rect.width / origTransform.scaleX
|
||||
const drawHeight = rect.height / origTransform.scaleY
|
||||
const drawRatio = drawWidth / drawHeight
|
||||
@@ -276,7 +295,7 @@ class View {
|
||||
const elRatio = elWidth / elHeight
|
||||
let newScale = 0
|
||||
let flag = ''
|
||||
if (drawWidth <= elWidth && drawHeight <= elHeight) {
|
||||
if (drawWidth <= elWidth && drawHeight <= elHeight && !enlarge) {
|
||||
newScale = 1
|
||||
flag = 1
|
||||
} else {
|
||||
@@ -294,7 +313,7 @@ class View {
|
||||
newScale = newWidth / drawWidth
|
||||
}
|
||||
this.setScale(newScale)
|
||||
const newRect = draw.rbox()
|
||||
const newRect = getRbox() || draw.rbox()
|
||||
// 需要考虑画布容器距浏览器窗口左上角的距离
|
||||
newRect.x -= this.mindMap.elRect.left
|
||||
newRect.y -= this.mindMap.elRect.top
|
||||
@@ -313,20 +332,31 @@ class View {
|
||||
this.translateXY(newX, newY)
|
||||
}
|
||||
|
||||
// 将思维导图限制在画布内
|
||||
limitMindMapInCanvas() {
|
||||
// 判断是否需要将思维导图限制在画布内
|
||||
checkNeedMindMapInCanvas() {
|
||||
const { isLimitMindMapInCanvasWhenHasScrollbar, isLimitMindMapInCanvas } =
|
||||
this.mindMap.opt
|
||||
// 如果注册了滚动条插件,那么使用isLimitMindMapInCanvasWhenHasScrollbar配置
|
||||
if (this.mindMap.scrollbar) {
|
||||
if (!isLimitMindMapInCanvasWhenHasScrollbar) return
|
||||
return isLimitMindMapInCanvasWhenHasScrollbar
|
||||
} else {
|
||||
// 否则使用isLimitMindMapInCanvas配置
|
||||
if (!isLimitMindMapInCanvas) return
|
||||
return isLimitMindMapInCanvas
|
||||
}
|
||||
}
|
||||
|
||||
// 将思维导图限制在画布内
|
||||
limitMindMapInCanvas() {
|
||||
if (!this.checkNeedMindMapInCanvas()) return
|
||||
|
||||
let { scale, left, top, right, bottom } = this.getPositionLimit()
|
||||
|
||||
// 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
|
||||
const centerXChange =
|
||||
((this.mindMap.width - this.mindMap.initWidth) / 2) * scale
|
||||
const centerYChange =
|
||||
((this.mindMap.height - this.mindMap.initHeight) / 2) * scale
|
||||
|
||||
// 如果缩放值改变了
|
||||
const scaleRatio = this.scale / scale
|
||||
left *= scaleRatio
|
||||
@@ -338,10 +368,10 @@ class View {
|
||||
const centerX = this.mindMap.width / 2
|
||||
const centerY = this.mindMap.height / 2
|
||||
const scaleOffset = this.scale - 1
|
||||
left -= scaleOffset * centerX
|
||||
right -= scaleOffset * centerX
|
||||
top -= scaleOffset * centerY
|
||||
bottom -= scaleOffset * centerY
|
||||
left -= scaleOffset * centerX - centerXChange
|
||||
right -= scaleOffset * centerX - centerXChange
|
||||
top -= scaleOffset * centerY - centerYChange
|
||||
bottom -= scaleOffset * centerY - centerYChange
|
||||
|
||||
// 判断是否超出边界
|
||||
if (this.x > left) {
|
||||
@@ -379,6 +409,16 @@ class View {
|
||||
bottom
|
||||
}
|
||||
}
|
||||
|
||||
// 派发事件
|
||||
emitEvent(type) {
|
||||
switch (type) {
|
||||
case 'scale':
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
case 'translate':
|
||||
this.mindMap.emit('translate', this.x, this.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default View
|
||||
|
||||
@@ -144,9 +144,10 @@ class Base {
|
||||
this.cacheNode(newUid, newNode)
|
||||
// 数据关联实际节点
|
||||
data._node = newNode
|
||||
if (data.data.isActive) {
|
||||
this.renderer.addNodeToActiveList(newNode)
|
||||
}
|
||||
}
|
||||
// 如果该节点数据是已激活状态,那么添加到激活节点列表里
|
||||
if (data.data.isActive) {
|
||||
this.renderer.addNodeToActiveList(newNode)
|
||||
}
|
||||
// 如果当前节点在激活节点列表里,那么添加上激活的状态
|
||||
if (this.mindMap.renderer.findActiveNodeIndex(newNode) !== -1) {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
|
||||
const getNodeText = node => {
|
||||
// 优先找出其中的text类型的子节点
|
||||
let textChild = (node.children || []).find(item => {
|
||||
return item.type === 'text'
|
||||
})
|
||||
// 没有找到,那么直接使用第一个子节点
|
||||
textChild = textChild || node.children[0]
|
||||
if (textChild) {
|
||||
if (textChild.value !== undefined) {
|
||||
return textChild.value
|
||||
}
|
||||
return getNodeText(textChild)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 处理list的情况
|
||||
const handleList = node => {
|
||||
let list = []
|
||||
@@ -9,7 +25,7 @@ const handleList = node => {
|
||||
let node = {}
|
||||
node.data = {
|
||||
// 节点内容
|
||||
text: cur.children[0].children[0].value
|
||||
text: getNodeText(cur)
|
||||
}
|
||||
node.children = []
|
||||
newArr.push(node)
|
||||
@@ -45,7 +61,7 @@ export const transformMarkdownTo = md => {
|
||||
let node = {}
|
||||
node.data = {
|
||||
// 节点内容
|
||||
text: cur.children[0].value
|
||||
text: getNodeText(cur)
|
||||
}
|
||||
node.children = []
|
||||
// 如果当前的层级大于上一个节点的层级,那么是其子节点
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { walk } from '../utils'
|
||||
import { walk, nodeRichTextToTextWithWrap } from '../utils'
|
||||
|
||||
let el = null
|
||||
const getText = str => {
|
||||
if (!el) {
|
||||
el = document.createElement('div')
|
||||
}
|
||||
el.innerHTML = str
|
||||
return el.textContent
|
||||
const getNodeText = data => {
|
||||
return data.richText ? nodeRichTextToTextWithWrap(data.text) : data.text
|
||||
}
|
||||
|
||||
const getTitleMark = level => {
|
||||
@@ -24,21 +19,22 @@ export const transformToMarkdown = root => {
|
||||
root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
let level = layerIndex + 1
|
||||
let text = node.data.richText ? getText(node.data.text) : node.data.text
|
||||
const level = layerIndex + 1
|
||||
if (level <= 6) {
|
||||
content += getTitleMark(level)
|
||||
} else {
|
||||
content += getIndentMark(level)
|
||||
}
|
||||
content += ' ' + text
|
||||
content += ' ' + getNodeText(node.data)
|
||||
// 概要
|
||||
let generalization = node.data.generalization
|
||||
if (generalization && generalization.text) {
|
||||
let generalizationText = generalization.richText
|
||||
? getText(generalization.text)
|
||||
: generalization.text
|
||||
content += `[${generalizationText}]`
|
||||
const generalization = node.data.generalization
|
||||
if (Array.isArray(generalization)) {
|
||||
content += generalization.map(item => {
|
||||
return ` [${getNodeText(item)}]`
|
||||
})
|
||||
} else if (generalization && generalization.text) {
|
||||
const generalizationText = getNodeText(generalization)
|
||||
content += ` [${generalizationText}]`
|
||||
}
|
||||
content += '\n\n'
|
||||
// 备注
|
||||
|
||||
35
simple-mind-map/src/parse/toTxt.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { walk, nodeRichTextToTextWithWrap } from '../utils'
|
||||
|
||||
const getNodeText = data => {
|
||||
return data.richText ? nodeRichTextToTextWithWrap(data.text) : data.text
|
||||
}
|
||||
|
||||
const getIndent = level => {
|
||||
return new Array(level).fill(' ').join('')
|
||||
}
|
||||
|
||||
// 转换成txt格式
|
||||
export const transformToTxt = root => {
|
||||
let content = ''
|
||||
walk(
|
||||
root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
content += getIndent(layerIndex)
|
||||
content += ' ' + getNodeText(node.data)
|
||||
// 概要
|
||||
const generalization = node.data.generalization
|
||||
if (Array.isArray(generalization)) {
|
||||
content += generalization.map(item => {
|
||||
return ` [${getNodeText(item)}]`
|
||||
})
|
||||
} else if (generalization && generalization.text) {
|
||||
content += ` [${getNodeText(generalization)}]`
|
||||
}
|
||||
content += '\n\n'
|
||||
},
|
||||
() => {},
|
||||
true
|
||||
)
|
||||
return content
|
||||
}
|
||||
@@ -198,7 +198,7 @@ const transformOldXmind = content => {
|
||||
childrenItem.elements.length > 0
|
||||
) {
|
||||
const children = getElementsByType(childrenItem.elements, 'attached')
|
||||
children.forEach((item, index) => {
|
||||
;(children || []).forEach((item, index) => {
|
||||
const newChild = {}
|
||||
newNode.children.push(newChild)
|
||||
if (childrenSummary[index]) {
|
||||
|
||||
@@ -339,6 +339,10 @@ class AssociativeLine {
|
||||
dasharray: [6, 4]
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
// 箭头
|
||||
this.markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
this.creatingLine.marker('end', this.marker)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ class Cooperate {
|
||||
this.currentData = null
|
||||
// 用户信息
|
||||
this.userInfo = null
|
||||
// 是否正在重新设置思维导图数据
|
||||
this.isSetData = false
|
||||
// 绑定事件
|
||||
this.bindEvent()
|
||||
// 处理实例化时传入的思维导图数据
|
||||
@@ -92,8 +94,8 @@ class Cooperate {
|
||||
this.mindMap.on('node_tree_render_end', this.onNodeTreeRenderEnd)
|
||||
|
||||
// 监听设置思维导图数据事件
|
||||
this.initData = this.initData.bind(this)
|
||||
this.mindMap.on('set_data', this.initData)
|
||||
this.onSetData = this.onSetData.bind(this)
|
||||
this.mindMap.on('set_data', this.onSetData)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -104,7 +106,7 @@ class Cooperate {
|
||||
this.mindMap.off('data_change', this.onDataChange)
|
||||
this.mindMap.off('node_active', this.onNodeActive)
|
||||
this.mindMap.off('node_tree_render_end', this.onNodeTreeRenderEnd)
|
||||
this.mindMap.off('set_data', this.initData)
|
||||
this.mindMap.off('set_data', this.onSetData)
|
||||
this.ydoc.destroy()
|
||||
}
|
||||
|
||||
@@ -118,35 +120,63 @@ class Cooperate {
|
||||
const res = transformObjectToTreeData(data)
|
||||
if (!res) return
|
||||
// 更新思维导图画布
|
||||
this.mindMap.renderer.setData(res)
|
||||
this.mindMap.render()
|
||||
this.mindMap.command.addHistory()
|
||||
this.mindMap.updateData(res)
|
||||
}
|
||||
|
||||
// 当前思维导图改变后的处理,触发同步
|
||||
onDataChange(data) {
|
||||
if (this.isSetData) {
|
||||
this.isSetData = false
|
||||
return
|
||||
}
|
||||
const res = transformTreeDataToObject(data)
|
||||
this.updateChanges(res)
|
||||
}
|
||||
|
||||
// 找出更新点
|
||||
updateChanges(data) {
|
||||
const { beforeCooperateUpdate } = this.mindMap.opt
|
||||
const oldData = this.currentData
|
||||
this.currentData = data
|
||||
this.ydoc.transact(() => {
|
||||
// 找出新增的或修改的
|
||||
const createOrUpdateList = []
|
||||
Object.keys(data).forEach(uid => {
|
||||
// 新增的或已经存在的,如果数据发生了改变
|
||||
if (!oldData[uid] || !isSameObject(oldData[uid], data[uid])) {
|
||||
this.ymap.set(uid, data[uid])
|
||||
createOrUpdateList.push({
|
||||
uid,
|
||||
data: data[uid],
|
||||
oldData: oldData[uid]
|
||||
})
|
||||
}
|
||||
})
|
||||
if (beforeCooperateUpdate && createOrUpdateList.length > 0) {
|
||||
beforeCooperateUpdate({
|
||||
type: 'createOrUpdate',
|
||||
list: createOrUpdateList,
|
||||
data
|
||||
})
|
||||
}
|
||||
createOrUpdateList.forEach(item => {
|
||||
this.ymap.set(item.uid, item.data)
|
||||
})
|
||||
// 找出删除的
|
||||
const deleteList = []
|
||||
Object.keys(oldData).forEach(uid => {
|
||||
if (!data[uid]) {
|
||||
this.ymap.delete(uid)
|
||||
deleteList.push({ uid, data: oldData[uid] })
|
||||
}
|
||||
})
|
||||
if (beforeCooperateUpdate && deleteList.length > 0) {
|
||||
beforeCooperateUpdate({
|
||||
type: 'delete',
|
||||
list: deleteList
|
||||
})
|
||||
}
|
||||
deleteList.forEach(item => {
|
||||
this.ymap.delete(item.uid)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -177,6 +207,12 @@ class Cooperate {
|
||||
this.waitNodeUidMap = {}
|
||||
}
|
||||
|
||||
// 监听思维导图数据的重新设置事件
|
||||
onSetData(data) {
|
||||
this.isSetData = true
|
||||
this.initData(data)
|
||||
}
|
||||
|
||||
// 设置用户信息
|
||||
/**
|
||||
* {
|
||||
@@ -220,6 +256,7 @@ class Cooperate {
|
||||
// 设置当前数据
|
||||
const data = Array.from(this.awareness.getStates().values())
|
||||
this.currentAwarenessData = data
|
||||
this.waitNodeUidMap = {}
|
||||
walk(data, (uid, node, userInfo) => {
|
||||
// 不显示自己
|
||||
if (userInfo.id === this.userInfo.id) return
|
||||
|
||||
397
simple-mind-map/src/plugins/Demonstrate.js
Normal file
@@ -0,0 +1,397 @@
|
||||
import {
|
||||
walk,
|
||||
getNodeTreeBoundingRect,
|
||||
fullscrrenEvent,
|
||||
fullScreen,
|
||||
exitFullScreen,
|
||||
formatGetNodeGeneralization
|
||||
} from '../utils/index'
|
||||
import { keyMap } from '../core/command/keyMap'
|
||||
|
||||
const defaultConfig = {
|
||||
boxShadowColor: 'rgba(0, 0, 0, 0.8)', // 高亮框四周的区域颜色
|
||||
borderRadius: '5px', // 高亮框的圆角大小
|
||||
transition: 'all 0.3s ease-out', // 高亮框动画的过渡
|
||||
zIndex: 9999, // 高亮框元素的层级
|
||||
padding: 20, // 高亮框的内边距
|
||||
margin: 50, // 高亮框的外边距
|
||||
openBlankMode: true // 是否开启填空模式,即带下划线的文本默认不显示,按回车键才依次显示
|
||||
}
|
||||
|
||||
// 演示插件
|
||||
class Demonstrate {
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
// 演示的步骤列表
|
||||
this.stepList = []
|
||||
// 当前所在步骤
|
||||
this.currentStepIndex = 0
|
||||
// 当前所在步骤对应的节点实例
|
||||
this.currentStepNode = null
|
||||
// 当前所在步骤节点的下划线文本数据
|
||||
this.currentUnderlineTextData = null
|
||||
// 临时的样式剩余
|
||||
this.tmpStyleEl = null
|
||||
// 高亮样式元素
|
||||
this.highlightEl = null
|
||||
this.transformState = null
|
||||
this.renderTree = null
|
||||
this.config = Object.assign(
|
||||
{ ...defaultConfig },
|
||||
this.mindMap.opt.demonstrateConfig || {}
|
||||
)
|
||||
}
|
||||
|
||||
// 进入演示模式
|
||||
enter() {
|
||||
// 全屏
|
||||
this.bindFullscreenEvent()
|
||||
// 如果已经全屏了
|
||||
if (document.fullscreenElement === this.mindMap.el) {
|
||||
this._enter()
|
||||
} else {
|
||||
// 否则申请全屏
|
||||
fullScreen(this.mindMap.el)
|
||||
}
|
||||
}
|
||||
|
||||
_enter() {
|
||||
// 添加演示用的临时的样式
|
||||
this.addTmpStyles()
|
||||
// 记录演示前的画布状态
|
||||
this.transformState = this.mindMap.view.getTransformData()
|
||||
// 记录演示前的画布数据
|
||||
this.renderTree = this.mindMap.getData()
|
||||
// 暂停收集历史记录
|
||||
this.mindMap.command.pause()
|
||||
// 暂停思维导图快捷键响应
|
||||
this.mindMap.keyCommand.pause()
|
||||
// 创建高亮元素
|
||||
this.createHighlightEl()
|
||||
// 计算步骤数据
|
||||
this.getStepList()
|
||||
// 收起所有节点
|
||||
this.mindMap.execCommand('UNEXPAND_ALL', false)
|
||||
const onRenderEnd = () => {
|
||||
this.mindMap.off('node_tree_render_end', onRenderEnd)
|
||||
// 聚焦到第一步
|
||||
this.jump(this.currentStepIndex)
|
||||
this.bindEvent()
|
||||
}
|
||||
this.mindMap.on('node_tree_render_end', onRenderEnd)
|
||||
}
|
||||
|
||||
// 退出演示模式
|
||||
exit() {
|
||||
exitFullScreen(this.mindMap.el)
|
||||
this.mindMap.updateData(this.renderTree)
|
||||
this.mindMap.view.setTransformData(this.transformState)
|
||||
this.renderTree = null
|
||||
this.transformState = null
|
||||
this.stepList = []
|
||||
this.currentStepIndex = 0
|
||||
this.currentStepNode = null
|
||||
this.currentUnderlineTextData = null
|
||||
this.unBindEvent()
|
||||
this.removeTmpStyles()
|
||||
this.removeHighlightEl()
|
||||
this.mindMap.command.recovery()
|
||||
this.mindMap.keyCommand.recovery()
|
||||
this.mindMap.emit('exit_demonstrate')
|
||||
}
|
||||
|
||||
// 添加临时的样式
|
||||
addTmpStyles() {
|
||||
this.tmpStyleEl = document.createElement('style')
|
||||
let cssText = `
|
||||
/* 画布所有元素禁止响应鼠标事件 */
|
||||
.smm-mind-map-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
/* 超链接图标允许响应鼠标事件 */
|
||||
.smm-node a {
|
||||
pointer-events: all;
|
||||
}
|
||||
/* 备注图标允许响应鼠标事件 */
|
||||
.smm-node .smm-node-note {
|
||||
pointer-events: all;
|
||||
}
|
||||
`
|
||||
if (this.config.openBlankMode) {
|
||||
cssText += `
|
||||
/* 带下划线的文本内容全部隐藏 */
|
||||
.smm-richtext-node-wrap u {
|
||||
opacity: 0;
|
||||
}
|
||||
`
|
||||
}
|
||||
this.tmpStyleEl.innerText = cssText
|
||||
document.head.appendChild(this.tmpStyleEl)
|
||||
}
|
||||
|
||||
// 移除临时的样式
|
||||
removeTmpStyles() {
|
||||
if (this.tmpStyleEl) document.head.removeChild(this.tmpStyleEl)
|
||||
}
|
||||
|
||||
// 创建高亮元素
|
||||
createHighlightEl() {
|
||||
if (!this.highlightEl) {
|
||||
// 高亮元素
|
||||
this.highlightEl = document.createElement('div')
|
||||
this.highlightEl.style.cssText = `
|
||||
position: absolute;
|
||||
box-shadow: 0 0 0 5000px ${this.config.boxShadowColor};
|
||||
border-radius: ${this.config.borderRadius};
|
||||
transition: ${this.config.transition};
|
||||
z-index: ${this.config.zIndex + 1};
|
||||
pointer-events: none;
|
||||
`
|
||||
this.mindMap.el.appendChild(this.highlightEl)
|
||||
}
|
||||
}
|
||||
|
||||
// 移除高亮元素
|
||||
removeHighlightEl() {
|
||||
if (this.highlightEl) {
|
||||
this.mindMap.el.removeChild(this.highlightEl)
|
||||
this.highlightEl = null
|
||||
}
|
||||
}
|
||||
|
||||
// 更新高亮元素的位置和大小
|
||||
updateHighlightEl({ left, top, width, height }) {
|
||||
const padding = this.config.padding
|
||||
if (left) {
|
||||
this.highlightEl.style.left = left - padding + 'px'
|
||||
}
|
||||
if (top) {
|
||||
this.highlightEl.style.top = top - padding + 'px'
|
||||
}
|
||||
if (width) {
|
||||
this.highlightEl.style.width = width + padding * 2 + 'px'
|
||||
}
|
||||
if (height) {
|
||||
this.highlightEl.style.height = height + padding * 2 + 'px'
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
this.onKeydown = this.onKeydown.bind(this)
|
||||
window.addEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
|
||||
// 绑定全屏事件
|
||||
bindFullscreenEvent() {
|
||||
this.onFullscreenChange = this.onFullscreenChange.bind(this)
|
||||
document.addEventListener(fullscrrenEvent, this.onFullscreenChange)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
unBindEvent() {
|
||||
window.removeEventListener('keydown', this.onKeydown)
|
||||
document.removeEventListener(fullscrrenEvent, this.onFullscreenChange)
|
||||
}
|
||||
|
||||
// 全屏状态改变
|
||||
onFullscreenChange() {
|
||||
if (!document.fullscreenElement) {
|
||||
this.exit()
|
||||
} else if (document.fullscreenElement === this.mindMap.el) {
|
||||
this._enter()
|
||||
}
|
||||
}
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
// 上一个
|
||||
if (e.keyCode === keyMap.Left) {
|
||||
this.prev()
|
||||
} else if (e.keyCode === keyMap.Right) {
|
||||
// 下一个
|
||||
this.next()
|
||||
} else if (e.keyCode === keyMap.Esc) {
|
||||
// 退出演示
|
||||
this.exit()
|
||||
} else if (e.keyCode === keyMap.Enter) {
|
||||
// 回车键显示隐藏的下划线文本
|
||||
this.showNextUnderlineText()
|
||||
}
|
||||
}
|
||||
|
||||
// 上一张
|
||||
prev() {
|
||||
if (this.currentStepIndex > 0) {
|
||||
this.jump(this.currentStepIndex - 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 下一张
|
||||
next() {
|
||||
const stepLength = this.stepList.length
|
||||
if (this.currentStepIndex < stepLength - 1) {
|
||||
this.jump(this.currentStepIndex + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示隐藏的下划线文本
|
||||
showNextUnderlineText() {
|
||||
if (
|
||||
!this.config.openBlankMode ||
|
||||
!this.currentStepNode ||
|
||||
!this.currentUnderlineTextData
|
||||
)
|
||||
return
|
||||
const { index, list, length } = this.currentUnderlineTextData
|
||||
if (index >= length) return
|
||||
const node = list[index]
|
||||
this.currentUnderlineTextData.index++
|
||||
node.node.style.opacity = 1
|
||||
}
|
||||
|
||||
// 跳转到某一张
|
||||
jump(index) {
|
||||
// 移除该当前下划线元素设置的样式
|
||||
if (this.currentUnderlineTextData) {
|
||||
this.currentUnderlineTextData.list.forEach(item => {
|
||||
item.node.style.opacity = ''
|
||||
})
|
||||
this.currentUnderlineTextData = null
|
||||
}
|
||||
this.currentStepNode = null
|
||||
this.currentStepIndex = index
|
||||
this.mindMap.emit(
|
||||
'demonstrate_jump',
|
||||
this.currentStepIndex,
|
||||
this.stepList.length
|
||||
)
|
||||
const step = this.stepList[index]
|
||||
// 这一步的节点数据
|
||||
const nodeData = step.node
|
||||
// 该节点的uid
|
||||
const uid = nodeData.data.uid
|
||||
// 根据uid在画布上找到该节点实例
|
||||
const node = this.mindMap.renderer.findNodeByUid(uid)
|
||||
// 如果该节点实例不存在,那么先展开到该节点
|
||||
if (!node) {
|
||||
this.mindMap.renderer.expandToNodeUid(uid, () => {
|
||||
const node = this.mindMap.renderer.findNodeByUid(uid)
|
||||
// 展开后还是没找到,那么就别进入了,否则会死循环
|
||||
if (node) {
|
||||
this.jump(index)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
// 1.聚焦到某个节点
|
||||
if (step.type === 'node') {
|
||||
this.currentStepNode = node
|
||||
// 当前节点存在带下划线的文本内容
|
||||
const uNodeList = this.config.openBlankMode ? node.group.find('u') : null
|
||||
if (uNodeList && uNodeList.length > 0) {
|
||||
this.currentUnderlineTextData = {
|
||||
index: 0,
|
||||
list: uNodeList,
|
||||
length: uNodeList.length
|
||||
}
|
||||
}
|
||||
// 适应画布大小
|
||||
this.mindMap.view.fit(
|
||||
() => {
|
||||
return node.group.rbox()
|
||||
},
|
||||
true,
|
||||
this.config.padding + this.config.margin
|
||||
)
|
||||
const rect = node.group.rbox()
|
||||
this.updateHighlightEl({
|
||||
left: rect.x,
|
||||
top: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
})
|
||||
} else {
|
||||
// 2.聚焦到某个节点的所有子节点
|
||||
// 聚焦该节点的所有子节点
|
||||
const task = () => {
|
||||
// 先收起该节点所有子节点的子节点
|
||||
nodeData.children.forEach(item => {
|
||||
item.data.expand = false
|
||||
})
|
||||
this.mindMap.render(() => {
|
||||
// 适应画布大小
|
||||
this.mindMap.view.fit(
|
||||
() => {
|
||||
const res = getNodeTreeBoundingRect(node, 0, 0, 0, 0, true)
|
||||
return {
|
||||
...res,
|
||||
x: res.left,
|
||||
y: res.top
|
||||
}
|
||||
},
|
||||
true,
|
||||
this.config.padding + this.config.margin
|
||||
)
|
||||
const res = getNodeTreeBoundingRect(node, 0, 0, 0, 0, true)
|
||||
this.updateHighlightEl(res)
|
||||
})
|
||||
}
|
||||
// 如果该节点是收起状态,那么需要先展开
|
||||
if (!nodeData.data.expand) {
|
||||
this.mindMap.execCommand('SET_NODE_EXPAND', node, true)
|
||||
const onRenderEnd = () => {
|
||||
this.mindMap.off('node_tree_render_end', onRenderEnd)
|
||||
task()
|
||||
}
|
||||
this.mindMap.on('node_tree_render_end', onRenderEnd)
|
||||
} else {
|
||||
// 否则直接聚焦
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深度度优先遍历所有节点,返回步骤列表
|
||||
getStepList() {
|
||||
walk(this.mindMap.renderer.renderTree, null, node => {
|
||||
this.stepList.push({
|
||||
type: 'node',
|
||||
node
|
||||
})
|
||||
// 添加概要步骤
|
||||
const generalizationList = formatGetNodeGeneralization(node.data)
|
||||
generalizationList.forEach(item => {
|
||||
// 没有uid的直接过滤掉,否则会死循环
|
||||
if (item.uid) {
|
||||
this.stepList.push({
|
||||
type: 'node',
|
||||
node: {
|
||||
data: item
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
if (node.children.length > 1) {
|
||||
this.stepList.push({
|
||||
type: 'children',
|
||||
node
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
}
|
||||
|
||||
Demonstrate.instanceName = 'demonstrate'
|
||||
|
||||
export default Demonstrate
|
||||
@@ -122,6 +122,10 @@ class Drag extends Base {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
// 停止自动移动
|
||||
if (this.mindMap.opt.autoMoveWhenMouseInEdgeOnDrag && this.mindMap.select) {
|
||||
this.mindMap.select.clearAutoMoveTimer()
|
||||
}
|
||||
this.isMousedown = false
|
||||
// 恢复被拖拽节点的临时设置
|
||||
this.beingDragNodeList.forEach(node => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
|
||||
import { transformToMarkdown } from '../parse/toMarkdown'
|
||||
import { ERROR_TYPES } from '../constants/constant'
|
||||
import { transformToTxt } from '../parse/toTxt'
|
||||
|
||||
// 导出插件
|
||||
class Export {
|
||||
@@ -46,13 +47,26 @@ class Export {
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
async getSvgData() {
|
||||
let { exportPaddingX, exportPaddingY, errorHandler, resetCss } =
|
||||
this.mindMap.opt
|
||||
let { svg, svgHTML } = this.mindMap.getSvgData({
|
||||
async getSvgData(node) {
|
||||
let {
|
||||
exportPaddingX,
|
||||
exportPaddingY,
|
||||
errorHandler,
|
||||
resetCss,
|
||||
addContentToHeader,
|
||||
addContentToFooter
|
||||
} = this.mindMap.opt
|
||||
let { svg, svgHTML, clipData } = this.mindMap.getSvgData({
|
||||
paddingX: exportPaddingX,
|
||||
paddingY: exportPaddingY
|
||||
paddingY: exportPaddingY,
|
||||
addContentToHeader,
|
||||
addContentToFooter,
|
||||
node
|
||||
})
|
||||
if (clipData) {
|
||||
clipData.paddingX = exportPaddingX
|
||||
clipData.paddingY = exportPaddingY
|
||||
}
|
||||
// svg的image标签,把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
const task1 = this.createTransformImgTaskList(
|
||||
svg,
|
||||
@@ -87,12 +101,13 @@ class Export {
|
||||
}
|
||||
return {
|
||||
node: svg,
|
||||
str: svgHTML
|
||||
str: svgHTML,
|
||||
clipData
|
||||
}
|
||||
}
|
||||
|
||||
// svg转png
|
||||
svgToPng(svgSrc, transparent) {
|
||||
svgToPng(svgSrc, transparent, clipData = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||
@@ -106,6 +121,15 @@ class Export {
|
||||
)
|
||||
let imgWidth = img.width
|
||||
let imgHeight = img.height
|
||||
// 如果是裁减操作的话,那么需要手动添加内边距,及调整图片大小为实际的裁减区域的大小,不要忘了内边距哦
|
||||
let paddingX = 0
|
||||
let paddingY = 0
|
||||
if (clipData) {
|
||||
paddingX = clipData.paddingX
|
||||
paddingY = clipData.paddingY
|
||||
imgWidth = clipData.width + paddingX * 2
|
||||
imgHeight = clipData.height + paddingY * 2
|
||||
}
|
||||
// 检查是否超出canvas支持的像素上限
|
||||
const maxSize = 16384 / dpr
|
||||
const maxArea = maxSize * maxSize
|
||||
@@ -132,7 +156,22 @@ class Export {
|
||||
await this.drawBackgroundToCanvas(ctx, imgWidth, imgHeight)
|
||||
}
|
||||
// 图片绘制到canvas里
|
||||
ctx.drawImage(img, 0, 0, imgWidth, imgHeight)
|
||||
// 如果有裁减数据,那么需要进行裁减
|
||||
if (clipData) {
|
||||
ctx.drawImage(
|
||||
img,
|
||||
clipData.left,
|
||||
clipData.top,
|
||||
clipData.width,
|
||||
clipData.height,
|
||||
paddingX,
|
||||
paddingY,
|
||||
clipData.width,
|
||||
clipData.height
|
||||
)
|
||||
} else {
|
||||
ctx.drawImage(img, 0, 0, imgWidth, imgHeight)
|
||||
}
|
||||
resolve(canvas.toDataURL())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
@@ -216,13 +255,24 @@ class Export {
|
||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
||||
*/
|
||||
async png(name, transparent = false) {
|
||||
const { str } = await this.getSvgData()
|
||||
async png(name, transparent = false, node = null) {
|
||||
this.handleNodeExport(node)
|
||||
const { str, clipData } = await this.getSvgData(node)
|
||||
const svgUrl = await this.fixSvgStrAndToBlob(str)
|
||||
const res = await this.svgToPng(svgUrl, transparent)
|
||||
const res = await this.svgToPng(svgUrl, transparent, clipData)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出指定节点,如果该节点是激活状态,那么取消激活和隐藏展开收起按钮
|
||||
handleNodeExport(node) {
|
||||
if (node && node.getData('isActive')) {
|
||||
node.deactivate()
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn && node.getData('expand')) {
|
||||
node.removeExpandBtn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出为pdf
|
||||
async pdf(name, transparent = false) {
|
||||
if (!this.mindMap.doExportPDF) {
|
||||
@@ -294,6 +344,15 @@ class Export {
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// txt文件
|
||||
async txt() {
|
||||
const data = this.mindMap.getData()
|
||||
const content = transformToTxt(data)
|
||||
const blob = new Blob([content])
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
Export.instanceName = 'doExport'
|
||||
|
||||
@@ -93,10 +93,7 @@ class MiniMap {
|
||||
|
||||
return {
|
||||
getImgUrl: async callback => {
|
||||
const blob = new Blob([svgStr], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
const res = await readBlob(blob)
|
||||
const res = await this.mindMap.doExport.fixSvgStrAndToBlob(svgStr)
|
||||
callback(res)
|
||||
},
|
||||
svgHTML: svgStr, // 小地图html
|
||||
|
||||
92
simple-mind-map/src/plugins/RainbowLines.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import { walk, getNodeDataIndex } from '../utils/index'
|
||||
|
||||
const defaultColorsList = [
|
||||
'rgb(255, 213, 73)',
|
||||
'rgb(255, 136, 126)',
|
||||
'rgb(107, 225, 141)',
|
||||
'rgb(151, 171, 255)',
|
||||
'rgb(129, 220, 242)',
|
||||
'rgb(255, 163, 125)',
|
||||
'rgb(152, 132, 234)'
|
||||
]
|
||||
|
||||
// 彩虹线条插件
|
||||
class RainbowLines {
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
}
|
||||
|
||||
// 更新彩虹线条配置
|
||||
updateRainLinesConfig(config = {}) {
|
||||
const newConfig = this.mindMap.opt.rainbowLinesConfig || {}
|
||||
newConfig.open = !!config.open
|
||||
newConfig.colorsList = Array.isArray(config.colorsList)
|
||||
? config.colorsList
|
||||
: []
|
||||
// 如果开启彩虹线条,那么先移除所有节点的自定义连线颜色配置
|
||||
if (this.mindMap.opt.rainbowLinesConfig.open) {
|
||||
this.removeNodeLineColor()
|
||||
}
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 删除所有节点的连线颜色
|
||||
removeNodeLineColor() {
|
||||
const tree = this.mindMap.renderer.renderTree
|
||||
if (!tree) return
|
||||
walk(
|
||||
tree,
|
||||
null,
|
||||
cur => {
|
||||
delete cur.data.lineColor
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
|
||||
// 获取一个节点的第二层级的祖先节点
|
||||
getSecondLayerAncestor(node) {
|
||||
if (node.layerIndex === 0) {
|
||||
return null
|
||||
} else if (node.layerIndex === 1) {
|
||||
return node
|
||||
} else {
|
||||
let res = null
|
||||
let parent = node.parent
|
||||
while (parent) {
|
||||
if (parent.layerIndex === 1) {
|
||||
return parent
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
// 获取颜色列表
|
||||
getColorsList() {
|
||||
const { rainbowLinesConfig } = this.mindMap.opt
|
||||
return rainbowLinesConfig &&
|
||||
Array.isArray(rainbowLinesConfig.colorsList) &&
|
||||
rainbowLinesConfig.colorsList.length > 0
|
||||
? rainbowLinesConfig.colorsList
|
||||
: [...defaultColorsList]
|
||||
}
|
||||
|
||||
// 获取一个节点的彩虹线条颜色
|
||||
getNodeColor(node) {
|
||||
const { rainbowLinesConfig } = this.mindMap.opt
|
||||
if (!rainbowLinesConfig || !rainbowLinesConfig.open) return ''
|
||||
const ancestor = this.getSecondLayerAncestor(node)
|
||||
if (!ancestor) return
|
||||
const index = getNodeDataIndex(ancestor)
|
||||
const colorsList = this.getColorsList()
|
||||
return colorsList[index % colorsList.length]
|
||||
}
|
||||
}
|
||||
|
||||
RainbowLines.instanceName = 'rainbowLines'
|
||||
|
||||
export default RainbowLines
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
getVisibleColorFromTheme,
|
||||
isUndef,
|
||||
checkSmmFormatData,
|
||||
removeHtmlNodeByClass
|
||||
removeHtmlNodeByClass,
|
||||
formatGetNodeGeneralization
|
||||
} from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
@@ -327,8 +328,8 @@ class RichText {
|
||||
list.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
|
||||
// if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
// node.generalizationBelongNode.updateGeneralization()
|
||||
// 概要节点
|
||||
// node.generalizationBelongNode.updateGeneralization()
|
||||
// }
|
||||
this.mindMap.render()
|
||||
})
|
||||
@@ -641,6 +642,7 @@ class RichText {
|
||||
|
||||
// 将所有节点转换成非富文本节点
|
||||
transformAllNodesToNormalNode() {
|
||||
if (!this.mindMap.renderer.renderTree) return
|
||||
walk(
|
||||
this.mindMap.renderer.renderTree,
|
||||
null,
|
||||
@@ -648,7 +650,14 @@ class RichText {
|
||||
if (node.data.richText) {
|
||||
node.data.richText = false
|
||||
node.data.text = getTextFromHtml(node.data.text)
|
||||
// delete node.data.uid
|
||||
}
|
||||
// 概要
|
||||
if (node.data) {
|
||||
const generalizationList = formatGetNodeGeneralization(node.data)
|
||||
generalizationList.forEach(item => {
|
||||
item.richText = false
|
||||
item.text = getTextFromHtml(item.text)
|
||||
})
|
||||
}
|
||||
},
|
||||
null,
|
||||
@@ -669,6 +678,14 @@ class RichText {
|
||||
root.data.richText = true
|
||||
root.data.resetRichText = true
|
||||
}
|
||||
// 概要
|
||||
if (root.data) {
|
||||
const generalizationList = formatGetNodeGeneralization(root.data)
|
||||
generalizationList.forEach(item => {
|
||||
item.richText = true
|
||||
item.resetRichText = true
|
||||
})
|
||||
}
|
||||
if (root.children && root.children.length > 0) {
|
||||
Array.from(root.children).forEach(item => {
|
||||
walk(item)
|
||||
|
||||
@@ -40,6 +40,7 @@ class Scrollbar {
|
||||
this.mindMap.on('mouseup', this.onMouseup)
|
||||
this.mindMap.on('node_tree_render_end', this.updateScrollbar)
|
||||
this.mindMap.on('view_data_change', this.updateScrollbar)
|
||||
this.mindMap.on('resize', this.updateScrollbar)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -48,6 +49,7 @@ class Scrollbar {
|
||||
this.mindMap.off('mouseup', this.onMouseup)
|
||||
this.mindMap.off('node_tree_render_end', this.updateScrollbar)
|
||||
this.mindMap.off('view_data_change', this.updateScrollbar)
|
||||
this.mindMap.off('resize', this.updateScrollbar)
|
||||
}
|
||||
|
||||
// 渲染后、数据改变需要更新滚动条
|
||||
@@ -202,7 +204,8 @@ class Scrollbar {
|
||||
yOffset -
|
||||
paddingY * t.scaleY +
|
||||
paddingY -
|
||||
rootCenterOffset.y * t.scaleY
|
||||
rootCenterOffset.y * t.scaleY +
|
||||
((this.mindMap.height - this.mindMap.initHeight) / 2) * t.scaleY // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
|
||||
this.mindMap.view.translateYTo(chartTop)
|
||||
this.emitEvent({
|
||||
horizontal: scrollbarData.horizontal,
|
||||
@@ -238,7 +241,8 @@ class Scrollbar {
|
||||
xOffset -
|
||||
paddingX * t.scaleX +
|
||||
paddingX -
|
||||
rootCenterOffset.x * t.scaleX
|
||||
rootCenterOffset.x * t.scaleX +
|
||||
((this.mindMap.width - this.mindMap.initWidth) / 2) * t.scaleX // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
|
||||
this.mindMap.view.translateXTo(chartLeft)
|
||||
this.emitEvent({
|
||||
vertical: scrollbarData.vertical,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
isUndef,
|
||||
replaceHtmlText
|
||||
} from '../utils/index'
|
||||
import Node from '../core/render/node/Node'
|
||||
|
||||
// 搜索插件
|
||||
class Search {
|
||||
@@ -69,14 +70,14 @@ class Search {
|
||||
// 结束搜索
|
||||
endSearch() {
|
||||
if (!this.isSearching) return
|
||||
if (this.mindMap.opt.readonly && this.matchNodeList[this.currentIndex]) {
|
||||
this.matchNodeList[this.currentIndex].closeHighlight()
|
||||
}
|
||||
this.searchText = ''
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
this.notResetSearchText = false
|
||||
this.isSearching = false
|
||||
if (this.mindMap.opt.readonly) {
|
||||
this.mindMap.renderer.closeHighlightNode()
|
||||
}
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
@@ -84,8 +85,16 @@ class Search {
|
||||
doSearch() {
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
let { richText, text } = node.getData()
|
||||
const { isOnlySearchCurrentRenderNodes } = this.mindMap.opt
|
||||
// 如果要搜索收起来的节点,那么要遍历渲染树而不是节点树
|
||||
const tree = isOnlySearchCurrentRenderNodes
|
||||
? this.mindMap.renderer.root
|
||||
: this.mindMap.renderer.renderTree
|
||||
if (!tree) return
|
||||
bfsWalk(tree, node => {
|
||||
let { richText, text } = isOnlySearchCurrentRenderNodes
|
||||
? node.getData()
|
||||
: node.data
|
||||
if (richText) {
|
||||
text = getTextFromHtml(text)
|
||||
}
|
||||
@@ -95,6 +104,11 @@ class Search {
|
||||
})
|
||||
}
|
||||
|
||||
// 判断对象是否是节点实例
|
||||
isNodeInstance(node) {
|
||||
return node instanceof Node
|
||||
}
|
||||
|
||||
// 搜索下一个,定位到下一个匹配节点
|
||||
searchNext(callback) {
|
||||
if (!this.isSearching || this.matchNodeList.length <= 0) return
|
||||
@@ -103,14 +117,24 @@ class Search {
|
||||
} else {
|
||||
this.currentIndex = 0
|
||||
}
|
||||
let currentNode = this.matchNodeList[this.currentIndex]
|
||||
const currentNode = this.matchNodeList[this.currentIndex]
|
||||
this.notResetSearchText = true
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
|
||||
this.notResetSearchText = false
|
||||
const uid = this.isNodeInstance(currentNode)
|
||||
? currentNode.getData('uid')
|
||||
: currentNode.data.uid
|
||||
const targetNode = this.mindMap.renderer.findNodeByUid(uid)
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', uid, node => {
|
||||
if (!this.isNodeInstance(currentNode)) {
|
||||
this.matchNodeList[this.currentIndex] = node
|
||||
}
|
||||
callback()
|
||||
// 只读模式下节点无法激活,所以通过高亮的方式
|
||||
if (this.mindMap.opt.readonly) {
|
||||
this.mindMap.renderer.highlightNode(currentNode)
|
||||
node.highlight()
|
||||
}
|
||||
// 如果当前节点实例已经存在,则不会触发data_change事件,那么需要手动把标志复位
|
||||
if (targetNode) {
|
||||
this.notResetSearchText = false
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -154,15 +178,20 @@ class Search {
|
||||
return
|
||||
replaceText = String(replaceText)
|
||||
this.matchNodeList.forEach(node => {
|
||||
let text = this.getReplacedText(node, this.searchText, replaceText)
|
||||
this.mindMap.renderer.setNodeDataRender(
|
||||
node,
|
||||
{
|
||||
text,
|
||||
resetRichText: !!node.getData('richText')
|
||||
},
|
||||
true
|
||||
)
|
||||
const text = this.getReplacedText(node, this.searchText, replaceText)
|
||||
if (this.isNodeInstance(node)) {
|
||||
this.mindMap.renderer.setNodeDataRender(
|
||||
node,
|
||||
{
|
||||
text,
|
||||
resetRichText: !!node.getData('richText')
|
||||
},
|
||||
true
|
||||
)
|
||||
} else {
|
||||
node.data.text = text
|
||||
node.data.resetRichText = !!node.data.richText
|
||||
}
|
||||
})
|
||||
this.mindMap.render()
|
||||
this.mindMap.command.addHistory()
|
||||
@@ -171,7 +200,9 @@ class Search {
|
||||
|
||||
// 获取某个节点替换后的文本
|
||||
getReplacedText(node, searchText, replaceText) {
|
||||
let { richText, text } = node.getData()
|
||||
let { richText, text } = this.isNodeInstance(node)
|
||||
? node.getData()
|
||||
: node.data
|
||||
if (richText) {
|
||||
return replaceHtmlText(text, searchText, replaceText)
|
||||
} else {
|
||||
|
||||
@@ -44,7 +44,7 @@ class Select {
|
||||
}
|
||||
let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
|
||||
if (
|
||||
!e.ctrlKey &&
|
||||
!(e.ctrlKey || e.metaKey) &&
|
||||
(useLeftKeySelectionRightKeyDrag ? e.which !== 1 : e.which !== 3)
|
||||
) {
|
||||
return
|
||||
@@ -237,11 +237,13 @@ class Select {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.addNodeToActiveList(node)
|
||||
this.mindMap.renderer.emitNodeActiveEvent()
|
||||
} else if (node.getData('isActive')) {
|
||||
if (!node.getData('isActive')) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.removeNodeFromActiveList(node)
|
||||
this.mindMap.renderer.emitNodeActiveEvent()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||