Compare commits

..

77 Commits

Author SHA1 Message Date
街角小林
0041be9892 打包0.12.1 2024-10-25 10:02:17 +08:00
街角小林
7ebab0298b 打包Demo 2024-10-24 18:27:39 +08:00
街角小林
079b963ae3 Fix:修复拖拽调整节点宽度后回退操作时节点宽度没有回退的问题 2024-10-24 18:25:31 +08:00
街角小林
fb6fcd6bd3 Fix:修复当存在滚动条插件,且思维导图被限制在画布内时,演示模式中边缘节点无法正常显示的问题 2024-10-24 09:49:07 +08:00
街角小林
59950b2ba0 Demo:增加是否自动进入文本编辑的配置 2024-10-24 09:35:52 +08:00
街角小林
1f23257917 Demo:增加是否一直显示展开收起按钮的配置 2024-10-24 09:26:25 +08:00
街角小林
4e1db01f44 Demo:增加节点标签显示位置的配置 2024-10-24 09:20:34 +08:00
街角小林
38769c3b55 打包demo 2024-10-23 18:14:06 +08:00
街角小林
a4ef09779d Demo:将设置类的配置从基础样式移到单独的设置栏中 2024-10-23 18:11:48 +08:00
街角小林
4821dd6052 打包Demo 2024-10-23 09:30:19 +08:00
街角小林
f33c886d6a Feat:修复处于文本编辑中切换为只读模式时文本未保存的问题 2024-10-23 09:26:25 +08:00
街角小林
b5b8c2be60 Merge pull request #951 from ZhangMingZhao1/fix-exit-edit-pr
fix: 修复设置readonly如果处于编辑态还能编辑的问题
2024-10-23 09:18:18 +08:00
街角小林
4a7485c58e Demo:节点右键菜单新增添加待办按钮 2024-10-22 17:32:46 +08:00
街角小林
c097d20748 Feat:去除代码中对编号插件的硬编码,新增节点库前置内容的创建逻辑 2024-10-22 17:22:54 +08:00
ZhangMingZhao1
eeeae7d0e2 fix: 修复设置readonly如果处于编辑态还能编辑的问题 2024-10-22 10:49:29 +08:00
街角小林
dd3e169946 Fix:修复在节点文本编辑中调用destroy方法时setBackgroundStyle方法会报错的问题 2024-10-21 09:37:13 +08:00
街角小林
b895a58194 打包Demo 2024-10-17 18:02:25 +08:00
街角小林
2b42b9fafa Demo:支持设置节点文本编辑是否实时更新节点大小,默认开启 2024-10-17 17:56:03 +08:00
街角小林
c2125b07ca Feat:非富文本模式文本编辑框的样式同步节点样式 2024-10-17 17:54:41 +08:00
街角小林
eb342bf69b Feat:当开启openRealtimeRenderOnNodeTextEdit选项后,会去除文本编辑框的背景和阴影,达到类似原地编辑的效果 2024-10-17 13:51:17 +08:00
街角小林
a7eb66a6c9 Feat:after_update_config事件新增上一次配置的返回参数 2024-10-17 11:32:35 +08:00
街角小林
e24fd9bdbb Fix:修复开启maxImgResizeWidthInheritTheme选项后第一次上传图片拖动图片到最大值会导致后续调整按钮无法显示的问题 2024-10-17 09:34:17 +08:00
街角小林
34d7c6fed2 Fix:修复开启openRealtimeRenderOnNodeTextEdit选项后非富文本模式编辑文本时输入框会左右抖动的问题 2024-10-17 09:13:55 +08:00
街角小林
a0f88031c1 Demo:去除节点样式的行高配置 2024-10-16 18:42:38 +08:00
街角小林
889ec13dbf Feat:1.去除主题的行高配置;2.优化非富文本模式下的文本编辑效果 2024-10-16 18:40:47 +08:00
街角小林
4aa5a8c48b Fix:修复给Text文本设置的字号没有加单位导致在一些浏览器上不生效的问题 2024-10-16 17:39:25 +08:00
街角小林
0ec20b8fa0 update 2024-10-16 10:09:46 +08:00
街角小林
f3285cf4e6 Doc update 2024-10-15 17:08:29 +08:00
街角小林
d7786cd449 update 2024-10-14 17:48:06 +08:00
街角小林
777a9d9047 打包0.12.0 2024-10-14 17:44:48 +08:00
街角小林
0dd7b0ed03 Feat:新增拦截关联线创建的实例化选项 2024-10-14 17:41:35 +08:00
街角小林
b661e8cc92 update 2024-10-14 17:30:01 +08:00
街角小林
1412fb5d09 打包0.12.0 2024-10-14 09:46:53 +08:00
街角小林
ec6a40e381 Feat:同时激活多个节点时编辑某个节点的文本只对该节点生效 2024-10-14 09:33:20 +08:00
街角小林
ca5075d50c 打包0.12.0 2024-10-12 17:35:44 +08:00
街角小林
fa1bf89e70 Feat:非富文本模式下节点文本编辑框增加类名,方便覆盖样式 2024-10-12 17:20:54 +08:00
街角小林
729533b3c1 Feat:mousedownEventPreventDefault选项支持控制节点的mousedown事件是否阻止默认事件 2024-10-12 11:12:03 +08:00
街角小林
6ffd26fd7f Feat:新增拖拽调整图片大小的最小值实例化选项 2024-10-12 10:09:59 +08:00
街角小林
48da6cb642 Feat:支持设置拖拽调整图片大小的最大值 2024-10-11 17:46:47 +08:00
街角小林
0055bbb39d Fix:修复缩放图片工具方法有误的问题;Feat:默认主题的最大图片宽度改为200;Feat:缩放节点图片按钮大小支持配置 2024-10-11 09:16:50 +08:00
街角小林
3349df2183 Fix:修复开启性能模式时拖动子节点在画布外的节点时报错的问题 2024-10-10 17:30:27 +08:00
街角小林
6c4800a6f0 update 2024-10-10 17:15:18 +08:00
街角小林
ca40204d43 Fix:修复某些场景(搜索全部替换等)下节点状态未更新的问题,节点实例新增了数据快照的属性 2024-10-10 11:16:24 +08:00
街角小林
cd28be4b01 Fix:修复非富文本模式下新建节点操作撤回也需要两次的问题 2024-10-10 10:27:40 +08:00
街角小林
af752ea761 Feat:新增添加和删除必要的css样式的方法;Fix:修复富文本模式下节点文本存在连续的数字或字母时导出图片换行失效的问题 2024-10-10 09:28:28 +08:00
街角小林
f2b72830b4 update 2024-10-09 19:14:53 +08:00
街角小林
f5cf7abd4f Feat:抽离库和demo中的主题文件为单独的包 2024-10-09 09:27:51 +08:00
街角小林
29695d1d7e Fix:修改派发节点激活事件的逻辑,去除不必要的判断,避免激活节点没有改变的情况下也会触发事件 2024-09-30 17:35:26 +08:00
街角小林
1510f7a135 打包Demo 2024-09-30 16:56:53 +08:00
街角小林
4b5a691713 Feat:支持拖拽调整节点宽度 2024-09-30 16:43:47 +08:00
街角小林
e9058ed67e Doc: 添加注释 2024-09-29 10:03:31 +08:00
街角小林
fc728ec018 Feat:剪贴板中同时存在文本和图片数据,默认只粘贴文本,可通过实例化选项修改 2024-09-27 17:34:13 +08:00
街角小林
7361df8697 Fix:修复搜索替换时搜索文本是替换文本的子串时搜索结果不正确的问题 2024-09-27 17:20:01 +08:00
街角小林
14e36aa699 Doc: update 2024-09-27 16:49:04 +08:00
街角小林
eff4cd0e77 打包0.11.2 2024-09-25 10:52:21 +08:00
街角小林
82c2d848a9 Feat:增加是否阻止mousedown事件默认事件的实例化选项 2024-09-25 10:31:39 +08:00
街角小林
0344599411 Fix:优化公式插件,适配创建多个实例的情况 2024-09-24 18:21:48 +08:00
街角小林
19fa0af6c0 Fix:修复创建多个思维导图实例时调用addPlugin添加同一个插件只有第一个实例会生效的问题 2024-09-24 17:55:52 +08:00
街角小林
bca1a073f7 Fix:修复创建多个思维导图实例时公式插件会多次扩展Quill的问题 2024-09-24 17:25:06 +08:00
街角小林
98fb23bf7c Feat:主题支持配置各个层级节点的内边距 2024-09-23 17:48:11 +08:00
街角小林
1c9c399b76 Feat:主题新增节点高亮框的圆角配置 2024-09-23 17:19:47 +08:00
wanglin2
bc43fedd87 Feat:新增自定义判断wheel事件是否来自触控板的实例化选项;优化代码 2024-09-21 19:54:43 +08:00
街角小林
156054ed93 Merge pull request #886 from Tarrency/feature-to-the-feature
feat: 配置平移步长和扩缩最值,解决触控板灵敏度问题
2024-09-21 15:24:58 +08:00
wangqi01
937f7d2969 fix: 限值平移步长比例生效只在鼠标/触控板滚动行为内 2024-09-20 18:03:18 +08:00
街角小林
e56a6d36cb Demo:优化节点图片添加了无法访问的图片的展示样式 2024-09-20 17:33:16 +08:00
街角小林
9f19061010 Fix:修复节点富文本编辑能粘贴图片的问题 2024-09-20 16:58:06 +08:00
街角小林
0d465f28f3 Feat:注释掉非https情况下的粘贴逻辑 2024-09-20 16:37:07 +08:00
街角小林
b4fdcd81b0 Fix:修复自定义主题节点渐变色方向无效的问题 2024-09-20 09:32:12 +08:00
Tarrency
38c0fe2e39 feat: 配置平移步长和扩缩最值,解决触控板灵敏度问题 2024-09-19 20:35:46 +08:00
街角小林
29ddbba9b9 Demo:新增一键展开某个节点所有下级节点的右键菜单 2024-09-19 19:52:34 +08:00
街角小林
9f9ed1e84f Fix:修复思维导图非常大的情况下导出图片失败的问题 2024-09-19 19:36:10 +08:00
街角小林
9ebc416167 Demo:修复右键菜单中的二级菜单会超出边界的问题 2024-09-19 18:19:57 +08:00
街角小林
5d49d985c0 优化代码 2024-09-19 18:14:52 +08:00
街角小林
c36338a794 Merge pull request #876 from BlackEyeBear/main
处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本
2024-09-19 18:05:36 +08:00
wanglin2
c21ee4960e Fix:修复存在概要时切换主题会报错的问题 2024-09-18 21:52:11 +08:00
BlackEyeBear
5090f21b0e 处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本 2024-09-16 22:08:55 +08:00
panda
3d9d172fa0 '处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本' 2024-09-16 21:32:52 +08:00
163 changed files with 2062 additions and 3401 deletions

