This commit is contained in:
街角小林 2024-05-13 17:35:55 +08:00
commit 164dec88b0
142 changed files with 2836 additions and 353 deletions

5
Dockerfile Normal file
View File

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

View File

@ -31,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.概要节点后面继续添加节点;
>
> 如果你需要以上特性,那么本库可能无法满足你的需求。
# 安装
@ -84,15 +97,15 @@ const mindMap = new MindMap({
# License
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用。
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用。如不想保留可联系作者。
# 微信交流群
群聊人数较多,无法通过二维码入群,可以微信添加`wanglinguanfang`拉你入群。
群聊人数较多,无法通过二维码入群,可以微信添加`wanglinguanfang`拉你入群。思维导图相关问题皆可在群里提问,不必私聊作者。
# star
如果喜欢本项目欢迎点个star这对我们很重要。
如果喜欢本项目,欢迎点个 star这对我们很重要。
[![Star History Chart](https://api.star-history.com/svg?repos=wanglin2/mind-map&type=Date)](https://star-history.com/#wanglin2/mind-map&Date)
@ -320,4 +333,36 @@ const mindMap = new MindMap({
<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>

2
dist/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

2
dist/js/app.js vendored

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>//
window.externalPublicPath = './dist/'
// 接管应用
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?ac10265d54028b4359f6" rel="stylesheet"><link href="dist/css/app.css?ac10265d54028b4359f6" 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?ac10265d54028b4359f6"></script><script src="dist/js/app.js?ac10265d54028b4359f6"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?78d97b280dca5d10b8bc"></script><script src="dist/js/app.js?78d97b280dca5d10b8bc"></script></body></html>

30
nginx.conf Normal file
View File

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

View File

@ -29,7 +29,7 @@ MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.9.10'
MindMap.version = '0.9.12'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)

View File

@ -10,17 +10,16 @@ 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,
isUndef,
handleGetSvgDataExtraContent
handleGetSvgDataExtraContent,
getNodeTreeBoundingRect
} from './src/utils'
import defaultTheme, {
checkIsNodeSizeIndependenceConfig
@ -129,13 +128,18 @@ class MindMap {
// 创建容器元素
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')
@ -228,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
}
}
// 设置主题
@ -420,7 +415,8 @@ class MindMap {
paddingY = 0,
ignoreWatermark = false,
addContentToHeader,
addContentToFooter
addContentToFooter,
node
} = {}) {
const { cssTextList, header, headerHeight, footer, footerHeight } =
handleGetSvgDataExtraContent({
@ -438,6 +434,17 @@ 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
@ -517,6 +524,7 @@ class MindMap {
return {
svg: clone, // 思维导图图形的整体svg元素包括svg画布容器、g实际的思维导图组
svgHTML: clone.svg(), // svg字符串
clipData,
rect: {
...rect, // 思维导图图形未缩放时的位置尺寸等信息
ratio: rect.width / rect.height // 思维导图图形的宽高比
@ -584,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()

View File

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

View File

@ -317,12 +317,6 @@ export const nodeDataNoStylePropList = [
'attachmentName'
]
// 数据缓存
export const commonCaches = {
measureCustomNodeContentSizeEl: null,
measureRichtextNodeTextSizeEl: null
}
// 错误类型
export const ERROR_TYPES = {
READ_CLIPBOARD_ERROR: 'read_clipboard_error',

View File

@ -318,5 +318,13 @@ export const defaultOpt = {
}
*/
addContentToHeader: null,
addContentToFooter: null
addContentToFooter: null,
// 演示插件配置
demonstrateConfig: null,
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
resetScaleOnMoveNodeToCenter: false,
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
createNodePrefixContent: null,
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
createNodePostfixContent: null
}

View File

@ -23,6 +23,18 @@ class Command {
this.mindMap.opt.addHistoryTime,
this
)
// 是否暂停收集历史数据
this.isPause = false
}
// 暂停收集历史数据
pause() {
this.isPause = true
}
// 恢复收集历史数据
recovery() {
this.isPause = false
}
// 清空历史数据
@ -88,7 +100,7 @@ class Command {
// 添加回退数据
addHistory() {
if (this.mindMap.opt.readonly) {
if (this.mindMap.opt.readonly || this.isPause) {
return
}
const lastData =

View File

@ -30,7 +30,8 @@ import {
createSmmFormatData,
checkSmmFormatData,
checkIsNodeStyleDataKey,
removeRichTextStyes
removeRichTextStyes,
formatGetNodeGeneralization
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
@ -520,7 +521,7 @@ class Render {
}
// 添加节点到激活列表里
addNodeToActiveList(node) {
addNodeToActiveList(node, notEmitBeforeNodeActiveEvent = false) {
if (
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
node.userList.length > 0
@ -528,6 +529,9 @@ class Render {
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)
}
@ -989,8 +993,9 @@ class Render {
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
@ -1236,7 +1241,7 @@ class Render {
root.nodeData.children = []
} else {
// 如果只选中了一个节点,删除后激活其兄弟节点或者父节点
needActiveNode = this.getNextActiveNode()
needActiveNode = this.getNextActiveNode(list)
for (let i = 0; i < list.length; i++) {
const node = list[i]
const currentEditNode = this.textEdit.getCurrentEditNode()
@ -1291,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) {
@ -1323,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 &&
@ -1496,7 +1505,7 @@ class Render {
}
// 收起所有
unexpandAllNode() {
unexpandAllNode(isSetRootNodeCenter = true) {
if (!this.renderTree) return
walk(
this.renderTree,
@ -1512,7 +1521,9 @@ class Render {
0
)
this.mindMap.render(() => {
this.setRootNodeCenter()
if (isSetRootNodeCenter) {
this.setRootNodeCenter()
}
})
}
@ -1632,7 +1643,7 @@ class Render {
}
// 添加节点概要
addGeneralization(data) {
addGeneralization(data, openEdit = true) {
if (this.activeNodeList.length <= 0) {
return
}
@ -1644,13 +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,
uid: createUid()
uid: createUid(),
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
let generalization = item.node.getData('generalization')
if (generalization) {
@ -1670,6 +1690,10 @@ class Render {
expand: true
})
})
// 需要清除原来激活的节点
if (focusNewNode) {
this.clearActiveNodeList()
}
this.mindMap.render(() => {
// 修复祖先节点存在概要时位置未更新的问题
// 修复同时给存在上下级关系的节点添加概要时重叠的问题
@ -1776,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)
}
}
// 回到中心主题,即设置根节点到画布中心
@ -1803,14 +1836,24 @@ class Render {
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 => {
@ -1819,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 {
@ -1834,12 +1889,25 @@ class Render {
res = node
return true
}
// 概要节点
let isGeneralization = false
;(node._generalizationList || []).forEach(item => {
if (item.generalizationNode.getData('uid') === uid) {
res = item.generalizationNode
isGeneralization = true
}
})
if (isGeneralization) {
return true
}
})
return res
}
// 高亮节点或子节点
highlightNode(node, range) {
// 如果当前正在渲染,那么不进行高亮,因为节点位置可能不正确
if (this.isRendering) return
const { highlightNodeBoxStyle = {} } = this.mindMap.opt
if (!this.highlightBoxNode) {
this.highlightBoxNode = new Polygon()

View File

@ -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 {
@ -76,6 +76,8 @@ class Node {
this.noteEl = null
this.noteContentIsShow = false
this._attachmentData = null
this._prefixData = null
this._postfixData = null
this._expandBtn = null
this._lastExpandBtnType = null
this._showExpandBtn = false
@ -182,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)
}
@ -201,6 +208,12 @@ class Node {
this._tagData = this.createTagNode()
this._noteData = this.createNoteNode()
this._attachmentData = this.createAttachmentNode()
this._prefixData = createNodePrefixContent
? createNodePrefixContent(this)
: null
this._postfixData = createNodePostfixContent
? createNodePostfixContent(this)
: null
}
// 计算节点的宽高
@ -237,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) => {
@ -272,7 +290,15 @@ class Node {
// 附件
if (this._attachmentData) {
textContentWidth += this._attachmentData.width
textContentHeight = Math.max(textContentHeight, this._attachmentData.height)
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
@ -334,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
@ -352,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) {
@ -368,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)
@ -414,6 +456,19 @@ class Node {
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,
@ -466,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)
@ -477,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)
@ -510,7 +565,7 @@ class Node {
// 双击事件
this.group.on('dblclick', e => {
const { readonly, onlyOneEnableActiveNodeOnCooperate } = this.mindMap.opt
if (readonly || e.ctrlKey) {
if (readonly || e.ctrlKey || e.metaKey) {
return
}
e.stopPropagation()
@ -558,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) {
@ -569,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 {
// 更新展开收起按钮
@ -580,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()

View File

@ -4,19 +4,13 @@ import {
removeHtmlStyle,
addHtmlStyle,
checkIsRichText,
isUndef
isUndef,
createForeignObjectNode
} from '../../../utils'
import {
Image as SVGImage,
SVG,
A,
G,
Rect,
Text,
ForeignObject
} from '@svgdotjs/svg.js'
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
import iconsSvg from '../../../svg/icons'
import { CONSTANTS, commonCaches } from '../../../constants/constant'
import { CONSTANTS } from '../../../constants/constant'
import { defenseXSS } from '../../../utils/xss'
// 创建图片节点
function createImgNode() {
@ -148,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')
@ -174,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,
@ -287,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)
@ -313,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' }))
// 备注图标
@ -413,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

View File

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

View File

@ -37,7 +37,7 @@ class View {
this.mindMap.event.on('drag', (e, event) => {
// 按住ctrl键拖动为多选
// 禁用拖拽
if (e.ctrlKey || this.mindMap.opt.isDisableDrag) {
if (e.ctrlKey || e.metaKey || this.mindMap.opt.isDisableDrag) {
return
}
if (this.firstDrag) {
@ -72,7 +72,11 @@ class View {
return customHandleMousewheel(e)
}
// 1.鼠标滚轮事件控制缩放
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM || e.ctrlKey) {
if (
mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM ||
e.ctrlKey ||
e.metaKey
) {
if (disableMouseWheelZoom) return
const { x: clientX, y: clientY } = this.mindMap.toPos(
e.clientX,
@ -276,11 +280,12 @@ class View {
}
// 适应画布大小
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
@ -290,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 {
@ -308,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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,15 +47,26 @@ class Export {
}
// 获取svg数据
async getSvgData() {
let { exportPaddingX, exportPaddingY, errorHandler, resetCss, addContentToHeader, addContentToFooter } =
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,
addContentToHeader,
addContentToFooter
addContentToFooter,
node
})
if (clipData) {
clipData.paddingX = exportPaddingX
clipData.paddingY = exportPaddingY
}
// svg的image标签把图片的url转换成data:url类型否则导出会丢失图片
const task1 = this.createTransformImgTaskList(
svg,
@ -90,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()
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
@ -109,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
@ -135,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)
@ -219,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) {

View File

@ -8,7 +8,8 @@ import {
getVisibleColorFromTheme,
isUndef,
checkSmmFormatData,
removeHtmlNodeByClass
removeHtmlNodeByClass,
formatGetNodeGeneralization
} from '../utils'
import { CONSTANTS } from '../constants/constant'
@ -327,8 +328,8 @@ class RichText {
list.forEach(node => {
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
// if (node.isGeneralization) {
// 概要节点
// node.generalizationBelongNode.updateGeneralization()
// 概要节点
// node.generalizationBelongNode.updateGeneralization()
// }
this.mindMap.render()
})
@ -649,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,
@ -670,6 +678,14 @@ class RichText {
root.data.richText = true
root.data.resetRichText = true
}
// 概要
if (root.data) {
const generalizationList = formatGetNodeGeneralization(root.data)
generalizationList.forEach(item => {
item.richText = true
item.resetRichText = true
})
}
if (root.children && root.children.length > 0) {
Array.from(root.children).forEach(item => {
walk(item)

View File

@ -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()
}
})
}

View File

@ -157,9 +157,10 @@ export const copyRenderTree = (tree, root, removeActiveState = false) => {
tree.data = simpleDeepClone(root.data)
if (removeActiveState) {
tree.data.isActive = false
if (tree.data.generalization) {
tree.data.generalization.isActive = false
}
const generalizationList = formatGetNodeGeneralization(tree.data)
generalizationList.forEach(item => {
item.isActive = false
})
}
tree.children = []
if (root.children && root.children.length > 0) {
@ -1335,9 +1336,7 @@ export const handleGetSvgDataExtraContent = ({
const { el, cssText, height } = res
if (el instanceof HTMLElement) {
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
const foreignObject = new ForeignObject()
foreignObject.height(height)
foreignObject.add(el)
const foreignObject = createForeignObjectNode({ el, height })
callback(foreignObject, height)
}
if (cssText) {
@ -1361,3 +1360,119 @@ export const handleGetSvgDataExtraContent = ({
footerHeight
}
}
// 获取指定节点的包围框信息
export const getNodeTreeBoundingRect = (
node,
x = 0,
y = 0,
paddingX = 0,
paddingY = 0,
excludeSelf = false
) => {
let minX = Infinity
let maxX = -Infinity
let minY = Infinity
let maxY = -Infinity
const walk = (root, isRoot) => {
if (!(isRoot && excludeSelf)) {
const { x, y, width, height } = root.group
.findOne('.smm-node-shape')
.rbox()
if (x < minX) {
minX = x
}
if (x + width > maxX) {
maxX = x + width
}
if (y < minY) {
minY = y
}
if (y + height > maxY) {
maxY = y + height
}
}
if (root._generalizationList.length > 0) {
root._generalizationList.forEach(item => {
walk(item.generalizationNode)
})
}
if (root.children) {
root.children.forEach(item => {
walk(item)
})
}
}
walk(node, true)
minX = minX - x + paddingX
minY = minY - y + paddingY
maxX = maxX - x + paddingX
maxY = maxY - y + paddingY
return {
left: minX,
top: minY,
width: maxX - minX,
height: maxY - minY
}
}
// 全屏事件检测
const getOnfullscreEnevt = () => {
if (document.documentElement.requestFullScreen) {
return 'fullscreenchange'
} else if (document.documentElement.webkitRequestFullScreen) {
return 'webkitfullscreenchange'
} else if (document.documentElement.mozRequestFullScreen) {
return 'mozfullscreenchange'
} else if (document.documentElement.msRequestFullscreen) {
return 'msfullscreenchange'
}
}
export const fullscrrenEvent = getOnfullscreEnevt()
// 全屏
export const fullScreen = element => {
if (element.requestFullScreen) {
element.requestFullScreen()
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen()
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen()
}
}
// 退出全屏
export const exitFullScreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen()
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen()
}
}
// 创建foreignObject节点
export const createForeignObjectNode = ({ el, width, height }) => {
const foreignObject = new ForeignObject()
if (width !== undefined) {
foreignObject.width(width)
}
if (height !== undefined) {
foreignObject.height(height)
}
foreignObject.add(el)
return foreignObject
}
// 格式化获取节点的概要数据
export const formatGetNodeGeneralization = data => {
const generalization = data.generalization
if (generalization) {
return Array.isArray(generalization) ? generalization : [generalization]
} else {
return []
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,48 @@
/**
* 防御 XSS 攻击过滤恶意 HTML 标签和属性
* @param {string} text 需要过滤的文本
* @returns {string} 过滤后的文本
*/
export function defenseXSS(text) {
text = String(text)
// 初始化结果变量
let result = text;
// 使用正则表达式匹配 HTML 标签
const match = text.match(/<(\S*?)[^>]*>.*?|<.*? \/>/g);
if (match == null) {
// 如果没有匹配到任何标签,则直接返回原始文本
return text;
}
// 遍历匹配到的标签
for (let value of match) {
// 定义白名单属性正则表达式style、target、href
const whiteAttrRegex = new RegExp(/(style|target|href)=["'][^"']*["']/g);
// 定义黑名单href正则表达式javascript:
const aHrefBlackRegex = new RegExp(/href=["']javascript:/g);
// 过滤 HTML 标签
const filterHtml = value.replace(
// 匹配属性键值对key="value"
/([a-zA-Z-]+)\s*=\s*["']([^"']*)["']/g,
(text) => {
// 如果属性值包含黑名单href或不在白名单中则删除该属性
if (aHrefBlackRegex.test(text) || !whiteAttrRegex.test(text)) {
return "";
}
// 否则,保留该属性
return text;
}
);
// 将过滤后的标签替换回原始文本
result = result.replace(value, filterHtml);
}
// 返回最终结果
return result;
}

View File

@ -1,6 +1,6 @@
{
"name": "thoughts",
"version": "0.9.10",
"version": "0.9.12",
"private": true,
"description": "一个简洁的思维导图",
"author": "街角小林<1013335014@qq.com>",

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1711935414521') format('woff2'),
url('iconfont.woff?t=1711935414521') format('woff'),
url('iconfont.ttf?t=1711935414521') format('truetype');
src: url('iconfont.woff2?t=1713438156457') format('woff2'),
url('iconfont.woff?t=1713438156457') format('woff'),
url('iconfont.ttf?t=1713438156457') format('truetype');
}
.iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.iconyanshibofang:before {
content: "\e648";
}
.iconfujian:before {
content: "\e88a";
}

View File

@ -99,7 +99,8 @@ export default {
removeHyperlink: 'Remove hyperlink',
removeNote: 'Remove note',
removeCustomStyles: 'Remove custom styles',
removeAllNodeCustomStyles: 'Remove all node custom styles'
removeAllNodeCustomStyles: 'Remove all node custom styles',
exportNodeToPng: 'Export node to png'
},
count: {
words: 'Words',
@ -139,6 +140,9 @@ export default {
fullscreenShow: 'Full screen show',
fullscreenEdit: 'Full screen edit'
},
demonstrate: {
demonstrate: 'Enter demonstration mode'
},
import: {
title: 'Import',
selectFile: 'Select file',

View File

@ -99,7 +99,8 @@ export default {
removeHyperlink: '移除超链接',
removeNote: '移除备注',
removeCustomStyles: '一键去除自定义样式',
removeAllNodeCustomStyles: '一键去除所有节点自定义样式'
removeAllNodeCustomStyles: '一键去除所有节点自定义样式',
exportNodeToPng: '导出该节点为图片'
},
count: {
words: '字数',
@ -137,6 +138,9 @@ export default {
fullscreenShow: '全屏查看',
fullscreenEdit: '全屏编辑'
},
demonstrate: {
demonstrate: '进入演示模式'
},
import: {
title: '导入',
selectFile: '选取文件',

View File

@ -11,7 +11,7 @@ let langList = [
}
]
let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog']
let CourseList = new Array(27).fill(0).map((_, index) => {
let CourseList = new Array(28).fill(0).map((_, index) => {
return 'course' + (index + 1)
})
let APIList = [
@ -40,6 +40,7 @@ let APIList = [
'cooperate',
'rainbowLines',
'handDrawnLikeStyle',
'demonstrate',
'xmind',
'markdown',
'utils'

View File

@ -1,5 +1,85 @@
# Changelog
## 0.9.12
Fix:
> Fix the issue of bold title text being parsed as undefined when importing MD files;
>
> Select all, delete nodes, activate adjacent nodes, select multiple nodes, and other operations to increase the distribution of beforeynode-active events;
>
> Change the selection of multiple nodes to real-time distribution of node activation events;
>
> Fix the issue where the distance between icons and text gradually widens when both icons and line breaks are present in non rich text mode and are folded and unfolded;
>
> Fix xss vulnerability when displaying rich text content on nodes;
New:
> Support parsing of HTML formatted title text in MD files;
>
> Prohibit all contents of the canvas (except for hyperlinks and note icons of nodes) from responding to mouse events in demonstration mode;
>
> The demonstration mode supports fill in the blank mode, which means that underlined text is not displayed by default and will only be displayed sequentially by pressing the enter key;
>
> Demonstration mode supports summary content;
>
> The expandToNodeUid method of the render class supports summary nodes;
>
> The findNodeByUid (find the specified node instance on the canvas based on the uid) method of the render class supports summary nodes;
>
> Add instantiation options for adding pre - and post content to nodes, allowing custom content to be added before or after node text;
>
> Click event for adding distribution node labels;
>
> When using the moveNodeToCenter method of the render class to move a specified node to the center of the canvas, scaling is not restored by default;
Demo:
> Fix the issue of node note floating layer not being displayed in full screen view mode;
>
> Fix the issue where the note floating layer does not disappear after clicking the collapse button to collapse the node when displaying the note floating layer;
## 0.9.11
Fix
> 1.Fix the issue where clicking on the summary will trigger the data_change_detail event;
>
> 2.Fix the issue where the custom style command for removing all nodes with one click does not support summaries that are not arrays;
>
> 3.Fix the issue where summary nodes created in rich text mode are not rich text;
>
> 4.Fix the issue where the rich text plugin did not process node summaries when converting node data;
>
> 5.Fix the issue where the highlighted box of the corresponding node will be displayed incorrectly when pressing enter to end in summary node text editing;
>
> 6.Fix the issue of node width and height loss after text editing when creating multiple instances simultaneously;
>
> 7.Fix the issue where the expand/collapse button does not disappear after deleting all child nodes of the currently activated node;
>
> 8.Fix the issue where the canvas automatically moves and cannot stop when dragging nodes to the edge;
New:
> 1.Support default focus and entering editing mode when inserting summaries;
>
> 2.Support holding down the Command and Win keys to select multiple nodes;
>
> 3.Support exporting a node as an image;
>
> 4.Add demonstration plugin;
Demo:
> 1.Add the Export as Image button in the right-click menu of the node;
>
> 2.Optimize the issue of incomplete menu display when clicking the right mouse button at the window edge;
>
> 3.Add demonstration mode;
>
> 4.When copying Zhixi data, create summary data in array form;
## 0.9.10
Fix:

View File

@ -1,6 +1,58 @@
<template>
<div>
<h1>Changelog</h1>
<h2>0.9.12</h2>
<p>Fix:</p>
<blockquote>
<p>Fix the issue of bold title text being parsed as undefined when importing MD files;</p>
<p>Select all, delete nodes, activate adjacent nodes, select multiple nodes, and other operations to increase the distribution of beforeynode-active events;</p>
<p>Change the selection of multiple nodes to real-time distribution of node activation events;</p>
<p>Fix the issue where the distance between icons and text gradually widens when both icons and line breaks are present in non rich text mode and are folded and unfolded;</p>
<p>Fix xss vulnerability when displaying rich text content on nodes;</p>
</blockquote>
<p>New:</p>
<blockquote>
<p>Support parsing of HTML formatted title text in MD files;</p>
<p>Prohibit all contents of the canvas (except for hyperlinks and note icons of nodes) from responding to mouse events in demonstration mode;</p>
<p>The demonstration mode supports fill in the blank mode, which means that underlined text is not displayed by default and will only be displayed sequentially by pressing the enter key;</p>
<p>Demonstration mode supports summary content;</p>
<p>The expandToNodeUid method of the render class supports summary nodes;</p>
<p>The findNodeByUid (find the specified node instance on the canvas based on the uid) method of the render class supports summary nodes;</p>
<p>Add instantiation options for adding pre - and post content to nodes, allowing custom content to be added before or after node text;</p>
<p>Click event for adding distribution node labels;</p>
<p>When using the moveNodeToCenter method of the render class to move a specified node to the center of the canvas, scaling is not restored by default;</p>
</blockquote>
<p>Demo:</p>
<blockquote>
<p>Fix the issue of node note floating layer not being displayed in full screen view mode;</p>
<p>Fix the issue where the note floating layer does not disappear after clicking the collapse button to collapse the node when displaying the note floating layer;</p>
</blockquote>
<h2>0.9.11</h2>
<p>Fix</p>
<blockquote>
<p>1.Fix the issue where clicking on the summary will trigger the data_change_detail event;</p>
<p>2.Fix the issue where the custom style command for removing all nodes with one click does not support summaries that are not arrays;</p>
<p>3.Fix the issue where summary nodes created in rich text mode are not rich text;</p>
<p>4.Fix the issue where the rich text plugin did not process node summaries when converting node data;</p>
<p>5.Fix the issue where the highlighted box of the corresponding node will be displayed incorrectly when pressing enter to end in summary node text editing;</p>
<p>6.Fix the issue of node width and height loss after text editing when creating multiple instances simultaneously;</p>
<p>7.Fix the issue where the expand/collapse button does not disappear after deleting all child nodes of the currently activated node;</p>
<p>8.Fix the issue where the canvas automatically moves and cannot stop when dragging nodes to the edge;</p>
</blockquote>
<p>New:</p>
<blockquote>
<p>1.Support default focus and entering editing mode when inserting summaries;</p>
<p>2.Support holding down the Command and Win keys to select multiple nodes;</p>
<p>3.Support exporting a node as an image;</p>
<p>4.Add demonstration plugin;</p>
</blockquote>
<p>Demo:</p>
<blockquote>
<p>1.Add the Export as Image button in the right-click menu of the node;</p>
<p>2.Optimize the issue of incomplete menu display when clicking the right mouse button at the window edge;</p>
<p>3.Add demonstration mode;</p>
<p>4.When copying Zhixi data, create summary data in array form;</p>
</blockquote>
<h2>0.9.10</h2>
<p>Fix:</p>
<blockquote>

View File

@ -7,6 +7,18 @@ to an operation that needs to add a copy to the history stack data. The
## Methods
### pause()
> v0.9.11+
Pause collecting historical data.
### recovery()
> v0.9.11+
Restore the collection of historical data.
### add(name, fn)
Add a command.

View File

@ -6,6 +6,16 @@ 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
<code>mindMap.command</code> instance can be obtained through this.&quot;</p>
<h2>Methods</h2>
<h3>pause()</h3>
<blockquote>
<p>v0.9.11+</p>
</blockquote>
<p>Pause collecting historical data.</p>
<h3>recovery()</h3>
<blockquote>
<p>v0.9.11+</p>
</blockquote>
<p>Restore the collection of historical data.</p>
<h3>add(name, fn)</h3>
<p>Add a command.</p>
<p><code>name</code>: Command name</p>

View File

@ -24,7 +24,7 @@ const mindMap = new MindMap({
| Field Name | Type | Default Value | Description | Required |
| -------------------------------- | ------- | ---------------- | ------------------------------------------------------------ | -------- |
| el | Element | | Container element, must be a DOM element | Yes |
| el | Element | | Container element, must be a DOM elementWhen the position of container elements on the page has changed but the size has not changed, the 'getElRectInfo()' method must be called to update the relevant information inside the library; When the size also changes, the 'resize()' method must be called, otherwise it will cause some functional exceptions | Yes |
| data | Object 、null | | Mind map data, Please refer to the introduction of 【Data structure】 below. V0.9.9+supports passing empty objects or null, and the canvas will display blank space | |
| layout | String | logicalStructure | Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram)、timelinev0.5.4+, timeline、timeline2v0.5.4+, up down alternating timeline、fishbonev0.5.4+, fishbone diagram | |
| fishboneDegv0.5.4+ | Number | 45 | Set the diagonal angle of the fishbone structure diagram | |
@ -118,6 +118,10 @@ const mindMap = new MindMap({
| rainbowLinesConfigv0.9.9+ | Object | { open: false, colorsList: [] } | Rainbow line configuration requires registering the RainbowLines plugin first. Object type, Structure: { open: false【Is turn on rainbow lines】, colorsList: []【Customize the color list for rainbow lines. If not set, the default color list will be used】 } | |
| addContentToHeaderv0.9.9+ | Function、null | null | Add custom content to the header when exporting PNG, SVG, and PDF. Can pass a function that can return null to indicate no content is added, or it can return an object, For a detailed introduction, please refer to section 【How to add custom content when exporting】 below | |
| addContentToFooterv0.9.9+ | Function、null | null | The basic definition is the same as addContentToHeader, adding custom content at the end | |
| demonstrateConfigv0.9.11+ | Object、null | null | Demonstration plugin configuration. If not transmitted, the default configuration will be used. An object can be transmitted. If only a certain property is configured, only that property can be set. Other properties that have not been set will also use the default configuration. For complete configuration, please refer to the 【Demonstration Plugin Configuration】 section below | |
| resetScaleOnMoveNodeToCenterv0.9.12+ | Boolean | false | Whether to reset the scaling level to 100% when moving nodes to the canvas center, returning to the root node, and other operationsThe underlying impact is on the moveNodeToCenter method of the render class | |
| createNodePrefixContentv0.9.12+ | Function、null | null | Add additional node pre content.Pre content refers to the pre content in the area of the same line as the text, excluding the node image section.You can pass a function that takes the parameters of a node instance, returns a DOM node, or returns null | |
| createNodePostfixContentv0.9.12+ | Function、null | null | Add additional node post content.Post content refers to the post content in the area of the same line as the text, excluding the node image section.You can pass a function that takes the parameters of a node instance, returns a DOM node, or returns null | |
### Data structure
@ -142,9 +146,11 @@ The basic data structure is as follows:
hyperlinkTitle: '', // Title of hyperlink
note: '', // Content of remarks
tag: [], // Tag list
generalization: {// The summary of the node, if there is no summary, the generalization can be set to null
text: ''// Summary Text
},
generalization: [{// (Arrays are not supported in versions below 0.9.0, and only a single summary data can be set)The summary of the node, if there is no summary, the generalization can be set to null
text: '', // Summary Text
richText: false, // Is the text of the node in rich text mode
// ...The fields of other ordinary nodes are supported, But it does not support children
}],
associativeLineTargets: [''],// If there are associated lines, then it is the uid list of the target node
associativeLineText: '',// Association Line Text
// ...For other style fields, please refer to the topic
@ -214,6 +220,18 @@ new MindMap({
})
```
### Demonstration Plugin Configuration
| Field Name | Type | Default Value | Description |
| ----------- | ------ | ------------------------------------------- | ------------------------------------ |
| boxShadowColor | String | rgba(0, 0, 0, 0.8) | The color of the area around the highlighted box |
| borderRadius | String | 5px | The size of the rounded corners of the highlighted box |
| transition | String | all 0.3s ease-out | Transition properties of highlight box animation and CSS transition properties |
| zIndex | Number | 9999 | The hierarchy of highlighted box elements |
| padding | Number | 20 | The inner margin of the highlighted box |
| margin | Number | 50 | The outer margin of the highlighted box |
| openBlankModev0.9.12+ | Boolean | true | Is enable fill in the blank mode, where underlined text is not displayed by default and only displayed sequentially by pressing the enter key |
## Static methods
### defineTheme(name, config)
@ -353,6 +371,10 @@ Current Theme Configuration.
## Instance methods
### getElRectInfo()
Update the position and size information of container elements. Be sure to call this method to update information when the position of container elements on the page changes. If the size of container elements has also changed, please call the 'resize' method.
### updateData(data)
> v0.9.9+
@ -371,7 +393,7 @@ Clear `lineDraw`、`associativeLineDraw`、`nodeDraw`、`otherDraw` containers.
Destroy mind maps. It will remove registered plugins, remove listening events, and delete all nodes on the canvas.
### getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false, addContentToHeader, addContentToFooter })
### getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false, addContentToHeader, addContentToFooter, node })
> v0.3.0+
@ -385,6 +407,8 @@ Destroy mind maps. It will remove registered plugins, remove listening events, a
`addContentToFooter`v0.9.9+, Function, You can return the custom content to be added to the tail, as detailed in the configuration in 【Instantiation options】
`node`: v0.9.11+, Node instance, if passed, only export the content of that node
Get the `svg` data and return an object. The detailed structure is as follows:
```js
@ -396,6 +420,7 @@ Get the `svg` data and return an object. The detailed structure is as follows:
origHeight, // Number, canvas height
scaleX, // Number, horizontal zoom value of mind map graphics
scaleY, // Number, vertical zoom value of mind map graphics
clipData// v0.9.11+If node is passed, that is, the content of the specified node is exported, then this field will be returned, Represents the position coordinate data of the node region cropped from the complete image
}
```
@ -483,6 +508,9 @@ Listen to an event. Event list:
| node_cooperate_avatar_clickv0.9.9+ | Triggered when the mouse clicks on a person's avatar during collaborative editing | userInfo(User info)、 this(Current node instance)、 node(Avatar node)、 e(Event Object) |
| node_cooperate_avatar_mouseenterv0.9.9+ | Triggered when the mouse moves over a person's avatar during collaborative editing | userInfo(User info)、 this(Current node instance)、 node(Avatar node)、 e(Event Object) |
| node_cooperate_avatar_mouseleavev0.9.9+ | Triggered when removing personnel avatars with the mouse during collaborative editing | userInfo(User info)、 this(Current node instance)、 node(Avatar node)、 e(Event Object) |
| exit_demonstratev0.9.11+ | Triggered when exiting demonstration mode | |
| demonstrate_jumpv0.9.11+ | Trigger when switching steps in demonstration mode | currentStepIndexThe index of the steps currently played, counting from 0、stepLengthTotal number of playback steps |
| node_tag_clickv0.9.12+ | Click events on node labels | this(Current node instance)、itemContent of clicked tags |
### emit(event, ...args)
@ -574,7 +602,7 @@ redo. All commands are as follows:
| CLEAR_ACTIVE_NODE | Clear the active state of the currently active node(s), the active node will be the operation node | |
| SET_NODE_EXPAND | Set whether the node is expanded | node (the node to set), expand (boolean, whether to expand) |
| EXPAND_ALL | Expand all nodes | |
| UNEXPAND_ALL | Collapse all nodes | |
| UNEXPAND_ALL | Collapse all nodes | isSetRootNodeCenterv0.9.11+default is trueWill the root node be moved to the center after retracting all nodes |
| UNEXPAND_TO_LEVEL (v0.2.8+) | Expand to a specified level | level (the level to expand to, 1, 2, 3...) |
| SET_NODE_DATA | Update node data, that is, update the data in the data object of the node data object. Note that this command will not trigger view updates | node (the node to set), data (object, the data to update, e.g. `{expand: true}`) |
| SET_NODE_TEXT | Set node text | node (the node to set), text (the new text for the node), richTextv0.4.0+, If you want to set a rich text character, you need to set it to `true`、resetRichTextv0.6.10+Do you want to reset rich text? The default is false. If true is passed, the style of the rich text node will be reset |
@ -587,7 +615,7 @@ redo. All commands are as follows:
| INSERT_AFTER (v0.1.5+) | Move Node to After Another Node | node (node to move, (v0.7.2+supports passing node arrays to move multiple nodes simultaneously)), exist (target node) |
| INSERT_BEFORE (v0.1.5+) | Move Node to Before Another Node | node (node to move, (v0.7.2+supports passing node arrays to move multiple nodes simultaneously)), exist (target node) |
| MOVE_NODE_TO (v0.1.5+) | Move a node as a child of another node | node (the node to move, (v0.7.2+supports passing node arrays to move multiple nodes simultaneously)), toNode (the target node) |
| ADD_GENERALIZATION (v0.2.0+) | Add a node summary | data (the data for the summary, in object format, all numerical fields of the node are supported, default is `{text: 'summary'}`) |
| ADD_GENERALIZATION (v0.2.0+) | Add a node summary | data (the data for the summary, in object format, all numerical fields of the node are supported, default is `{text: 'summary'}`)、openEditv0.9.11+Default is trueWhether to enter text editing status by default |
| REMOVE_GENERALIZATION (v0.2.0+) | Remove a node summary | |
| SET_NODE_CUSTOM_POSITION (v0.2.0+) | Set a custom position for a node | node (the node to set), left (custom x coordinate, default is undefined), top (custom y coordinate, default is undefined) |
| RESET_LAYOUT (v0.2.0+) | Arrange layout with one click | |

View File

@ -32,7 +32,7 @@
<td>el</td>
<td>Element</td>
<td></td>
<td>Container element, must be a DOM element</td>
<td>Container element, must be a DOM elementWhen the position of container elements on the page has changed but the size has not changed, the 'getElRectInfo()' method must be called to update the relevant information inside the library; When the size also changes, the 'resize()' method must be called, otherwise it will cause some functional exceptions</td>
<td>Yes</td>
</tr>
<tr>
@ -686,6 +686,34 @@
<td>The basic definition is the same as addContentToHeader, adding custom content at the end</td>
<td></td>
</tr>
<tr>
<td>demonstrateConfigv0.9.11+</td>
<td>Objectnull</td>
<td>null</td>
<td>Demonstration plugin configuration. If not transmitted, the default configuration will be used. An object can be transmitted. If only a certain property is configured, only that property can be set. Other properties that have not been set will also use the default configuration. For complete configuration, please refer to the Demonstration Plugin Configuration section below</td>
<td></td>
</tr>
<tr>
<td>resetScaleOnMoveNodeToCenterv0.9.12+</td>
<td>Boolean</td>
<td>false</td>
<td>Whether to reset the scaling level to 100% when moving nodes to the canvas center, returning to the root node, and other operationsThe underlying impact is on the moveNodeToCenter method of the render class</td>
<td></td>
</tr>
<tr>
<td>createNodePrefixContentv0.9.12+</td>
<td>Functionnull</td>
<td>null</td>
<td>Add additional node pre content.Pre content refers to the pre content in the area of the same line as the text, excluding the node image section.You can pass a function that takes the parameters of a node instance, returns a DOM node, or returns null</td>
<td></td>
</tr>
<tr>
<td>createNodePostfixContentv0.9.12+</td>
<td>Functionnull</td>
<td>null</td>
<td>Add additional node post content.Post content refers to the post content in the area of the same line as the text, excluding the node image section.You can pass a function that takes the parameters of a node instance, returns a DOM node, or returns null</td>
<td></td>
</tr>
</tbody>
</table>
<h3>Data structure</h3>
@ -708,9 +736,11 @@
<span class="hljs-attr">hyperlinkTitle</span>: <span class="hljs-string">&#x27;&#x27;</span>, <span class="hljs-comment">// Title of hyperlink</span>
<span class="hljs-attr">note</span>: <span class="hljs-string">&#x27;&#x27;</span>, <span class="hljs-comment">// Content of remarks</span>
<span class="hljs-attr">tag</span>: [], <span class="hljs-comment">// Tag list</span>
<span class="hljs-attr">generalization</span>: {<span class="hljs-comment">// The summary of the node, if there is no summary, the generalization can be set to null</span>
<span class="hljs-attr">text</span>: <span class="hljs-string">&#x27;&#x27;</span><span class="hljs-comment">// Summary Text</span>
},
<span class="hljs-attr">generalization</span>: [{<span class="hljs-comment">// (Arrays are not supported in versions below 0.9.0, and only a single summary data can be set)The summary of the node, if there is no summary, the generalization can be set to null</span>
<span class="hljs-attr">text</span>: <span class="hljs-string">&#x27;&#x27;</span>, <span class="hljs-comment">// Summary Text</span>
<span class="hljs-attr">richText</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// Is the text of the node in rich text mode</span>
<span class="hljs-comment">// ...The fields of other ordinary nodes are supported, But it does not support children</span>
}],
<span class="hljs-attr">associativeLineTargets</span>: [<span class="hljs-string">&#x27;&#x27;</span>],<span class="hljs-comment">// If there are associated lines, then it is the uid list of the target node</span>
<span class="hljs-attr">associativeLineText</span>: <span class="hljs-string">&#x27;&#x27;</span>,<span class="hljs-comment">// Association Line Text</span>
<span class="hljs-comment">// ...For other style fields, please refer to the topic</span>
@ -832,6 +862,61 @@
}
})
</code></pre>
<h3>Demonstration Plugin Configuration</h3>
<table>
<thead>
<tr>
<th>Field Name</th>
<th>Type</th>
<th>Default Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>boxShadowColor</td>
<td>String</td>
<td>rgba(0, 0, 0, 0.8)</td>
<td>The color of the area around the highlighted box</td>
</tr>
<tr>
<td>borderRadius</td>
<td>String</td>
<td>5px</td>
<td>The size of the rounded corners of the highlighted box</td>
</tr>
<tr>
<td>transition</td>
<td>String</td>
<td>all 0.3s ease-out</td>
<td>Transition properties of highlight box animation and CSS transition properties</td>
</tr>
<tr>
<td>zIndex</td>
<td>Number</td>
<td>9999</td>
<td>The hierarchy of highlighted box elements</td>
</tr>
<tr>
<td>padding</td>
<td>Number</td>
<td>20</td>
<td>The inner margin of the highlighted box</td>
</tr>
<tr>
<td>margin</td>
<td>Number</td>
<td>50</td>
<td>The outer margin of the highlighted box</td>
</tr>
<tr>
<td>openBlankModev0.9.12+</td>
<td>Boolean</td>
<td>true</td>
<td>Is enable fill in the blank mode, where underlined text is not displayed by default and only displayed sequentially by pressing the enter key</td>
</tr>
</tbody>
</table>
<h2>Static methods</h2>
<h3>defineTheme(name, config)</h3>
<blockquote>
@ -928,6 +1013,8 @@ mindMap.setTheme(<span class="hljs-string">&#x27;Theme name&#x27;</span>)
<h3>themeConfig</h3>
<p>Current Theme Configuration.</p>
<h2>Instance methods</h2>
<h3>getElRectInfo()</h3>
<p>Update the position and size information of container elements. Be sure to call this method to update information when the position of container elements on the page changes. If the size of container elements has also changed, please call the 'resize' method.</p>
<h3>updateData(data)</h3>
<blockquote>
<p>v0.9.9+</p>
@ -943,7 +1030,7 @@ mindMap.setTheme(<span class="hljs-string">&#x27;Theme name&#x27;</span>)
<p>v0.6.0+</p>
</blockquote>
<p>Destroy mind maps. It will remove registered plugins, remove listening events, and delete all nodes on the canvas.</p>
<h3>getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false, addContentToHeader, addContentToFooter })</h3>
<h3>getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false, addContentToHeader, addContentToFooter, node })</h3>
<blockquote>
<p>v0.3.0+</p>
</blockquote>
@ -952,6 +1039,7 @@ mindMap.setTheme(<span class="hljs-string">&#x27;Theme name&#x27;</span>)
<p><code>ignoreWatermark</code>v0.8.0+, Do not draw watermarks. If you do not need to draw watermarks, you can pass 'true' because drawing watermarks is very slow</p>
<p><code>addContentToHeader</code>v0.9.9+, Function, You can return the custom content to be added to the header, as detailed in the configuration in Instantiation options</p>
<p><code>addContentToFooter</code>v0.9.9+, Function, You can return the custom content to be added to the tail, as detailed in the configuration in Instantiation options</p>
<p><code>node</code>: v0.9.11+, Node instance, if passed, only export the content of that node</p>
<p>Get the <code>svg</code> data and return an object. The detailed structure is as follows:</p>
<pre class="hljs"><code>{
svg, <span class="hljs-comment">// Element, the overall svg element of the mind map graphics, including: svg (canvas container), g (actual mind map group)</span>
@ -961,6 +1049,7 @@ mindMap.setTheme(<span class="hljs-string">&#x27;Theme name&#x27;</span>)
origHeight, <span class="hljs-comment">// Number, canvas height</span>
scaleX, <span class="hljs-comment">// Number, horizontal zoom value of mind map graphics</span>
scaleY, <span class="hljs-comment">// Number, vertical zoom value of mind map graphics</span>
clipData<span class="hljs-comment">// v0.9.11+If node is passed, that is, the content of the specified node is exported, then this field will be returned, Represents the position coordinate data of the node region cropped from the complete image</span>
}
</code></pre>
<h3>render(callback)</h3>
@ -1254,6 +1343,21 @@ poor performance and should be used sparingly.</p>
<td>Triggered when removing personnel avatars with the mouse during collaborative editing</td>
<td>userInfo(User info) this(Current node instance) node(Avatar node) e(Event Object)</td>
</tr>
<tr>
<td>exit_demonstratev0.9.11+</td>
<td>Triggered when exiting demonstration mode</td>
<td></td>
</tr>
<tr>
<td>demonstrate_jumpv0.9.11+</td>
<td>Trigger when switching steps in demonstration mode</td>
<td>currentStepIndexThe index of the steps currently played, counting from 0stepLengthTotal number of playback steps</td>
</tr>
<tr>
<td>node_tag_clickv0.9.12+</td>
<td>Click events on node labels</td>
<td>this(Current node instance)itemContent of clicked tags</td>
</tr>
</tbody>
</table>
<h3>emit(event, ...args)</h3>
@ -1392,7 +1496,7 @@ redo. All commands are as follows:</p>
<tr>
<td>UNEXPAND_ALL</td>
<td>Collapse all nodes</td>
<td></td>
<td>isSetRootNodeCenterv0.9.11+default is trueWill the root node be moved to the center after retracting all nodes</td>
</tr>
<tr>
<td>UNEXPAND_TO_LEVEL (v0.2.8+)</td>
@ -1457,7 +1561,7 @@ redo. All commands are as follows:</p>
<tr>
<td>ADD_GENERALIZATION (v0.2.0+)</td>
<td>Add a node summary</td>
<td>data (the data for the summary, in object format, all numerical fields of the node are supported, default is <code>{text: 'summary'}</code>)</td>
<td>data (the data for the summary, in object format, all numerical fields of the node are supported, default is <code>{text: 'summary'}</code>)openEditv0.9.11+Default is trueWhether to enter text editing status by default</td>
</tr>
<tr>
<td>REMOVE_GENERALIZATION (v0.2.0+)</td>

View File

@ -80,6 +80,14 @@ MindMap.usePlugin(Cooperate)
After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.cooperate`.
### Config
This plugin supports some configurations that can be passed in when instantiating a mind map. The supported configurations are as follows:
`beforeCooperateUpdate`
Please refer to the 【Instantiation options】 section in the 【Constructor】 section for details.
## Methods
### getDoc()

View File

@ -60,6 +60,10 @@ npm run wsServe
MindMap.usePlugin(Cooperate)
</code></pre>
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.cooperate</code>.</p>
<h3>Config</h3>
<p>This plugin supports some configurations that can be passed in when instantiating a mind map. The supported configurations are as follows:</p>
<p><code>beforeCooperateUpdate</code></p>
<p>Please refer to the Instantiation options section in the Constructor section for details.</p>
<h2>Methods</h2>
<h3>getDoc()</h3>
<p>Obtain Yjs doc instance.</p>

View File

@ -0,0 +1,71 @@
# Demonstrate plugin
> v0.9.11+
The `Demonstrate` plugin provides demonstration functionality.
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.
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.
## Register
```js
import MindMap from 'simple-mind-map'
import Demonstrate from 'simple-mind-map/src/plugins/Demonstrate.js'
MindMap.usePlugin(Demonstrate)
```
After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.demonstrate`.
### Config
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.
### Event
The plugin will dispatch the following events:
`exit_demonstrate`Triggered when exiting the demonstration.
`demonstrate_jump`Triggered when jumping.
Please refer to the 'on' function in the 【Instance methods】 section of the 【Constructor】 chapter for details.
## Props
### stepList
List of all steps demonstrated. Available when the 'enter' method is called.
### currentStepIndex
The index of the steps currently played, counting from 0.
### config
The current configuration of the plugin.
## Methods
### enter()
Entering demonstration mode will automatically display the container elements in full screen.
### exit()
Exit demonstration mode, which can also be exited by pressing the 'Esc' key.
### prev()
Previous step.
### next()
Next step.
### jump(index)
- `index`NumberTo jump to a certain step, count from 0.
Jump to a certain step.

View File

@ -0,0 +1,57 @@
<template>
<div>
<h1>Demonstrate plugin</h1>
<blockquote>
<p>v0.9.11+</p>
</blockquote>
<p>The <code>Demonstrate</code> plugin provides demonstration functionality.</p>
<p>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.</p>
<p>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.</p>
<h2>Register</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> Demonstrate <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/plugins/Demonstrate.js&#x27;</span>
MindMap.usePlugin(Demonstrate)
</code></pre>
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.demonstrate</code>.</p>
<h3>Config</h3>
<p>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.</p>
<h3>Event</h3>
<p>The plugin will dispatch the following events:</p>
<p><code>exit_demonstrate</code>Triggered when exiting the demonstration.</p>
<p><code>demonstrate_jump</code>Triggered when jumping.</p>
<p>Please refer to the 'on' function in the Instance methods section of the Constructor chapter for details.</p>
<h2>Props</h2>
<h3>stepList</h3>
<p>List of all steps demonstrated. Available when the 'enter' method is called.</p>
<h3>currentStepIndex</h3>
<p>The index of the steps currently played, counting from 0.</p>
<h3>config</h3>
<p>The current configuration of the plugin.</p>
<h2>Methods</h2>
<h3>enter()</h3>
<p>Entering demonstration mode will automatically display the container elements in full screen.</p>
<h3>exit()</h3>
<p>Exit demonstration mode, which can also be exited by pressing the 'Esc' key.</p>
<h3>prev()</h3>
<p>Previous step.</p>
<h3>next()</h3>
<p>Next step.</p>
<h3>jump(index)</h3>
<ul>
<li><code>index</code>NumberTo jump to a certain step, count from 0.</li>
</ul>
<p>Jump to a certain step.</p>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@ -63,7 +63,7 @@ However, this requires backend support, as our application is a single page clie
## Docker
> Thank you very much [水车](https://github.com/shuiche-it), This section is written by him, and the corresponding Docker package is also maintained by him.
> Thank you very much [水车](https://github.com/shuiche-it), the corresponding Docker package is maintained by him.
Install directly from Docker Hub:

View File

@ -40,7 +40,7 @@ npm link simple-mind-map
<p>However, this requires backend support, as our application is a single page client application. If the backend is not properly configured, users will return 404 when accessing sub routes directly in the browser. Therefore, you need to add a candidate resource on the server that covers all situations: if the 'URL' cannot match any static resources, the same 'index. html' page should be returned.</p>
<h2>Docker</h2>
<blockquote>
<p>Thank you very much <a href="https://github.com/shuiche-it">水车</a>, This section is written by him, and the corresponding Docker package is also maintained by him.</p>
<p>Thank you very much <a href="https://github.com/shuiche-it">水车</a>, the corresponding Docker package is maintained by him.</p>
</blockquote>
<p>Install directly from Docker Hub:</p>
<pre class="hljs"><code>docker run -d -p 8081:8080 shuiche/mind-map:latest

View File

@ -38,7 +38,9 @@ a.download = 'xxx'
a.click()
```
### png(name, transparent = false, checkRotate, compress)
### png(name, transparent = false, node = null)
> Versions below v0.9.2 arepng(name, transparent = false, checkRotate, compress)
> Versions below v0.7.0 are: png(name, transparent = false, rotateWhenWidthLongerThenHeight)
@ -52,6 +54,8 @@ a.click()
- `compress`v0.8.1+, (v0.9.2+obsolete)null | { width, height }, The parameter for compressing images. In some cases, the length and width of the exported image may be very large. If you want to reduce it, you can use this parameter to control it. Only one width or height can be provided, and it will be scaled proportionally
- `node`v0.9.11+Node instances, if passed, will only export the content of that node;
Exports as `png`.
### svg(name, plusCssText)
@ -115,14 +119,17 @@ Return `json` data.
Export as `markdown` file.
### getSvgData()
### getSvgData(node)
`node`: v0.9.11+, Node instance, if passed, will return a 'clipData' object, representing the position coordinate data of the node region cropped from the complete image;
Gets `svg` data, an async method that returns an object:
```js
{
node // svg node
str // svg string
node, // svg node
str, // svg string
clipData
}
```

View File

@ -27,7 +27,10 @@ a.href = <span class="hljs-string">&#x27;xxx.png&#x27;</span><span class="hljs-c
a.download = <span class="hljs-string">&#x27;xxx&#x27;</span>
a.click()
</code></pre>
<h3>png(name, transparent = false, checkRotate, compress)</h3>
<h3>png(name, transparent = false, node = null)</h3>
<blockquote>
<p>Versions below v0.9.2 arepng(name, transparent = false, checkRotate, compress)</p>
</blockquote>
<blockquote>
<p>Versions below v0.7.0 are: png(name, transparent = false, rotateWhenWidthLongerThenHeight)</p>
</blockquote>
@ -47,6 +50,9 @@ a.click()
<li>
<p><code>compress</code>v0.8.1+, (v0.9.2+obsolete)null | { width, height }, The parameter for compressing images. In some cases, the length and width of the exported image may be very large. If you want to reduce it, you can use this parameter to control it. Only one width or height can be provided, and it will be scaled proportionally</p>
</li>
<li>
<p><code>node</code>v0.9.11+Node instances, if passed, will only export the content of that node;</p>
</li>
</ul>
<p>Exports as <code>png</code>.</p>
<h3>svg(name, plusCssText)</h3>
@ -112,11 +118,13 @@ MindMap.usePlugin(ExportPDF)
<p>v0.4.7+</p>
</blockquote>
<p>Export as <code>markdown</code> file.</p>
<h3>getSvgData()</h3>
<h3>getSvgData(node)</h3>
<p><code>node</code>: v0.9.11+, Node instance, if passed, will return a 'clipData' object, representing the position coordinate data of the node region cropped from the complete image;</p>
<p>Gets <code>svg</code> data, an async method that returns an object:</p>
<pre class="hljs"><code>{
node <span class="hljs-comment">// svg node</span>
str <span class="hljs-comment">// svg string</span>
node, <span class="hljs-comment">// svg node</span>
str, <span class="hljs-comment">// svg string</span>
clipData
}
</code></pre>
<h3>xmind(name)</h3>

View File

@ -12,16 +12,21 @@
- [x] Support logical structure chart, mind map, Organizational chart, directory organization chart, timeline (horizontal and vertical), fishbone chart and other structures
- [x] Built-in multiple themes, allowing for highly customizable styles, and supporting registration of new themes
- [x] Node content supports text (regular text, rich text), images, icons, hyperlinks, notes, labels, summaries, and math formulas
- [x] Nodes support drag and drop (drag and move, freely adjust), multiple node shapes, and fully customize node content using DDM
- [x] Nodes support drag and drop (drag and move, freely adjust), multiple node shapes, Support for expanding node content, and fully customize node content using DDM
- [x] Support canvas dragging and scaling
- [x] Supports two multi node selection methods: mouse button drag selection and Ctrl+left button selection
- [x] Supoorts to export as `json`、`png`、`svg`、`pdf`、`markdown`、`xmind`, support import from `json`、`xmind`、`markdown`
- [x] Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, watermarks, and scrollbar
- [x] Supoorts to export as `json`、`png`、`svg`、`pdf`、`markdown`、`xmind`、`txt`, support import from `json`、`xmind`、`markdown`
- [x] Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, watermarks, scrollbar, Hand drawn style, and rainbow lines
- [x] Provide rich configurations to meet various scenarios and usage habits
- [x] Support collaborative editing
- [x] Support demonstration mode
The official provides the following plugins, which can be introduced as needed (a certain function may not be effective because you did not introduce the corresponding plugin). Please refer to the documentation for specific usage methods:
> RichTextNode Rich Text Plugin、SelectMouse Multiple Selection Node Plugin、DragNode drag plugin、AssociativeLineAssociate Line Plugin、ExportExport plugin、KeyboardNavigationKeyboard navigation plugin、MiniMapMini Map Plugin、WatermarkWatermark plugin、TouchEventMobile touch event support plugin、NodeImgAdjustDrag and adjust node image size plugin、SearchSearch plugin、PainterNode Format Brush Plugin、ScrollbarScroll bar plugin、FormulaMathematical Formula Plugin、CooperateCollaborative editing plugin、RainbowLinesRainbow Line Plugin、DemonstrateDemonstration mode plugin、HandDrawnLikeStyleHand drawn style plugin[收费]
Features that will not be implemented in this project:
> Features that will not be implemented:
>
> 1.Free nodes, i.e. multiple root nodes;
>
> 2.Continue adding nodes after the summary node;
@ -123,7 +128,7 @@ Unsupported: `IE` browser.
## License
[MIT](https://opensource.org/licenses/MIT)
[MIT](https://github.com/wanglin2/mind-map/blob/main/LICENSE). You can use it for commercial purposes without retaining the copyright statement of 'mind-map'. If you don't want to keep it, you can contact the author.
## Invite the author to a cup of coffee
@ -357,4 +362,36 @@ Open source is not easy. If this project is helpful to you, you can invite the a
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>sunniberg</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/在下青铜五.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>在下青铜五</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/木星二号.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>木星二号</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/阿晨.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>阿晨</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p></p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/庆国.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>庆国</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/孟照星.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>孟照星</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/子豪.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>子豪</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/宏涛.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>宏涛</p>
</div>
</div>

View File

@ -8,20 +8,25 @@
</blockquote>
<h2>Features</h2>
<ul>
<li><input type="checkbox" id="checkbox32" checked="true" /><label for="checkbox32">Pluggable architecture, in addition to core functions, other functions are provided as plugins, which can be used as needed to reduce packaging volume</label></li>
<li><input type="checkbox" id="checkbox33" checked="true" /><label for="checkbox33">Support logical structure chart, mind map, Organizational chart, directory organization chart, timeline (horizontal and vertical), fishbone chart and other structures</label></li>
<li><input type="checkbox" id="checkbox34" checked="true" /><label for="checkbox34">Built-in multiple themes, allowing for highly customizable styles, and supporting registration of new themes</label></li>
<li><input type="checkbox" id="checkbox35" checked="true" /><label for="checkbox35">Node content supports text (regular text, rich text), images, icons, hyperlinks, notes, labels, summaries, and math formulas</label></li>
<li><input type="checkbox" id="checkbox36" checked="true" /><label for="checkbox36">Nodes support drag and drop (drag and move, freely adjust), multiple node shapes, and fully customize node content using DDM</label></li>
<li><input type="checkbox" id="checkbox37" checked="true" /><label for="checkbox37">Support canvas dragging and scaling</label></li>
<li><input type="checkbox" id="checkbox38" checked="true" /><label for="checkbox38">Supports two multi node selection methods: mouse button drag selection and Ctrl+left button selection</label></li>
<li><input type="checkbox" id="checkbox39" checked="true" /><label for="checkbox39">Supoorts to export as </label><code>json</code><code>png</code><code>svg</code><code>pdf</code><code>markdown</code><code>xmind</code>, support import from <code>json</code><code>xmind</code><code>markdown</code></li>
<li><input type="checkbox" id="checkbox40" checked="true" /><label for="checkbox40">Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, watermarks, and scrollbar</label></li>
<li><input type="checkbox" id="checkbox41" checked="true" /><label for="checkbox41">Provide rich configurations to meet various scenarios and usage habits</label></li>
<li><input type="checkbox" id="checkbox42" checked="true" /><label for="checkbox42">Support collaborative editing</label></li>
<li><input type="checkbox" id="checkbox252" checked="true" /><label for="checkbox252">Pluggable architecture, in addition to core functions, other functions are provided as plugins, which can be used as needed to reduce packaging volume</label></li>
<li><input type="checkbox" id="checkbox253" checked="true" /><label for="checkbox253">Support logical structure chart, mind map, Organizational chart, directory organization chart, timeline (horizontal and vertical), fishbone chart and other structures</label></li>
<li><input type="checkbox" id="checkbox254" checked="true" /><label for="checkbox254">Built-in multiple themes, allowing for highly customizable styles, and supporting registration of new themes</label></li>
<li><input type="checkbox" id="checkbox255" checked="true" /><label for="checkbox255">Node content supports text (regular text, rich text), images, icons, hyperlinks, notes, labels, summaries, and math formulas</label></li>
<li><input type="checkbox" id="checkbox256" checked="true" /><label for="checkbox256">Nodes support drag and drop (drag and move, freely adjust), multiple node shapes, Support for expanding node content, and fully customize node content using DDM</label></li>
<li><input type="checkbox" id="checkbox257" checked="true" /><label for="checkbox257">Support canvas dragging and scaling</label></li>
<li><input type="checkbox" id="checkbox258" checked="true" /><label for="checkbox258">Supports two multi node selection methods: mouse button drag selection and Ctrl+left button selection</label></li>
<li><input type="checkbox" id="checkbox259" checked="true" /><label for="checkbox259">Supoorts to export as </label><code>json</code><code>png</code><code>svg</code><code>pdf</code><code>markdown</code><code>xmind</code><code>txt</code>, support import from <code>json</code><code>xmind</code><code>markdown</code></li>
<li><input type="checkbox" id="checkbox260" checked="true" /><label for="checkbox260">Support shortcut keys, forward and backward, correlation lines, search and replacement, small maps, watermarks, scrollbar, Hand drawn style, and rainbow lines</label></li>
<li><input type="checkbox" id="checkbox261" checked="true" /><label for="checkbox261">Provide rich configurations to meet various scenarios and usage habits</label></li>
<li><input type="checkbox" id="checkbox262" checked="true" /><label for="checkbox262">Support collaborative editing</label></li>
<li><input type="checkbox" id="checkbox263" checked="true" /><label for="checkbox263">Support demonstration mode</label></li>
</ul>
<p>The official provides the following plugins, which can be introduced as needed (a certain function may not be effective because you did not introduce the corresponding plugin). Please refer to the documentation for specific usage methods:</p>
<blockquote>
<p>RichTextNode Rich Text PluginSelectMouse Multiple Selection Node PluginDragNode drag pluginAssociativeLineAssociate Line PluginExportExport pluginKeyboardNavigationKeyboard navigation pluginMiniMapMini Map PluginWatermarkWatermark pluginTouchEventMobile touch event support pluginNodeImgAdjustDrag and adjust node image size pluginSearchSearch pluginPainterNode Format Brush PluginScrollbarScroll bar pluginFormulaMathematical Formula PluginCooperateCollaborative editing pluginRainbowLinesRainbow Line PluginDemonstrateDemonstration mode pluginHandDrawnLikeStyleHand drawn style plugin[收费]</p>
</blockquote>
<p>Features that will not be implemented in this project:</p>
<blockquote>
<p>Features that will not be implemented:</p>
<p>1.Free nodes, i.e. multiple root nodes;</p>
<p>2.Continue adding nodes after the summary node;</p>
<p>If you need the above features, this library may not meet your needs.</p>
@ -34,16 +39,16 @@ frameworks such as Vue and React, or without a framework.</p>
<p>This is an online mind map built using the <code>simple-mind-map</code> library and based
on <code>Vue2.x</code> and <code>ElementUI</code>. Features include:</p>
<ul>
<li><input type="checkbox" id="checkbox43" checked="true" /><label for="checkbox43">Toolbar, which supports inserting and deleting nodes, and editing node</label>
<li><input type="checkbox" id="checkbox264" checked="true" /><label for="checkbox264">Toolbar, which supports inserting and deleting nodes, and editing node</label>
images, icons, hyperlinks, notes, tags, and summaries</li>
<li><input type="checkbox" id="checkbox44" checked="true" /><label for="checkbox44">Sidebar, with panels for basic style settings, node style settings,</label>
<li><input type="checkbox" id="checkbox265" checked="true" /><label for="checkbox265">Sidebar, with panels for basic style settings, node style settings,</label>
outline, theme selection, and structure selection</li>
<li><input type="checkbox" id="checkbox45" checked="true" /><label for="checkbox45">Import and export functionality; data is saved in the browser's local</label>
<li><input type="checkbox" id="checkbox266" checked="true" /><label for="checkbox266">Import and export functionality; data is saved in the browser's local</label>
storage by default, but it also supports creating, opening, and editing
local files on the computer directly</li>
<li><input type="checkbox" id="checkbox46" checked="true" /><label for="checkbox46">Right-click menu, which supports operations such as expanding, collapsing,</label>
<li><input type="checkbox" id="checkbox267" checked="true" /><label for="checkbox267">Right-click menu, which supports operations such as expanding, collapsing,</label>
and organizing layout</li>
<li><input type="checkbox" id="checkbox47" checked="true" /><label for="checkbox47">Bottom bar, which supports node and word count statistics, switching</label>
<li><input type="checkbox" id="checkbox268" checked="true" /><label for="checkbox268">Bottom bar, which supports node and word count statistics, switching</label>
between edit and read-only modes, zooming in and out, and switching to
full screen, support mini map</li>
</ul>
@ -82,7 +87,7 @@ full screen, support mini map</li>
<p>Normal operation: <code>360</code> extreme speed browserv13.5.2036.0<code>opera</code> browserv71.0.3770.284<code>Firefox</code>v98.0.2.</p>
<p>Unsupported: <code>IE</code> browser.</p>
<h2>License</h2>
<p><a href="https://opensource.org/licenses/MIT">MIT</a></p>
<p><a href="https://github.com/wanglin2/mind-map/blob/main/LICENSE">MIT</a>. You can use it for commercial purposes without retaining the copyright statement of 'mind-map'. If you don't want to keep it, you can contact the author.</p>
<h2>Invite the author to a cup of coffee</h2>
<p>Open source is not easy. If this project is helpful to you, you can invite the author to have a cup of coffee~</p>
<blockquote>
@ -311,6 +316,38 @@ full screen, support mini map</li>
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>sunniberg</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/在下青铜五.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>在下青铜五</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/木星二号.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>木星二号</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/阿晨.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>阿晨</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/default.png" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p></p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/庆国.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>庆国</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/孟照星.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>孟照星</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/子豪.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>子豪</p>
</div>
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
<img src="../../../../assets/avatar/宏涛.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
<p>宏涛</p>
</div>
</div>
</div>
</template>

View File

@ -56,6 +56,12 @@ Whether the node is currently being dragged
## Methods
### deactivate()
> 0.9.11+
Deactivate the node.
### getAncestorNodes()
> v0.9.9+

View File

@ -31,6 +31,11 @@
</blockquote>
<p>Whether the node is currently being dragged</p>
<h2>Methods</h2>
<h3>deactivate()</h3>
<blockquote>
<p>0.9.11+</p>
</blockquote>
<p>Deactivate the node.</p>
<h3>getAncestorNodes()</h3>
<blockquote>
<p>v0.9.9+</p>

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