View File

@@ -44,7 +44,7 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。百度云
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
> RichText节点富文本插件、Select鼠标多选节点插件、Drag节点拖拽插件、AssociativeLine关联线插件、Export导出插件、KeyboardNavigation键盘导航插件、MiniMap小地图插件、Watermark水印插件、TouchEvent移动端触摸事件支持插件、NodeImgAdjust拖拽调整节点图片大小插件、Search搜索插件、Painter节点格式刷插件、Scrollbar滚动条插件、Formula数学公式插件、Cooperate协同编辑插件、RainbowLines彩虹线条插件、Demonstrate演示模式插件、OuterFrame外框插件、HandDrawnLikeStyle手绘风格插件[收费]、Notation节点标记插件[收费]、Numbers节点编号插件[收费]、FreemindFreemind格式导入导出插件[收费]、ExcelExcel格式导入导出插件[收费]
> RichText节点富文本插件、Select鼠标多选节点插件、Drag节点拖拽插件、AssociativeLine关联线插件、Export导出插件、KeyboardNavigation键盘导航插件、MiniMap小地图插件、Watermark水印插件、TouchEvent移动端触摸事件支持插件、NodeImgAdjust拖拽调整节点图片大小插件、Search搜索插件、Painter节点格式刷插件、Scrollbar滚动条插件、Formula数学公式插件、Cooperate协同编辑插件、RainbowLines彩虹线条插件、Demonstrate演示模式插件、OuterFrame外框插件、HandDrawnLikeStyle手绘风格插件[收费]、Notation节点标记插件[收费]、Numbers节点编号插件[收费]、FreemindFreemind格式导入导出插件[收费]、ExcelExcel格式导入导出插件[收费]、Checkbox待办插件[收费]
本项目不会实现的特性:
@@ -103,9 +103,7 @@ const mindMap = new MindMap({
# 微信交流群
一群已满,可以扫描如下二维码进入二群,如已过期,可以微信添加`wanglinguanfang`拉你入群。思维导图相关问题皆可在群里提问,不必私聊作者。
<img src="./qrcode.jpg" style="width: 300px" />
微信添加`wanglinguanfang`拉你入群。根据过往的经验大部分问题都可以通过查看issue列表或文档解决所以提问前请确保你已经阅读完了所有文档文档里没有的可在群里提问,不必私聊作者。
# star
@@ -119,9 +117,13 @@ const mindMap = new MindMap({
# 请作者喝杯咖啡
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡,你的支持是开发者持续维护的最大动力~
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
>
> 也可以通过购买付费插件来支持我们:[付费插件](https://wanglin2.github.io/mind-map-docs/plugins/about.html)。
>
> 为什么需要你的赞助simple-mind-map 的目标是成为开源中最好的思维导图为开发者提供一个快速实现思维导图产品的js库为用户提供一个免费好用的思维导图软件为了这个目标作者已经持续开发维护了3年多耗费了非常多的精力随着时间的推移simple-mind-map 已经取得了一定的成绩,相比最初,无论是功能,还是体验都已经有了翻天覆地的改变,但是收益方面却可以忽略不计,因为 simple-mind-map 是采用 MIT 许可的开源项目,永久免费,保留版权下可随意商用,这也意味着很难直接通过项目获取收益,为爱发电的激情总会慢慢消退,所以你的赞助对项目的可持续发展非常重要,是作者持续维护的最大动力。
<p>
<img src="./web/src/assets/img/alipay.jpg" style="width: 300px" />
@@ -469,4 +471,32 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>晏江</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>Eric</span>
</span>
<span>
<img src="./web/src/assets/avatar/Joe.jpg" style="width: 50px;height: 50px;" />
<span>Joe</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>中文网字计划-江夏尧</span>
</span>
<span>
<img src="./web/src/assets/avatar/梁辉.jpg" style="width: 50px;height: 50px;" />
<span>梁辉</span>
</span>
<span>
<img src="./web/src/assets/avatar/海云.jpg" style="width: 50px;height: 50px;" />
<span>海云</span>
</span>
<span>
<img src="./web/src/assets/avatar/皮老板.jpg" style="width: 50px;height: 50px;" />
<span>皮老板</span>
</span>
<span>
<img src="./web/src/assets/avatar/h.r.w.jpg" style="width: 50px;height: 50px;" />
<span>h.r.w</span>
</span>
</p>

File diff suppressed because one or more lines are too long

BIN
dist/img/classic10.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/classic11.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/classic12.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/classic13.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
dist/img/classic14.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
dist/img/classic15.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/classic8.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
dist/img/classic9.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/dark5.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
dist/img/dark6.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
dist/img/dark7.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

2
dist/js/app.js vendored

File diff suppressed because one or more lines are too long

69
dist/js/chunk-41357460.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@
})
} catch (error) {
console.log(error)
}</script><link href="dist/css/chunk-vendors.css?b396be756fa41f2b3cf9" rel="stylesheet"><link href="dist/css/app.css?b396be756fa41f2b3cf9" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
}</script><link href="dist/css/chunk-vendors.css?fc8e52cca177f49cac0d" rel="stylesheet"><link href="dist/css/app.css?fc8e52cca177f49cac0d" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
@@ -74,4 +74,4 @@
// 可以通过window.$bus.$on()来监听应用的一些事件
// 实例化页面
window.initApp()
}</script><script src="dist/js/chunk-vendors.js?b396be756fa41f2b3cf9"></script><script src="dist/js/app.js?b396be756fa41f2b3cf9"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?fc8e52cca177f49cac0d"></script><script src="dist/js/app.js?fc8e52cca177f49cac0d"></script></body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -22,16 +22,14 @@ import xmind from './src/parse/xmind.js'
import markdown from './src/parse/markdown.js'
import icons from './src/svg/icons.js'
import * as constants from './src/constants/constant.js'
import themes from './src/themes/index.js'
import * as defaultTheme from './src/themes/default.js'
import * as defaultTheme from './src/theme/default.js'
MindMap.xmind = xmind
MindMap.markdown = markdown
MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.11.1'
MindMap.version = '0.12.1'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)

View File

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

View File

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

View File

@@ -1,167 +1,3 @@
// 主题列表
export const themeList = [
{
name: '默认',
value: 'default',
dark: false
},
{
name: '暗色2',
value: 'dark2',
dark: true
},
{
name: '天清绿',
value: 'skyGreen',
dark: false
},
{
name: '脑图经典2',
value: 'classic2',
dark: false
},
{
name: '脑图经典3',
value: 'classic3',
dark: false
},
{
name: '经典绿',
value: 'classicGreen',
dark: false
},
{
name: '经典蓝',
value: 'classicBlue',
dark: false
},
{
name: '天空蓝',
value: 'blueSky',
dark: false
},
{
name: '脑残粉',
value: 'brainImpairedPink',
dark: false
},
{
name: '暗色',
value: 'dark',
dark: true
},
{
name: '泥土黄',
value: 'earthYellow',
dark: false
},
{
name: '清新绿',
value: 'freshGreen',
dark: false
},
{
name: '清新红',
value: 'freshRed',
dark: false
},
{
name: '浪漫紫',
value: 'romanticPurple',
dark: false
},
{
name: '粉红葡萄',
value: 'pinkGrape',
dark: false
},
{
name: '薄荷',
value: 'mint',
dark: false
},
{
name: '金色vip',
value: 'gold',
dark: false
},
{
name: '活力橙',
value: 'vitalityOrange',
dark: false
},
{
name: '绿叶',
value: 'greenLeaf',
dark: false
},
{
name: '脑图经典',
value: 'classic',
dark: true
},
{
name: '脑图经典4',
value: 'classic4',
dark: false
},
{
name: '小黄人',
value: 'minions',
dark: false
},
{
name: '简约黑',
value: 'simpleBlack',
dark: false
},
{
name: '课程绿',
value: 'courseGreen',
dark: false
},
{
name: '咖啡',
value: 'coffee',
dark: false
},
{
name: '红色精神',
value: 'redSpirit',
dark: false
},
{
name: '黑色幽默',
value: 'blackHumour',
dark: true
},
{
name: '深夜办公室',
value: 'lateNightOffice',
dark: true
},
{
name: '黑金',
value: 'blackGold',
dark: true
},
{
name: '牛油果',
value: 'avocado',
dark: false
},
{
name: '秋天',
value: 'autumn',
dark: false
},
{
name: '橙汁',
value: 'orangeJuice',
dark: true
}
]
// 常量
export const CONSTANTS = {
CHANGE_THEME: 'changeTheme',
@@ -330,7 +166,9 @@ export const nodeDataNoStylePropList = [
'number',
'range',
'customLeft',
'customTop'
'customTop',
'customTextWidth',
'checkbox'
]
// 错误类型
@@ -375,3 +213,6 @@ export const selfCloseTagList = [
'meta',
'area'
]
// 非富文本模式下的节点文本行高
export const noneRichTextNodeLineHeight = 1.2

View File

@@ -7,6 +7,8 @@ export const defaultOpt = {
el: null,
// 思维导图回显数据
data: null,
// 要恢复的视图数据一般通过mindMap.view.getTransformData()方法获取
viewData: null,
// 是否只读
readonly: false,
// 布局
@@ -19,6 +21,16 @@ export const defaultOpt = {
themeConfig: {},
// 放大缩小的增量比例
scaleRatio: 0.2,
// 平移的步长比例,只在鼠标滚轮和触控板触发的平移中应用
translateRatio: 1,
// 最小缩小值百分数最小为0该选项只会影响view.narrow方法影响的行为为Ctrl+-快捷键、鼠标滚轮及触控板不会影响其他方法比如view.setScale所以需要你自行限制大小
minZoomRatio: 20,
// 最大放大值,百分数,传-1代表不限制否则传0以上数字该选项只会影响view.enlarge方法
maxZoomRatio: 400,
// 自定义判断wheel事件是否来自电脑的触控板
// 默认是通过判断e.deltaY的值是否小于10显然这种方法是不准确的当鼠标滚动的很慢或者触摸移动的很快时判断就失效了如果你有更好的方法欢迎提交issue
// 如果你希望自己来判断那么传递一个函数接收一个参数e事件对象需要返回true或false代表是否是来自触控板
customCheckIsTouchPad: null,
// 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
mouseScaleCenterUseMousePosition: true,
// 最多显示几个标签
@@ -214,7 +226,7 @@ export const defaultOpt = {
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
// 该选项实际影响的是render.moveNodeToCenter方法moveNodeToCenter方法本身也存在第二个参数resetScale来设置是否复位如果resetScale参数没有传递那么使用resetScaleOnMoveNodeToCenter配置否则使用resetScale配置
resetScaleOnMoveNodeToCenter: false,
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分。如果存在编号、任务勾选框内容,这里添加的前置内容会在这两者之后
createNodePrefixContent: null,
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
createNodePostfixContent: null,
@@ -235,6 +247,16 @@ export const defaultOpt = {
emptyTextMeasureHeightText: 'abc123我和你',
// 是否在进行节点文本编辑时实时更新节点大小和节点位置,开启后当节点数量比较多时可能会造成卡顿
openRealtimeRenderOnNodeTextEdit: false,
// 默认会给容器元素el绑定mousedown事件并且会阻止其默认事件这会带来一定问题比如你聚焦在思维导图外的其他输入框点击画布就不会触发其失焦可以通过该选项关闭阻止。关闭后也会带来一定问题比如鼠标框选节点时可能会选中节点文字看你如何取舍
mousedownEventPreventDefault: true,
// 在激活上粘贴用户剪贴板中的数据时,如果同时存在文本和图片,那么只粘贴文本,忽略图片
onlyPasteTextWhenHasImgAndText: true,
// 是否允许拖拽调整节点的宽度实际上压缩的是节点里面文本内容的宽度当节点文本内容宽度压缩到最小时无法继续压缩。如果节点存在图片那么最小值以图片宽度和文本内容最小宽度的最大值为准目前该特性仅在两种情况下可用1.开启了富文本模式即注册了RichText插件2.自定义节点内容)
enableDragModifyNodeWidth: true,
// 当允许拖拽调整节点的宽度时,可以通过该选项设置节点文本内容允许压缩的最小宽度
minNodeTextModifyWidth: 20,
// 同minNodeTextModifyWidth最大值传-1代表不限制
maxNodeTextModifyWidth: -1,
// 【Select插件】
// 多选节点时鼠标移动到边缘时的画布移动偏移量
@@ -318,6 +340,8 @@ export const defaultOpt = {
// 导出png、svg、pdf时会获取画布上的svg数据进行克隆然后通过该克隆的元素进行导出如果你想对该克隆元素做一些处理比如新增、替换、修改其中的一些元素那么可以通过该参数传递一个处理函数接收svg元素对象处理后需要返回原svg元素对象。
// 需要注意的是svg对象指的是@svgdotjs/svg.js库的元素对象所以你需要阅读该库的文档来操作该对象
handleBeingExportSvg: null,
// 导出图片或pdf都是通过canvas将svg绘制出来再导出所以如果思维导图特别大宽高可能会超出canvas支持的上限所以会进行缩放这个上限可以通过该参数设置代表canvas宽和高的最大宽度
maxCanvasSize: 16384,
// 【AssociativeLine插件】
// 关联线默认文字
@@ -334,6 +358,8 @@ export const defaultOpt = {
},
// 是否允许调整关联线两个端点的位置
enableAdjustAssociativeLinePoints: true,
// 关联线连接即将完成时执行如果要阻止本次连接可以返回true函数接收一个参数node目标节点实例
beforeAssociativeLineConnection: null,
// 【TouchEvent插件】
// 禁止双指缩放你仍旧可以使用api进行缩放
@@ -414,5 +440,15 @@ export const defaultOpt = {
// 【NodeImgAdjust】插件
// 拦截节点图片的删除点击节点图片上的删除按钮删除图片前会调用该函数如果函数返回true则取消删除
beforeDeleteNodeImg: null
beforeDeleteNodeImg: null,
// 删除和调整两个按钮的大小
imgResizeBtnSize: 25,
// 最小允许缩放的尺寸,请传入>=0的数字
minImgResizeWidth: 50,
minImgResizeHeight: 50,
// 最大允许缩放的尺寸依据主题的配置即主题的imgMaxWidth和imgMaxHeight配置如果设置为false那么使用maxImgResizeWidth和maxImgResizeHeight选项
maxImgResizeWidthInheritTheme: false,
// 最大允许缩放的尺寸maxImgResizeWidthInheritTheme选项设置为false时生效不限制最大值可传递Infinity
maxImgResizeWidth: Infinity,
maxImgResizeHeight: Infinity
}

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ import {
throttle
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
import { lineStyleProps } from '../../theme/default'
import { CONSTANTS, ERROR_TYPES } from '../../constants/constant'
import { Polygon } from '@svgdotjs/svg.js'
@@ -97,11 +97,11 @@ class Render {
this.beingPasteText = ''
this.beingPasteImgSize = 0
this.currentBeingPasteType = ''
this.pasteData = { text: null, img: null }
// 节点高亮框
this.highlightBoxNode = null
this.highlightBoxNodeStyle = null
// 上一次节点激活数据
this.lastActiveNode = null
this.lastActiveNodeList = []
// 布局
this.setLayout()
@@ -124,7 +124,7 @@ class Render {
// 重新设置思维导图数据
setData(data) {
if (this.mindMap.richText) {
if (this.hasRichTextPlugin()) {
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
} else {
this.renderTree = data
@@ -133,6 +133,11 @@ class Render {
// 绑定事件
bindEvent() {
const {
openPerformance,
performanceConfig,
openRealtimeRenderOnNodeTextEdit
} = this.mindMap.opt
// 画布点击事件清除当前激活节点列表
this.mindMap.on('draw_click', e => {
this.clearActiveNodeListOnDrawClick(e, 'click')
@@ -147,25 +152,6 @@ class Render {
this.setRootNodeCenter()
})
// 性能模式
this.performanceMode()
// 实时渲染当节点文本编辑时
if (this.mindMap.opt.openRealtimeRenderOnNodeTextEdit) {
this.mindMap.on('node_text_edit_change', ({ node, text }) => {
node._textData = node.createTextNode(text)
const { width, height } = node.getNodeRect()
node.width = width
node.height = height
node.layout()
this.mindMap.render(() => {
this.textEdit.updateTextEditNode()
})
})
}
}
// 性能模式,懒加载节点
performanceMode() {
const { openPerformance, performanceConfig } = this.mindMap.opt
const onViewDataChange = throttle(() => {
if (this.root) {
this.mindMap.emit('node_tree_render_start')
@@ -178,24 +164,57 @@ class Render {
)
}
}, performanceConfig.time)
let lastOpen = false
this.mindMap.on('before_update_config', opt => {
lastOpen = opt.openPerformance
})
this.mindMap.on('after_update_config', opt => {
if (opt.openPerformance && !lastOpen) {
// 动态开启性能模式
this.mindMap.on('view_data_change', onViewDataChange)
if (openPerformance) {
this.mindMap.on('view_data_change', onViewDataChange)
}
// 文本编辑时实时更新节点大小
this.onNodeTextEditChange = this.onNodeTextEditChange.bind(this)
if (openRealtimeRenderOnNodeTextEdit) {
this.mindMap.on('node_text_edit_change', this.onNodeTextEditChange)
}
// 监听配置改变事件
this.mindMap.on('after_update_config', (opt, lastOpt) => {
// 更新openPerformance配置
if (opt.openPerformance !== lastOpt.openPerformance) {
this.mindMap[opt.openPerformance ? 'on' : 'off'](
'view_data_change',
onViewDataChange
)
this.forceLoadNode()
}
if (!opt.openPerformance && lastOpen) {
// 动态关闭性能模式
this.mindMap.off('view_data_change', onViewDataChange)
this.forceLoadNode()
// 更新openRealtimeRenderOnNodeTextEdit配置
if (
opt.openRealtimeRenderOnNodeTextEdit !==
lastOpt.openRealtimeRenderOnNodeTextEdit
) {
this.mindMap[opt.openRealtimeRenderOnNodeTextEdit ? 'on' : 'off'](
'node_text_edit_change',
this.onNodeTextEditChange
)
}
})
if (!openPerformance) return
this.mindMap.on('view_data_change', onViewDataChange)
// 处理非https下的复制黏贴问题
// 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡
// if (!navigator.clipboard) {
// this.handlePaste = this.handlePaste.bind(this)
// window.addEventListener('paste', this.handlePaste)
// this.mindMap.on('beforeDestroy', () => {
// window.removeEventListener('paste', this.handlePaste)
// })
// }
}
// 监听文本编辑事件,实时更新节点大小
onNodeTextEditChange({ node, text }) {
node._textData = node.createTextNode(text)
const { width, height } = node.getNodeRect()
node.width = width
node.height = height
node.layout()
this.mindMap.render(() => {
// 输入框的left不会改变所以无需更新
this.textEdit.updateTextEditNode(['left'])
})
}
// 强制渲染节点,不考虑是否在画布可视区域内
@@ -405,7 +424,7 @@ class Render {
this.mindMap.keyCommand.addShortcut('Control+Down', () => {
this.mindMap.execCommand('DOWN_NODE')
})
// 复制节点
// 复制节点
this.mindMap.keyCommand.addShortcut('Control+c', () => {
this.copy()
})
@@ -415,7 +434,7 @@ class Render {
})
// 粘贴节点
this.mindMap.keyCommand.addShortcut('Control+v', () => {
this.paste()
if (navigator.clipboard) this.paste()
})
// 根节点居中显示
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
@@ -425,13 +444,11 @@ class Render {
// 派发节点激活事件
emitNodeActiveEvent(node = null, activeNodeList = [...this.activeNodeList]) {
let isChange = false
isChange = this.lastActiveNode !== node
if (!isChange) {
isChange = !checkNodeListIsEqual(this.lastActiveNodeList, activeNodeList)
}
const isChange = !checkNodeListIsEqual(
this.lastActiveNodeList,
activeNodeList
)
if (!isChange) return
this.lastActiveNode = node
this.lastActiveNodeList = [...activeNodeList]
this.mindMap.batchExecution.push('emitNodeActiveEvent', () => {
this.mindMap.emit('node_active', node, activeNodeList)
@@ -539,7 +556,7 @@ class Render {
}
// 触发一次保存,因为修改了渲染树的数据
if (
this.mindMap.richText &&
this.hasRichTextPlugin() &&
[CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)
) {
this.mindMap.command.addHistory()
@@ -553,7 +570,7 @@ class Render {
// 给当前被收起来的节点数据添加文本复位标志
resetUnExpandNodeStyle() {
if (!this.renderTree) return
if (!this.renderTree || !this.hasRichTextPlugin()) return
walk(this.renderTree, null, node => {
if (!node.data.expand) {
walk(node, null, node2 => {
@@ -730,7 +747,7 @@ class Render {
} = this.mindMap.opt
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
@@ -738,9 +755,9 @@ class Render {
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态
}
if (isRichText) params.resetRichText = isRichText
// 动态指定的子节点数据也需要添加相关属性
appointChildren = addDataToAppointNodes(appointChildren, {
...params
@@ -785,14 +802,14 @@ class Render {
}
this.textEdit.hideEditTextBox()
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode } = this.getNewNodeBehavior(false, true)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) params.resetRichText = isRichText
nodeList = addDataToAppointNodes(nodeList, params)
list.forEach(node => {
if (node.isGeneralization || node.isRoot) {
@@ -828,7 +845,7 @@ class Render {
} = this.mindMap.opt
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
@@ -836,9 +853,9 @@ class Render {
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) params.resetRichText = isRichText
// 动态指定的子节点数据也需要添加相关属性
appointChildren = addDataToAppointNodes(appointChildren, {
...params
@@ -885,14 +902,14 @@ class Render {
}
this.textEdit.hideEditTextBox()
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode } = this.getNewNodeBehavior(false, true)
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) params.resetRichText = isRichText
childList = addDataToAppointNodes(childList, params)
list.forEach(node => {
if (node.isGeneralization) {
@@ -927,7 +944,7 @@ class Render {
} = this.mindMap.opt
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
const handleMultiNodes = list.length > 1
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
handleMultiNodes
@@ -935,9 +952,9 @@ class Render {
const params = {
expand: true,
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) params.resetRichText = isRichText
list.forEach(node => {
if (node.isGeneralization || node.isRoot) {
return
@@ -956,9 +973,11 @@ class Render {
},
children: [node.nodeData]
}
node.setData({
resetRichText: true
})
if (isRichText) {
node.setData({
resetRichText: true
})
}
const parent = node.parent
// 获取当前节点所在位置
const index = getNodeDataIndex(node)
@@ -1048,7 +1067,7 @@ class Render {
}
})
// 如果是富文本,那么还要处理富文本内容
if (hasCustomStyles && this.mindMap.richText) {
if (hasCustomStyles && this.hasRichTextPlugin()) {
nodeData.resetRichText = true
nodeData.text = removeRichTextStyes(nodeData.text)
}
@@ -1118,20 +1137,45 @@ class Render {
})
}
// 非https下复制黏贴获取内容方法
handlePaste(event) {
const { disabledClipboard } = this.mindMap.opt
if (disabledClipboard) return
const clipboardData =
event.clipboardData || event.originalEvent.clipboardData
const items = clipboardData.items
let img = null
let text = ''
Array.from(items).forEach(item => {
if (item.type.indexOf('image') > -1) {
img = item.getAsFile()
}
if (item.type.indexOf('text') > -1) {
text = clipboardData.getData('text')
}
})
this.pasteData.img = img
this.pasteData.text = text
this.paste()
}
// 粘贴
async paste() {
const {
errorHandler,
handleIsSplitByWrapOnPasteCreateNewNode,
handleNodePasteImg,
disabledClipboard
disabledClipboard,
onlyPasteTextWhenHasImgAndText
} = this.mindMap.opt
// 读取剪贴板的文字和图片
let text = ''
let img = null
if (!disabledClipboard) {
try {
const res = await getDataFromClipboard()
const res = navigator.clipboard
? await getDataFromClipboard()
: this.pasteData
text = res.text || ''
img = res.img || null
} catch (error) {
@@ -1227,7 +1271,7 @@ class Render {
}
}
// 存在图片,则添加到当前激活节点
if (img) {
if (img && (!text || !onlyPasteTextWhenHasImgAndText)) {
try {
let imgData = null
// 自定义图片处理函数
@@ -1307,7 +1351,7 @@ class Render {
// 如果是富文本模式,那么某些层级变化需要更新样式
checkNodeLayerChange(node, toNode, toNodeIsParent = false) {
if (this.mindMap.richText) {
if (this.hasRichTextPlugin()) {
// 如果设置了自定义样式那么不需要更新
if (this.mindMap.richText.checkNodeHasCustomRichTextStyle(node)) {
return
@@ -1534,7 +1578,7 @@ class Render {
const newData = simpleDeepClone(item)
createUidForAppointNodes([newData], true, node => {
// 可能跨层级复制,那么富文本样式需要更新
if (this.mindMap.richText) {
if (this.hasRichTextPlugin()) {
// 如果设置了自定义样式那么不需要更新
if (
this.mindMap.richText.checkNodeHasCustomRichTextStyle(node.data)
@@ -1557,7 +1601,7 @@ class Render {
[prop]: value
}
// 如果开启了富文本,则需要应用到富文本上
if (this.mindMap.richText) {
if (this.hasRichTextPlugin()) {
this.mindMap.richText.setNotActiveNodeStyle(node, {
[prop]: value
})
@@ -1573,7 +1617,7 @@ class Render {
setNodeStyles(node, style) {
let data = { ...style }
// 如果开启了富文本,则需要应用到富文本上
if (this.mindMap.richText) {
if (this.hasRichTextPlugin()) {
this.mindMap.richText.setNotActiveNodeStyle(node, style)
}
this.setNodeDataRender(node, data)
@@ -1768,7 +1812,7 @@ class Render {
// 设置节点公式
insertFormula(formula, appointNodes = []) {
// 只在富文本模式下可用并且需要注册Formula插件
if (!this.mindMap.richText || !this.mindMap.formula) return
if (!this.hasRichTextPlugin() || !this.mindMap.formula) return
appointNodes = formatDataToArray(appointNodes)
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
list.forEach(node => {
@@ -1790,7 +1834,7 @@ class Render {
})
const list = parseAddGeneralizationNodeList(nodeList)
if (list.length <= 0) return
const isRichText = !!this.mindMap.richText
const isRichText = this.hasRichTextPlugin()
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
list.length > 1
@@ -1805,9 +1849,9 @@ class Render {
range: item.range || null,
uid: createUid(),
richText: isRichText,
resetRichText: isRichText,
isActive: focusNewNode
}
if (isRichText) newData.resetRichText = isRichText
let generalization = item.node.getData('generalization')
generalization = generalization
? Array.isArray(generalization)
@@ -2137,6 +2181,11 @@ class Render {
if (!this.highlightBoxNode) return
this.highlightBoxNode.remove()
}
// 是否存在富文本插件
hasRichTextPlugin() {
return !!this.mindMap.richText
}
}
export default Render

View File

@@ -6,9 +6,15 @@ import {
htmlEscape,
handleInputPasteText,
checkSmmFormatData,
getTextFromHtml
getTextFromHtml,
isWhite,
getVisibleColorFromTheme
} from '../../utils'
import { ERROR_TYPES, CONSTANTS } from '../../constants/constant'
import {
ERROR_TYPES,
CONSTANTS,
noneRichTextNodeLineHeight
} from '../../constants/constant'
// 节点文字编辑类
export default class TextEdit {
@@ -92,6 +98,32 @@ export default class TextEdit {
this.mindMap.on('beforeDestroy', () => {
this.unBindEvent()
})
this.mindMap.on('after_update_config', (opt, lastOpt) => {
if (
opt.openRealtimeRenderOnNodeTextEdit !==
lastOpt.openRealtimeRenderOnNodeTextEdit
) {
if (this.mindMap.richText) {
this.mindMap.richText.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
opt.openRealtimeRenderOnNodeTextEdit
)
} else {
this.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
opt.openRealtimeRenderOnNodeTextEdit
)
}
}
if (
opt.enableAutoEnterTextEditWhenKeydown !==
lastOpt.enableAutoEnterTextEditWhenKeydown
) {
window[
opt.enableAutoEnterTextEditWhenKeydown
? 'addEventListener'
: 'removeEventListener'
]('keydown', this.onKeydown)
}
})
}
// 解绑事件
@@ -158,7 +190,8 @@ export default class TextEdit {
if (node.isUseCustomNodeContent()) {
return
}
const { beforeTextEdit } = this.mindMap.opt
const { beforeTextEdit, openRealtimeRenderOnNodeTextEdit } =
this.mindMap.opt
if (typeof beforeTextEdit === 'function') {
let isShow = false
try {
@@ -172,7 +205,12 @@ export default class TextEdit {
this.currentNode = node
const { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
this.mindMap.view.translateXY(offsetLeft, offsetTop)
const rect = node._textData.node.node.getBoundingClientRect()
const g = node._textData.node
const rect = g.node.getBoundingClientRect()
// 如果开启了大小实时更新,那么直接隐藏节点原文本
if (openRealtimeRenderOnNodeTextEdit) {
g.hide()
}
const params = {
node,
rect,
@@ -187,6 +225,21 @@ export default class TextEdit {
this.showEditTextBox(params)
}
// 当openRealtimeRenderOnNodeTextEdit配置更新后需要更新编辑框样式
onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
openRealtimeRenderOnNodeTextEdit
) {
if (!this.textEditNode) return
this.textEditNode.style.background = openRealtimeRenderOnNodeTextEdit
? 'transparent'
: this.currentNode
? this.getBackground(this.currentNode)
: ''
this.textEditNode.style.boxShadow = openRealtimeRenderOnNodeTextEdit
? 'none'
: '0 0 20px rgba(0,0,0,.5)'
}
// 处理画布缩放
onScale() {
const node = this.getCurrentEditNode()
@@ -208,15 +261,34 @@ export default class TextEdit {
// 显示文本编辑框
showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
if (this.showTextEdit) return
const { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText } =
this.mindMap.opt
const {
nodeTextEditZIndex,
textAutoWrapWidth,
selectTextOnEnterEditText,
openRealtimeRenderOnNodeTextEdit
} = this.mindMap.opt
if (!isFromScale) {
this.mindMap.emit('before_show_text_edit')
}
this.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
this.textEditNode.classList.add('smm-node-edit-wrap')
this.textEditNode.style.cssText = `
position: fixed;
box-sizing: border-box;
${
openRealtimeRenderOnNodeTextEdit
? ''
: `box-shadow: 0 0 20px rgba(0,0,0,.5);`
}
padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;
margin-left: -${this.textNodePaddingX}px;
margin-top: -${this.textNodePaddingY}px;
outline: none;
word-break: break-all;
line-break: anywhere;
`
this.textEditNode.setAttribute('contenteditable', true)
this.textEditNode.addEventListener('keyup', e => {
e.stopPropagation()
@@ -253,30 +325,34 @@ export default class TextEdit {
this.mindMap.opt.customInnerElsAppendTo || document.body
targetNode.appendChild(this.textEditNode)
}
let scale = this.mindMap.view.scale
let lineHeight = node.style.merge('lineHeight')
let fontSize = node.style.merge('fontSize')
let textLines = (this.cacheEditingText || node.getData('text'))
const scale = this.mindMap.view.scale
const fontSize = node.style.merge('fontSize')
const textLines = (this.cacheEditingText || node.getData('text'))
.split(/\n/gim)
.map(item => {
return htmlEscape(item)
})
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
node.style.domText(this.textEditNode, scale, isMultiLine)
const isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
node.style.domText(this.textEditNode, scale)
if (!openRealtimeRenderOnNodeTextEdit) {
this.textEditNode.style.background = this.getBackground(node)
}
this.textEditNode.style.zIndex = nodeTextEditZIndex
this.textEditNode.innerHTML = textLines.join('<br>')
this.textEditNode.style.minWidth =
rect.width + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight =
rect.height + this.textNodePaddingY * 2 + 'px'
this.textEditNode.style.minHeight = rect.height + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
this.textEditNode.style.maxWidth = textAutoWrapWidth * scale + 'px'
if (isMultiLine && lineHeight !== 1) {
if (isMultiLine) {
this.textEditNode.style.lineHeight = noneRichTextNodeLineHeight
this.textEditNode.style.transform = `translateY(${
-((lineHeight * fontSize - fontSize) / 2) * scale
(((noneRichTextNodeLineHeight - 1) * fontSize) / 2) * scale
}px)`
} else {
this.textEditNode.style.lineHeight = 'normal'
}
this.showTextEdit = true
// 选中文本
@@ -292,7 +368,8 @@ export default class TextEdit {
}
// 更新文本编辑框的大小和位置
updateTextEditNode() {
// notChangeProps不会发生改变的属性列表
updateTextEditNode(notChangeProps = []) {
if (this.mindMap.richText) {
this.mindMap.richText.updateTextEditNode()
return
@@ -305,10 +382,32 @@ export default class TextEdit {
rect.width + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight =
rect.height + this.textNodePaddingY * 2 + 'px'
this.textEditNode.style.left = rect.left + 'px'
if (!notChangeProps.includes('left'))
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
}
// 获取编辑区域的背景填充
getBackground(node) {
const gradientStyle = node.style.merge('gradientStyle')
// 当前使用的是渐变色背景
if (gradientStyle) {
const startColor = node.style.merge('startColor')
const endColor = node.style.merge('endColor')
return `linear-gradient(to right, ${startColor}, ${endColor})`
} else {
// 单色背景
const bgColor = node.style.merge('fillColor')
const color = node.style.merge('color')
// 默认使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
return bgColor === 'transparent'
? isWhite(color)
? getVisibleColorFromTheme(this.mindMap.themeConfig)
: '#fff'
: bgColor
}
}
// 删除文本编辑元素
removeTextEditEl() {
if (this.mindMap.richText) {
@@ -333,16 +432,8 @@ export default class TextEdit {
if (!this.showTextEdit) {
return
}
this.renderer.activeNodeList.forEach(node => {
let str = this.getEditText()
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
if (node.isGeneralization) {
// 概要节点
node.generalizationBelongNode.updateGeneralization()
}
this.mindMap.render()
})
const currentNode = this.currentNode
const text = this.getEditText()
this.currentNode = null
this.textEditNode.style.display = 'none'
this.textEditNode.innerHTML = ''
@@ -351,6 +442,12 @@ export default class TextEdit {
this.textEditNode.style.fontWeight = 'normal'
this.textEditNode.style.transform = 'translateY(0)'
this.showTextEdit = false
this.mindMap.execCommand('SET_NODE_TEXT', currentNode, text)
// if (currentNode.isGeneralization) {
// // 概要节点
// currentNode.generalizationBelongNode.updateGeneralization()
// }
this.mindMap.render()
this.mindMap.emit(
'hide_text_edit',
this.textEditNode,

View File

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

View File

@@ -1,6 +1,5 @@
import { checkIsNodeStyleDataKey } from '../../../utils/index'
const rootProp = ['paddingX', 'paddingY']
const backgroundStyleProps = [
'backgroundColor',
'backgroundImage',
@@ -13,6 +12,7 @@ const backgroundStyleProps = [
class Style {
// 设置背景样式
static setBackgroundStyle(el, themeConfig) {
if (!el) return
// 缓存容器元素原本的样式
if (!Style.cacheStyle) {
Style.cacheStyle = {}
@@ -62,11 +62,10 @@ class Style {
// 合并样式
merge(prop, root) {
let themeConfig = this.ctx.mindMap.themeConfig
// 三级及以下节点
let defaultConfig = themeConfig.node
let defaultConfig = null
let useRoot = false
if (root || rootProp.includes(prop)) {
// 直接使用最外层样式
if (root) {
// 使用最外层样式
useRoot = true
defaultConfig = themeConfig
} else if (this.ctx.isGeneralization) {
@@ -78,12 +77,21 @@ class Style {
} else if (this.ctx.layerIndex === 1) {
// 二级节点
defaultConfig = themeConfig.second
} else {
// 三级及以下节点
defaultConfig = themeConfig.node
}
let value = ''
// 优先使用节点本身的样式
const value =
this.getSelfStyle(prop) !== undefined
? this.getSelfStyle(prop)
: defaultConfig[prop]
if (this.getSelfStyle(prop) !== undefined) {
value = this.getSelfStyle(prop)
} else if (defaultConfig[prop] !== undefined) {
// 否则使用对应层级的样式
value = defaultConfig[prop]
} else {
// 否则使用最外层样式
value = themeConfig[prop]
}
if (!useRoot) {
this.addToEffectiveStyles({
[prop]: value
@@ -176,7 +184,7 @@ class Style {
})
.css({
'font-family': styles.fontFamily,
'font-size': styles.fontSize,
'font-size': styles.fontSize + 'px',
'font-weight': styles.fontWeight,
'font-style': styles.fontStyle,
'text-decoration': styles.textDecoration
@@ -222,20 +230,20 @@ class Style {
}
// html文字节点
domText(node, fontSizeScale = 1, isMultiLine) {
domText(node, fontSizeScale = 1) {
const styles = {
color: this.merge('color'),
fontFamily: this.merge('fontFamily'),
fontSize: this.merge('fontSize'),
fontWeight: this.merge('fontWeight'),
fontStyle: this.merge('fontStyle'),
textDecoration: this.merge('textDecoration'),
lineHeight: this.merge('lineHeight')
textDecoration: this.merge('textDecoration')
}
node.style.color = styles.color
node.style.textDecoration = styles.textDecoration
node.style.fontFamily = styles.fontFamily
node.style.fontSize = styles.fontSize * fontSizeScale + 'px'
node.style.fontWeight = styles.fontWeight || 'normal'
node.style.lineHeight = !isMultiLine ? 'normal' : styles.lineHeight
node.style.fontStyle = styles.fontStyle
}
@@ -331,7 +339,7 @@ class Style {
node2.fill({ color: color })
fillNode.fill({ color: fill })
if (this.ctx.mindMap.opt.isShowExpandNum) {
node.attr({ 'font-size': fontSize, 'font-color': fontColor })
node.attr({ 'font-size': fontSize + 'px', 'font-color': fontColor })
}
}
@@ -348,8 +356,10 @@ class Style {
// hover和激活节点
hoverNode(node) {
const hoverRectColor = this.merge('hoverRectColor') || this.ctx.mindMap.opt.hoverRectColor
node.radius(5).fill('none').stroke({
const hoverRectColor =
this.merge('hoverRectColor') || this.ctx.mindMap.opt.hoverRectColor
const hoverRectRadius = this.merge('hoverRectRadius')
node.radius(hoverRectRadius).fill('none').stroke({
color: hoverRectColor
})
}

View File

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

View File

@@ -1,5 +1,4 @@
import {
measureText,
resizeImgSize,
removeHtmlStyle,
addHtmlStyle,
@@ -11,7 +10,19 @@ import {
} from '../../../utils'
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
import iconsSvg from '../../../svg/icons'
import { CONSTANTS } from '../../../constants/constant'
import {
CONSTANTS,
noneRichTextNodeLineHeight
} from '../../../constants/constant'
// 测量svg文本宽高
const measureText = (text, style) => {
const g = new G()
const node = new Text().text(text)
style.text(node)
g.add(node)
return g.bbox()
}
// 标签默认的样式
const defaultTagStyle = {
@@ -115,9 +126,11 @@ function createIconNode() {
// 创建富文本节点
function createRichTextNode(specifyText) {
const hasCustomWidth = this.hasCustomWidth()
let text =
typeof specifyText === 'string' ? specifyText : this.getData('text')
const { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
let { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
textAutoWrapWidth = hasCustomWidth ? this.customTextWidth : textAutoWrapWidth
let g = new G()
// 重新设置富文本节点内容
let recoverText = false
@@ -172,6 +185,11 @@ function createRichTextNode(specifyText) {
el.classList.add('smm-richtext-node-wrap')
addXmlns(el)
el.style.maxWidth = textAutoWrapWidth + 'px'
if (hasCustomWidth) {
el.style.width = this.customTextWidth + 'px'
} else {
el.style.width = ''
}
let { width, height } = el.getBoundingClientRect()
// 如果文本为空,那么需要计算一个默认高度
if (height <= 0) {
@@ -211,16 +229,14 @@ function createTextNode(specifyText) {
}
let g = new G()
let fontSize = this.getStyle('fontSize', false)
let lineHeight = this.getStyle('lineHeight', false)
// 文本超长自动换行
let textStyle = this.style.getTextFontStyle()
let textArr = []
if (!isUndef(text)) {
textArr = String(text).split(/\n/gim)
}
const { textAutoWrapWidth: maxWidth, emptyTextMeasureHeightText } =
this.mindMap.opt
let isMultiLine = false
let isMultiLine = textArr.length > 1
textArr.forEach((item, index) => {
let arr = item.split('')
let lines = []
@@ -228,7 +244,7 @@ function createTextNode(specifyText) {
while (arr.length) {
let str = arr.shift()
let text = [...line, str].join('')
if (measureText(text, textStyle).width <= maxWidth) {
if (measureText(text, this.style).width <= maxWidth) {
line.push(str)
} else {
lines.push(line.join(''))
@@ -247,7 +263,10 @@ function createTextNode(specifyText) {
textArr.forEach((item, index) => {
let node = new Text().text(item)
this.style.text(node)
node.y(fontSize * lineHeight * index)
node.y(
fontSize * noneRichTextNodeLineHeight * index +
((noneRichTextNodeLineHeight - 1) * fontSize) / 2
)
g.add(node)
})
let { width, height } = g.bbox()

View File

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

View File

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

View File

@@ -69,43 +69,41 @@ class Base {
}
}
// 获取节点编号信息
getNumberInfo({ parent, ancestors, layerIndex, index }) {
// 编号
const hasNumberPlugin = !!this.mindMap.numbers
const parentNumberStr =
hasNumberPlugin && parent && parent._node.number
? parent._node.number
: ''
const newNumberStr = hasNumberPlugin
? this.mindMap.numbers.getNodeNumberStr({
ancestors,
layerIndex,
num: index + 1,
parentNumberStr
})
: ''
return {
hasNumberPlugin,
newNumberStr
// 节点节点数据是否发生了改变
checkIsNodeDataChange(lastData, curData) {
if (lastData) {
// 对比忽略激活状态和展开收起状态
lastData = typeof lastData === 'string' ? JSON.parse(lastData) : lastData
lastData.isActive = curData.isActive
lastData.expand = curData.expand
lastData = JSON.stringify(lastData)
}
return lastData !== JSON.stringify(curData)
}
// 创建节点实例
createNode(data, parent, isRoot, layerIndex, index, ancestors) {
// 编号
const { hasNumberPlugin, newNumberStr } = this.getNumberInfo({
parent,
ancestors,
layerIndex,
index
})
// 创建节点
// 库前置内容数据
const nodeInnerPrefixData = {}
this.mindMap.nodeInnerPrefixList.forEach(item => {
if (item.createNodeData) {
const [key, value] = item.createNodeData({
data,
parent,
ancestors,
layerIndex,
index
})
nodeInnerPrefixData[key] = value
}
})
const uid = data.data.uid
let newNode = null
// 数据上保存了节点引用,那么直接复用节点
if (data && data._node && !this.renderer.reRender) {
newNode = data._node
// 节点层级改变了
const isLayerTypeChange = this.checkIsLayerTypeChange(
newNode.layerIndex,
layerIndex
@@ -119,26 +117,35 @@ class Base {
}
this.cacheNode(data._node.uid, newNode)
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
// 判断编号是否改变
let isNumberChange = false
if (hasNumberPlugin) {
isNumberChange = this.mindMap.numbers.updateNumber(
newNode,
newNumberStr
)
}
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
const isNeedResizeSources = this.checkIsNeedResizeSources()
// 库前置内容是否改变
let isNodeInnerPrefixChange = false
this.mindMap.nodeInnerPrefixList.forEach(item => {
if (item.updateNodeData) {
const isChange = item.updateNodeData(newNode, nodeInnerPrefixData)
if (isChange) {
isNodeInnerPrefixChange = isChange
}
}
})
// 主题或主题配置改变了
const isResizeSource = this.checkIsNeedResizeSources()
// 节点数据改变了
const isNodeDataChange = this.checkIsNodeDataChange(
data._node.nodeDataSnapshot,
data.data
)
// 重新计算节点大小和布局
if (
isNeedResizeSources ||
isResizeSource ||
isNodeDataChange ||
isLayerTypeChange ||
newNode.getData('resetRichText') ||
isNumberChange
isNodeInnerPrefixChange
) {
newNode.getSize()
newNode.needLayout = true
}
this.checkGetGeneralizationChange(newNode, isNeedResizeSources)
this.checkGetGeneralizationChange(newNode, isResizeSource)
} else if (
(this.lru.has(uid) || this.renderer.lastNodeCache[uid]) &&
!this.renderer.reRender
@@ -150,6 +157,7 @@ class Base {
newNode = this.lru.get(uid) || this.renderer.lastNodeCache[uid]
// 保存该节点上一次的数据
const lastData = JSON.stringify(newNode.getData())
// 节点层级改变了
const isLayerTypeChange = this.checkIsLayerTypeChange(
newNode.layerIndex,
layerIndex
@@ -167,22 +175,25 @@ class Base {
data._node = newNode
// 主题或主题配置改变了需要重新计算节点大小和布局
const isResizeSource = this.checkIsNeedResizeSources()
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本,节点数据改变了等情况需要重新计算节点大小和布局
const isNodeDataChange = lastData !== JSON.stringify(data.data)
// 判断编号是否改变
let isNumberChange = false
if (hasNumberPlugin) {
isNumberChange = this.mindMap.numbers.updateNumber(
newNode,
newNumberStr
)
}
// 点数据改变了
const isNodeDataChange = this.checkIsNodeDataChange(lastData, data.data)
// 库前置内容是否改变
let isNodeInnerPrefixChange = false
this.mindMap.nodeInnerPrefixList.forEach(item => {
if (item.updateNodeData) {
const isChange = item.updateNodeData(newNode, nodeInnerPrefixData)
if (isChange) {
isNodeInnerPrefixChange = isChange
}
}
})
// 重新计算节点大小和布局
if (
isResizeSource ||
isNodeDataChange ||
isLayerTypeChange ||
newNode.getData('resetRichText') ||
isNumberChange
isNodeInnerPrefixChange
) {
newNode.getSize()
newNode.needLayout = true
@@ -200,7 +211,7 @@ class Base {
layerIndex,
isRoot,
parent: !isRoot ? parent._node : null,
number: newNumberStr
...nodeInnerPrefixData
})
// uid保存到数据上为了节点复用
data.data.uid = newUid
@@ -244,7 +255,9 @@ class Base {
isResizeSource ||
(newData && JSON.stringify(oldData) !== JSON.stringify(newData))
) {
gNode.nodeData.data = newData
if (newData) {
gNode.nodeData.data = newData
}
gNode.getSize()
gNode.needLayout = true
}

View File

@@ -446,6 +446,12 @@ class AssociativeLine {
// 完成创建连接线
completeCreateLine(node) {
if (this.creatingStartNode.uid === node.uid) return
const { beforeAssociativeLineConnection } = this.mindMap.opt
let stop = false
if (typeof beforeAssociativeLineConnection === 'function') {
stop = beforeAssociativeLineConnection(node)
}
if (stop) return
this.addLine(this.creatingStartNode, node)
if (this.overlapNode && this.overlapNode.getData('isActive')) {
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, false)

View File

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

View File

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

View File

@@ -3,6 +3,9 @@ import Quill from 'quill'
import { getChromeVersion, htmlEscape } from '../utils/index'
import { getBaseStyleText, getFontStyleText } from './FormulaStyle'
let extended = false
const QuillFormula = Quill.import('formats/formula')
// 数学公式支持插件
// 该插件在富文本模式下可用
class Formula {
@@ -16,6 +19,18 @@ class Formula {
this.cssEl = null
this.addStyle()
this.extendQuill()
this.onDestroy = this.onDestroy.bind(this)
this.mindMap.on('beforeDestroy', this.onDestroy)
}
onDestroy() {
const instanceCount = Object.getPrototypeOf(this.mindMap).constructor
.instanceCount
// 如果思维导图实例数量变成0了那么就恢复成默认的
if (instanceCount <= 1) {
extended = false
Quill.register('formats/formula', QuillFormula, true)
}
}
init() {
@@ -50,7 +65,9 @@ class Formula {
// 修改formula格式工具
extendQuill() {
const QuillFormula = Quill.import('formats/formula')
if (extended) return
extended = true
const self = this
class CustomFormulaBlot extends QuillFormula {
@@ -168,11 +185,13 @@ class Formula {
// 插件被移除前做的事情
beforePluginRemove() {
this.removeStyle()
this.mindMap.off('beforeDestroy', this.onDestroy)
}
// 插件被卸载前做的事情
beforePluginDestroy() {
this.removeStyle()
this.mindMap.off('beforeDestroy', this.onDestroy)
}
}

View File

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

View File

@@ -4,8 +4,6 @@ import 'quill/dist/quill.snow.css'
import {
walk,
getTextFromHtml,
isWhite,
getVisibleColorFromTheme,
isUndef,
checkSmmFormatData,
removeHtmlNodeByClass,
@@ -89,6 +87,18 @@ class RichText {
// 插入样式
appendCss() {
this.mindMap.appendCss(
'richText',
`
.smm-richtext-node-wrap {
word-break: break-all;
}
.smm-richtext-node-wrap p {
font-family: auto;
}
`
)
let cssText = `
.ql-editor {
overflow: hidden;
@@ -107,15 +117,6 @@ class RichText {
border: none;
}
.smm-richtext-node-wrap {
word-break: break-all;
}
.smm-richtext-node-wrap p {
font-family: auto;
}
.smm-richtext-node-edit-wrap p {
font-family: auto;
}
@@ -180,14 +181,18 @@ class RichText {
if (this.showTextEdit) {
return
}
const {
let {
richTextEditFakeInPlace,
customInnerElsAppendTo,
nodeTextEditZIndex,
textAutoWrapWidth,
selectTextOnEnterEditText,
transformRichTextOnEnterEdit
transformRichTextOnEnterEdit,
openRealtimeRenderOnNodeTextEdit
} = this.mindMap.opt
textAutoWrapWidth = node.hasCustomWidth()
? node.customTextWidth
: textAutoWrapWidth
this.node = node
this.isInserting = isInserting
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
@@ -216,10 +221,13 @@ class RichText {
this.textEditNode.style.cssText = `
position:fixed;
box-sizing: border-box;
box-shadow: 0 0 20px rgba(0,0,0,.5);
${
openRealtimeRenderOnNodeTextEdit
? ''
: 'box-shadow: 0 0 20px rgba(0,0,0,.5);'
}
outline: none;
word-break:
break-all;
word-break: break-all;
padding: ${paddingY}px ${paddingX}px;
`
this.textEditNode.addEventListener('click', e => {
@@ -239,7 +247,10 @@ class RichText {
this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px`
this.textEditNode.style.marginTop = `-${paddingY * scaleY}px`
this.textEditNode.style.zIndex = nodeTextEditZIndex
this.textEditNode.style.background = this.getBackground(node)
if (!openRealtimeRenderOnNodeTextEdit) {
this.textEditNode.style.background =
this.mindMap.renderer.textEdit.getBackground(node)
}
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
this.textEditNode.style.minHeight = originHeight + 'px'
this.textEditNode.style.left = rect.left + 'px'
@@ -292,11 +303,26 @@ class RichText {
this.cacheEditingText = ''
}
// 当openRealtimeRenderOnNodeTextEdit配置更新后需要更新编辑框样式
onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
openRealtimeRenderOnNodeTextEdit
) {
if (!this.textEditNode) return
this.textEditNode.style.background = openRealtimeRenderOnNodeTextEdit
? 'transparent'
: this.node
? this.mindMap.renderer.textEdit.getBackground(this.node)
: ''
this.textEditNode.style.boxShadow = openRealtimeRenderOnNodeTextEdit
? 'none'
: '0 0 20px rgba(0,0,0,.5)'
}
// 更新文本编辑框的大小和位置
updateTextEditNode() {
if (!this.node) return
const rect = this.node._textData.node.node.getBoundingClientRect()
const g = this.node._textData.node
const rect = g.node.getBoundingClientRect()
const originWidth = g.attr('data-width')
const originHeight = g.attr('data-height')
this.textEditNode.style.minWidth =
@@ -313,27 +339,6 @@ class RichText {
targetNode.removeChild(this.textEditNode)
}
// 获取编辑区域的背景填充
getBackground(node) {
const gradientStyle = node.style.merge('gradientStyle')
// 当前使用的是渐变色背景
if (gradientStyle) {
const startColor = node.style.merge('startColor')
const endColor = node.style.merge('endColor')
return `linear-gradient(to right, ${startColor}, ${endColor})`
} else {
// 单色背景
const bgColor = node.style.merge('fillColor')
const color = node.style.merge('color')
// 默认使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
return bgColor === 'transparent'
? isWhite(color)
? getVisibleColorFromTheme(this.mindMap.themeConfig)
: '#fff'
: bgColor
}
}
// 如果是非富文本的情况,需要手动应用文本样式
setTextStyleIfNotRichText(node) {
let style = {
@@ -382,8 +387,13 @@ class RichText {
}
let html = this.getEditText()
html = this.sortHtmlNodeStyles(html)
let list =
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
const list = nodes && nodes.length > 0 ? nodes : [this.node]
const node = this.node
this.textEditNode.style.display = 'none'
this.showTextEdit = false
this.mindMap.emit('rich_text_selection_change', false)
this.node = null
this.isInserting = false
list.forEach(node => {
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
// if (node.isGeneralization) {
@@ -392,12 +402,6 @@ class RichText {
// }
this.mindMap.render()
})
const node = this.node
this.textEditNode.style.display = 'none'
this.showTextEdit = false
this.mindMap.emit('rich_text_selection_change', false)
this.node = null
this.isInserting = false
this.mindMap.emit('hide_text_edit', this.textEditNode, list, node)
}
@@ -534,6 +538,7 @@ class RichText {
// let style = this.getPasteTextStyle()
// return new Delta().insert(this.formatPasteText(node.data), style)
// })
// 剪贴板里只要存在文本就会走这里,所以当剪贴板里是纯文本,或文本+图片都可以监听到和拦截,但是只有纯图片时不会走这里,所以无法拦截
this.quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
let ops = []
let style = this.getPasteTextStyle()
@@ -549,6 +554,20 @@ class RichText {
delta.ops = ops
return delta
})
// 拦截图片的粘贴,当剪贴板里是纯图片,或文本+图片都可以拦截到,但是带来的问题是文本+图片时里面的文本也无法粘贴
this.quill.root.addEventListener(
'paste',
e => {
if (
e.clipboardData &&
e.clipboardData.files &&
e.clipboardData.files.length
) {
e.preventDefault()
}
},
true
)
}
// 获取粘贴的文本的样式
@@ -846,6 +865,7 @@ class RichText {
this.transformAllNodesToNormalNode()
document.head.removeChild(this.styleEl)
this.unbindEvent()
this.mindMap.removeAppendCss('richText')
}
// 插件被卸载前做的事情

View File

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

View File

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

View File

@@ -166,7 +166,7 @@ function styleText(node) {
})
.css({
'font-family': associativeLineTextFontFamily,
'font-size': associativeLineTextFontSize
'font-size': associativeLineTextFontSize + 'px'
})
}

View File

@@ -1,11 +1,10 @@
// 默认主题
export default {
// 节点内边距
paddingX: 15,
paddingY: 5,
// 图片显示的最大宽度
imgMaxWidth: 100,
imgMaxWidth: 200,
// 图片显示的最大高度
imgMaxHeight: 100,
// icon的大小
@@ -73,7 +72,6 @@ export default {
fontSize: 16,
fontWeight: 'bold',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: 'transparent',
borderWidth: 0,
borderDasharray: 'none',
@@ -87,7 +85,11 @@ export default {
// 连线标记的位置start头部、end尾部该配置在showLineMarker配置为true时生效
lineMarkerDir: 'end',
// 节点鼠标hover和激活时显示的矩形边框的颜色主题里不设置默认会取hoverRectColor实例化选项的值
hoverRectColor: ''
hoverRectColor: '',
// 点鼠标hover和激活时显示的矩形边框的圆角大小
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
},
// 二级节点样式
second: {
@@ -100,7 +102,6 @@ export default {
fontSize: 16,
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
borderWidth: 1,
borderDasharray: 'none',
@@ -112,7 +113,10 @@ export default {
startDir: [0, 0],
endDir: [1, 0],
lineMarkerDir: 'end',
hoverRectColor: ''
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
},
// 三级及以下节点样式
node: {
@@ -125,7 +129,6 @@ export default {
fontSize: 14,
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: 'transparent',
borderWidth: 0,
borderRadius: 5,
@@ -137,7 +140,10 @@ export default {
startDir: [0, 0],
endDir: [1, 0],
lineMarkerDir: 'end',
hoverRectColor: ''
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
},
// 概要节点样式
generalization: {
@@ -150,7 +156,6 @@ export default {
fontSize: 16,
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
borderWidth: 1,
borderDasharray: 'none',
@@ -161,7 +166,10 @@ export default {
endColor: '#fff',
startDir: [0, 0],
endDir: [1, 0],
hoverRectColor: ''
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
}
}
@@ -195,7 +203,8 @@ const nodeSizeIndependenceList = [
'endColor',
'startDir',
'endDir',
'hoverRectColor'
'hoverRectColor',
'hoverRectRadius'
]
export const checkIsNodeSizeIndependenceConfig = config => {
let keys = Object.keys(config)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import {
} from '../constants/constant'
import MersenneTwister from './mersenneTwister'
import { ForeignObject } from '@svgdotjs/svg.js'
import merge from 'deepmerge'
// 深度优先遍历树
export const walk = (
@@ -75,11 +76,11 @@ export const resizeImgSizeByOriginRatio = (
let nRatio = width / height
let mRatio = newWidth / newHeight
if (nRatio > mRatio) {
// 固定高度
arr = [nRatio * newHeight, newHeight]
} else {
// 固定宽度
arr = [newWidth, newWidth / nRatio]
} else {
// 固定高度
arr = [nRatio * newHeight, newHeight]
}
return arr
}
@@ -94,11 +95,11 @@ export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
} else {
let mRatio = maxWidth / maxHeight
if (nRatio > mRatio) {
// 固定高度
arr = [nRatio * maxHeight, maxHeight]
} else {
// 固定宽度
arr = [maxWidth, maxWidth / nRatio]
} else {
// 固定高度
arr = [nRatio * maxHeight, maxHeight]
}
}
} else if (maxWidth) {
@@ -1610,3 +1611,12 @@ export const sortNodeList = nodeList => {
})
return nodeList
}
// 合并主题配置
export const mergeTheme = (dest, source) => {
return merge(dest, source, {
arrayMerge: (destinationArray, sourceArray) => {
return sourceArray
}
})
}

63
web/package-lock.json generated
View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

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