Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5688bb6821 | ||
|
|
c9d0b6c916 | ||
|
|
5a116d952a | ||
|
|
5717b4fa1d | ||
|
|
e04a5d4a6f | ||
|
|
a7d97065c6 | ||
|
|
4332abce4d | ||
|
|
f71b47b215 | ||
|
|
656cfa50c6 | ||
|
|
f10f8e0610 | ||
|
|
7533599cac | ||
|
|
f34de3acd9 | ||
|
|
4a5501f7a3 | ||
|
|
f52fd2ff48 | ||
|
|
628a6b72a2 | ||
|
|
3642763301 | ||
|
|
11c6fa3e45 | ||
|
|
d85210372d | ||
|
|
62e02ae956 | ||
|
|
799b46c68e | ||
|
|
6479841dee | ||
|
|
f1622e1a15 | ||
|
|
f490ac6f8d | ||
|
|
c342fbbe75 | ||
|
|
dde085b54e | ||
|
|
f8f126e8de | ||
|
|
71f92c985f | ||
|
|
3f2b5be4aa | ||
|
|
07712e7ac3 | ||
|
|
2d0310f675 | ||
|
|
4ef99958b6 | ||
|
|
ec677c781e | ||
|
|
2822dcc99a | ||
|
|
46e3d85b5f | ||
|
|
024271ec54 | ||
|
|
da9fd4c36d | ||
|
|
5a291b4a5f | ||
|
|
3f002ce2ee | ||
|
|
78a242faff | ||
|
|
be229a0c04 | ||
|
|
9a36cd4478 | ||
|
|
732b6b50b0 | ||
|
|
ade7a95f3c | ||
|
|
322975528e | ||
|
|
2a76f5a0bc | ||
|
|
2a7eaefac5 | ||
|
|
68784f3e4d | ||
|
|
c29477ed55 | ||
|
|
88a6442539 | ||
|
|
24363d55a4 | ||
|
|
7fd4c7504d | ||
|
|
efb4dcf236 | ||
|
|
53c2af0bc0 | ||
|
|
74d302639a | ||
|
|
0a2e4e7c14 | ||
|
|
7d399b436b | ||
|
|
f02098f697 | ||
|
|
fbb3b47b7d | ||
|
|
508d8fe357 | ||
|
|
7258ed9ea7 | ||
|
|
6ae5d244f1 | ||
|
|
7213348c12 | ||
|
|
93092db49f | ||
|
|
b8df51eb02 | ||
|
|
6cdc2ff526 | ||
|
|
b1303ce7a5 | ||
|
|
ac5bb1d684 | ||
|
|
ad8cf74bba | ||
|
|
4e6688e4e0 | ||
|
|
15859e76b6 | ||
|
|
eb89bd71e1 | ||
|
|
d8a88f94d7 | ||
|
|
fa7761b5a3 | ||
|
|
e769c9602b | ||
|
|
2078d38092 | ||
|
|
97834086d0 | ||
|
|
8fe0a53ba1 | ||
|
|
20fb9c3067 | ||
|
|
1573141f2c | ||
|
|
dba711c9ef | ||
|
|
be3faa0aef | ||
|
|
d93511eca4 | ||
|
|
1d75e8519d | ||
|
|
02460c0642 | ||
|
|
a43ad7aa06 | ||
|
|
0041be9892 | ||
|
|
7ebab0298b | ||
|
|
079b963ae3 | ||
|
|
fb6fcd6bd3 | ||
|
|
59950b2ba0 | ||
|
|
1f23257917 | ||
|
|
4e1db01f44 | ||
|
|
38769c3b55 | ||
|
|
a4ef09779d | ||
|
|
4821dd6052 | ||
|
|
f33c886d6a | ||
|
|
b5b8c2be60 | ||
|
|
4a7485c58e | ||
|
|
c097d20748 | ||
|
|
eeeae7d0e2 | ||
|
|
dd3e169946 | ||
|
|
b895a58194 | ||
|
|
2b42b9fafa | ||
|
|
c2125b07ca | ||
|
|
eb342bf69b | ||
|
|
a7eb66a6c9 | ||
|
|
e24fd9bdbb | ||
|
|
34d7c6fed2 | ||
|
|
a0f88031c1 | ||
|
|
889ec13dbf | ||
|
|
4aa5a8c48b | ||
|
|
c6a8ec257c | ||
|
|
0ec20b8fa0 | ||
|
|
f3285cf4e6 | ||
|
|
d7786cd449 | ||
|
|
777a9d9047 | ||
|
|
0dd7b0ed03 | ||
|
|
b661e8cc92 | ||
|
|
1412fb5d09 | ||
|
|
ec6a40e381 | ||
|
|
ca5075d50c | ||
|
|
fa1bf89e70 | ||
|
|
729533b3c1 | ||
|
|
6ffd26fd7f | ||
|
|
48da6cb642 | ||
|
|
0055bbb39d | ||
|
|
3349df2183 | ||
|
|
6c4800a6f0 | ||
|
|
ca40204d43 | ||
|
|
cd28be4b01 | ||
|
|
af752ea761 | ||
|
|
f2b72830b4 | ||
|
|
f5cf7abd4f | ||
|
|
29695d1d7e | ||
|
|
1510f7a135 | ||
|
|
4b5a691713 | ||
|
|
e9058ed67e | ||
|
|
fc728ec018 | ||
|
|
7361df8697 | ||
|
|
14e36aa699 |
BIN
dist/img/classic10.png
vendored
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
dist/img/classic11.png
vendored
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
dist/img/classic12.png
vendored
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
dist/img/classic13.png
vendored
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
dist/img/classic14.png
vendored
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
dist/img/classic15.png
vendored
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
dist/img/classic8.png
vendored
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
dist/img/classic9.png
vendored
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
dist/img/dark5.png
vendored
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
dist/img/dark6.png
vendored
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
dist/img/dark7.png
vendored
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
2
dist/js/app.js
vendored
69
dist/js/chunk-1f155ff8.js
vendored
Normal file
69
dist/js/chunk-4cff5316.js
vendored
@@ -9,7 +9,7 @@
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}</script><link href="dist/css/chunk-vendors.css?9c8ee1f3de5ddc8a450e" rel="stylesheet"><link href="dist/css/app.css?9c8ee1f3de5ddc8a450e" 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?1cdf74fc7be543987c91" rel="stylesheet"><link href="dist/css/app.css?1cdf74fc7be543987c91" 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?9c8ee1f3de5ddc8a450e"></script><script src="dist/js/app.js?9c8ee1f3de5ddc8a450e"></script></body></html>
|
||||
}</script><script src="dist/js/chunk-vendors.js?1cdf74fc7be543987c91"></script><script src="dist/js/app.js?1cdf74fc7be543987c91"></script></body></html>
|
||||
21
simple-mind-map/bin/createPluginsTypeFiles.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { exec } = require('child_process')
|
||||
const fs = require('fs')
|
||||
|
||||
const base = './src/plugins/'
|
||||
const list = fs.readdirSync(base)
|
||||
const files = []
|
||||
list.forEach(item => {
|
||||
const stat = fs.statSync(base + item)
|
||||
if (stat.isFile()) {
|
||||
files.push(item)
|
||||
}
|
||||
})
|
||||
const str = files
|
||||
.map(item => {
|
||||
return base + item
|
||||
})
|
||||
.join(' ')
|
||||
|
||||
exec(
|
||||
`tsc ${str} --declaration --allowJs --emitDeclarationOnly --outDir types/src/ --target es2017 --skipLibCheck `
|
||||
)
|
||||
@@ -18,20 +18,19 @@ import Formula from './src/plugins/Formula.js'
|
||||
import RainbowLines from './src/plugins/RainbowLines.js'
|
||||
import Demonstrate from './src/plugins/Demonstrate.js'
|
||||
import OuterFrame from './src/plugins/OuterFrame.js'
|
||||
import MindMapLayoutPro from './src/plugins/MindMapLayoutPro.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
import * as constants from './src/constants/constant.js'
|
||||
import themes from './src/themes/index.js'
|
||||
import * as defaultTheme from './src/themes/default.js'
|
||||
import * as defaultTheme from './src/theme/default.js'
|
||||
|
||||
MindMap.xmind = xmind
|
||||
MindMap.markdown = markdown
|
||||
MindMap.iconList = icons.nodeIconList
|
||||
MindMap.constants = constants
|
||||
MindMap.themes = themes
|
||||
MindMap.defaultTheme = defaultTheme
|
||||
MindMap.version = '0.11.2'
|
||||
MindMap.version = '0.13.0'
|
||||
|
||||
MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Watermark)
|
||||
@@ -52,5 +51,6 @@ MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(RainbowLines)
|
||||
.usePlugin(Demonstrate)
|
||||
.usePlugin(OuterFrame)
|
||||
.usePlugin(MindMapLayoutPro)
|
||||
|
||||
export default MindMap
|
||||
|
||||
@@ -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'
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from './src/utils'
|
||||
import defaultTheme, {
|
||||
checkIsNodeSizeIndependenceConfig
|
||||
} from './src/themes/default'
|
||||
} from './src/theme/default'
|
||||
import { defaultOpt } from './src/constants/defaultOptions'
|
||||
|
||||
// 思维导图
|
||||
@@ -52,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()
|
||||
@@ -98,11 +118,16 @@ class MindMap {
|
||||
this.initPlugin(plugin)
|
||||
})
|
||||
|
||||
// 添加必要的css样式
|
||||
this.addCss()
|
||||
|
||||
// 初始渲染
|
||||
this.render(this.opt.fit ? () => this.view.fit() : () => {})
|
||||
setTimeout(() => {
|
||||
if (this.opt.data) this.command.addHistory()
|
||||
}, 0)
|
||||
|
||||
// 将初始数据添加到历史记录堆栈中
|
||||
if (this.opt.addHistoryOnInit && this.opt.data) {
|
||||
this.command.addHistory()
|
||||
}
|
||||
}
|
||||
|
||||
// 配置参数处理
|
||||
@@ -170,17 +195,46 @@ class MindMap {
|
||||
this.otherDraw.clear()
|
||||
}
|
||||
|
||||
// 追加必要的css样式
|
||||
// 该样式在实例化时会动态添加到页面,同时导出为svg时也会添加到svg源码中
|
||||
appendCss(key, str) {
|
||||
this.cssTextMap[key] = str
|
||||
this.removeCss()
|
||||
this.addCss()
|
||||
}
|
||||
|
||||
// 移除追加的css样式
|
||||
removeAppendCss(key) {
|
||||
if (this.cssTextMap[key]) {
|
||||
delete this.cssTextMap[key]
|
||||
this.removeCss()
|
||||
this.addCss()
|
||||
}
|
||||
}
|
||||
|
||||
// 拼接必要的css样式
|
||||
joinCss() {
|
||||
return (
|
||||
cssContent +
|
||||
Object.keys(this.cssTextMap)
|
||||
.map(key => {
|
||||
return this.cssTextMap[key]
|
||||
})
|
||||
.join('\n')
|
||||
)
|
||||
}
|
||||
|
||||
// 添加必要的css样式到页面
|
||||
addCss() {
|
||||
this.cssEl = document.createElement('style')
|
||||
this.cssEl.type = 'text/css'
|
||||
this.cssEl.innerHTML = cssContent
|
||||
this.cssEl.innerHTML = this.joinCss()
|
||||
document.head.appendChild(this.cssEl)
|
||||
}
|
||||
|
||||
// 移除css
|
||||
removeCss() {
|
||||
document.head.removeChild(this.cssEl)
|
||||
if (this.cssEl) document.head.removeChild(this.cssEl)
|
||||
}
|
||||
|
||||
// 渲染,部分渲染
|
||||
@@ -254,7 +308,10 @@ class MindMap {
|
||||
// 设置主题
|
||||
initTheme() {
|
||||
// 合并主题配置
|
||||
this.themeConfig = mergeTheme(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)
|
||||
}
|
||||
@@ -281,7 +338,7 @@ class MindMap {
|
||||
this.opt.themeConfig = config
|
||||
if (!notRender) {
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(changedConfig)
|
||||
const res = checkIsNodeSizeIndependenceConfig(changedConfig)
|
||||
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
}
|
||||
@@ -304,8 +361,11 @@ class MindMap {
|
||||
// 更新配置
|
||||
updateConfig(opt = {}) {
|
||||
this.emit('before_update_config', this.opt)
|
||||
const lastOpt = {
|
||||
...this.opt
|
||||
}
|
||||
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
|
||||
this.emit('after_update_config', this.opt)
|
||||
this.emit('after_update_config', this.opt, lastOpt)
|
||||
}
|
||||
|
||||
// 获取当前布局结构
|
||||
@@ -335,14 +395,17 @@ class MindMap {
|
||||
|
||||
// 更新画布数据,如果新的数据是在当前画布节点数据基础上增删改查后形成的,那么可以使用该方法来更新画布数据
|
||||
updateData(data) {
|
||||
this.emit('before_update_data', data)
|
||||
this.renderer.setData(data)
|
||||
this.render()
|
||||
this.command.addHistory()
|
||||
this.emit('update_data', data)
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,纯节点数据
|
||||
setData(data) {
|
||||
data = this.handleData(data)
|
||||
this.emit('before_set_data', data)
|
||||
this.opt.data = data
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
@@ -421,11 +484,20 @@ class MindMap {
|
||||
}
|
||||
const isReadonly = mode === CONSTANTS.MODE.READONLY
|
||||
if (isReadonly === this.opt.readonly) return
|
||||
this.opt.readonly = isReadonly
|
||||
if (this.opt.readonly) {
|
||||
if (isReadonly) {
|
||||
// 如果处于编辑态,要隐藏所有的编辑框
|
||||
if (this.renderer.textEdit.isShowTextEdit()) {
|
||||
this.renderer.textEdit.hideEditTextBox()
|
||||
this.command.originAddHistory()
|
||||
}
|
||||
// 取消当前激活的元素
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
this.opt.readonly = isReadonly
|
||||
// 切换为编辑模式时,如果历史记录堆栈是空的,那么进行一次入栈操作
|
||||
if (!isReadonly && this.command.history.length <= 0) {
|
||||
this.command.originAddHistory()
|
||||
}
|
||||
this.emit('mode_change', mode)
|
||||
}
|
||||
|
||||
@@ -511,7 +583,7 @@ class MindMap {
|
||||
this.watermark.isInExport = false
|
||||
}
|
||||
// 添加必要的样式
|
||||
;[cssContent, ...cssTextList].forEach(s => {
|
||||
[this.joinCss(), ...cssTextList].forEach(s => {
|
||||
clone.add(SVG(`<style>${s}</style>`))
|
||||
})
|
||||
// 附加内容
|
||||
@@ -645,5 +717,11 @@ MindMap.defineTheme = (name, config = {}) => {
|
||||
}
|
||||
theme[name] = mergeTheme(defaultTheme, config)
|
||||
}
|
||||
// 移除主题
|
||||
MindMap.removeTheme = name => {
|
||||
if (theme[name]) {
|
||||
theme[name] = null
|
||||
}
|
||||
}
|
||||
|
||||
export default MindMap
|
||||
|
||||
18
simple-mind-map/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.10.6",
|
||||
"version": "0.12.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.10.6",
|
||||
"version": "0.12.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "3.2.0",
|
||||
@@ -15,7 +15,7 @@
|
||||
"katex": "^0.16.8",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"quill": "^2.0.2",
|
||||
"quill": "^2.0.3",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^7.5.9",
|
||||
@@ -1822,9 +1822,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/quill": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
|
||||
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
|
||||
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -3553,9 +3553,9 @@
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
|
||||
},
|
||||
"quill": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
|
||||
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
|
||||
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
|
||||
"requires": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.11.2",
|
||||
"version": "0.13.0",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -22,7 +22,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"format": "prettier --write .",
|
||||
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck",
|
||||
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck & node ./bin/createPluginsTypeFiles.js",
|
||||
"wsServe": "node ./bin/wsServer.mjs"
|
||||
},
|
||||
"module": "index.js",
|
||||
@@ -35,7 +35,7 @@
|
||||
"katex": "^0.16.8",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"quill": "^2.0.2",
|
||||
"quill": "^2.0.3",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^7.5.9",
|
||||
|
||||
@@ -1,173 +1,8 @@
|
||||
// 主题列表
|
||||
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',
|
||||
CHANGE_LAYOUT: 'changeLayout',
|
||||
SET_DATA: 'setData',
|
||||
TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode',
|
||||
MODE: {
|
||||
READONLY: 'readonly',
|
||||
EDIT: 'edit'
|
||||
@@ -239,6 +74,11 @@ export const CONSTANTS = {
|
||||
TAG_POSITION: {
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom'
|
||||
},
|
||||
EDIT_NODE_CLASS: {
|
||||
SMM_NODE_EDIT_WRAP: 'smm-node-edit-wrap',
|
||||
RICH_TEXT_EDIT_WRAP: 'ql-editor',
|
||||
ASSOCIATIVE_LINE_TEXT_EDIT_WRAP: 'associative-line-text-edit-warp'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +156,7 @@ export const nodeDataNoStylePropList = [
|
||||
'isActive',
|
||||
'generalization',
|
||||
'richText',
|
||||
'resetRichText',
|
||||
'resetRichText',// 重新创建富文本内容,去掉原有样式
|
||||
'uid',
|
||||
'activeStyle',
|
||||
'associativeLineTargets',
|
||||
@@ -330,7 +170,11 @@ export const nodeDataNoStylePropList = [
|
||||
'number',
|
||||
'range',
|
||||
'customLeft',
|
||||
'customTop'
|
||||
'customTop',
|
||||
'customTextWidth',
|
||||
'checkbox',
|
||||
'dir',
|
||||
'needUpdate'// 重新创建节点内容
|
||||
]
|
||||
|
||||
// 错误类型
|
||||
@@ -363,6 +207,10 @@ export const cssContent = `
|
||||
opacity: 1;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.smm-text-node-wrap, .smm-expand-btn-text {
|
||||
user-select: none;
|
||||
}
|
||||
`
|
||||
|
||||
// html自闭合标签列表
|
||||
@@ -375,3 +223,16 @@ export const selfCloseTagList = [
|
||||
'meta',
|
||||
'area'
|
||||
]
|
||||
|
||||
// 非富文本模式下的节点文本行高
|
||||
export const noneRichTextNodeLineHeight = 1.2
|
||||
|
||||
// 富文本支持的样式列表
|
||||
export const richTextSupportStyleList = [
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
]
|
||||
|
||||
@@ -7,6 +7,8 @@ export const defaultOpt = {
|
||||
el: null,
|
||||
// 思维导图回显数据
|
||||
data: null,
|
||||
// 要恢复的视图数据,一般通过mindMap.view.getTransformData()方法获取
|
||||
viewData: null,
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
@@ -82,6 +84,9 @@ export const defaultOpt = {
|
||||
isShowExpandNum: true,
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 自定义判断是否响应快捷键事件,优先级比enableShortcutOnlyWhenMouseInSvg选项高
|
||||
// 可以传递一个函数,接收事件对象e为参数,需要返回true或false,返回true代表允许响应快捷键事件,反之不允许,库默认当事件目标为body,或为文本编辑框元素(普通文本编辑框、富文本编辑框、关联线文本编辑框)时响应快捷键,其他不响应
|
||||
customCheckEnableShortcut: null,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 节点文本编辑框的z-index
|
||||
@@ -128,6 +133,8 @@ export const defaultOpt = {
|
||||
// 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式
|
||||
// 开启该特性后,需要给你的输入框绑定keydown事件,并禁止冒泡
|
||||
enableAutoEnterTextEditWhenKeydown: false,
|
||||
// 当enableAutoEnterTextEditWhenKeydown选项开启时生效,当通过按键进入文本编辑时是否自动清空原有文本
|
||||
autoEmptyTextWhenKeydownEnterEdit: false,
|
||||
// 自定义对剪贴板文本的处理。当按ctrl+v粘贴时会读取用户剪贴板中的文本和图片,默认只会判断文本是否是普通文本和simple-mind-map格式的节点数据,如果你想处理其他思维导图的数据,比如processon、zhixi等,那么可以传递一个函数,接受当前剪贴板中的文本为参数,返回处理后的数据,可以返回两种类型:
|
||||
/*
|
||||
1.返回一个纯文本,那么会直接以该文本创建一个子节点
|
||||
@@ -224,7 +231,7 @@ export const defaultOpt = {
|
||||
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
|
||||
// 该选项实际影响的是render.moveNodeToCenter方法,moveNodeToCenter方法本身也存在第二个参数resetScale来设置是否复位,如果resetScale参数没有传递,那么使用resetScaleOnMoveNodeToCenter配置,否则使用resetScale配置
|
||||
resetScaleOnMoveNodeToCenter: false,
|
||||
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
|
||||
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分。如果存在编号、任务勾选框内容,这里添加的前置内容会在这两者之后
|
||||
createNodePrefixContent: null,
|
||||
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
|
||||
createNodePostfixContent: null,
|
||||
@@ -245,8 +252,46 @@ export const defaultOpt = {
|
||||
emptyTextMeasureHeightText: 'abc123我和你',
|
||||
// 是否在进行节点文本编辑时实时更新节点大小和节点位置,开启后当节点数量比较多时可能会造成卡顿
|
||||
openRealtimeRenderOnNodeTextEdit: false,
|
||||
// 默认会给容器元素el绑定mousedown事件,并且会阻止其默认事件,这会带来一定问题,比如你聚焦在思维导图外的其他输入框,点击画布就不会触发其失焦,可以通过该选项关闭阻止。关闭后也会带来一定问题,比如鼠标框选节点时可能会选中节点文字,看你如何取舍
|
||||
mousedownEventPreventDefault: true,
|
||||
// 默认会给容器元素el绑定mousedown事件,可通过该选项设置是否阻止其默认事件
|
||||
// 如果设置为true,会带来一定问题,比如你聚焦在思维导图外的其他输入框,点击画布就不会触发其失焦
|
||||
mousedownEventPreventDefault: false,
|
||||
// 在激活上粘贴用户剪贴板中的数据时,如果同时存在文本和图片,那么只粘贴文本,忽略图片
|
||||
onlyPasteTextWhenHasImgAndText: true,
|
||||
// 是否允许拖拽调整节点的宽度,实际上压缩的是节点里面文本内容的宽度,当节点文本内容宽度压缩到最小时无法继续压缩。如果节点存在图片,那么最小值以图片宽度和文本内容最小宽度的最大值为准(目前该特性仅在两种情况下可用:1.开启了富文本模式,即注册了RichText插件;2.自定义节点内容)
|
||||
enableDragModifyNodeWidth: true,
|
||||
// 当允许拖拽调整节点的宽度时,可以通过该选项设置节点文本内容允许压缩的最小宽度
|
||||
minNodeTextModifyWidth: 20,
|
||||
// 同minNodeTextModifyWidth,最大值,传-1代表不限制
|
||||
maxNodeTextModifyWidth: -1,
|
||||
// 自定义处理节点的连线方法,可以传递一个函数,函数接收三个参数:node(节点实例)、line(节点的某条连线,@svgjs库的path对象), { width, color, dasharray },dasharray(该条连线的虚线样式,为none代表实线),你可以修改line对象来达到修改节点连线样式的效果,比如增加流动效果
|
||||
customHandleLine: null,
|
||||
// 实例化完后是否立刻进行一次历史数据入栈操作
|
||||
// 即调用mindMap.command.addHistory方法
|
||||
addHistoryOnInit: true,
|
||||
// 自定义节点备注图标
|
||||
noteIcon: {
|
||||
icon: '', // svg字符串,如果不是确定要使用svg自带的样式,否则请去除其中的fill等样式属性
|
||||
style: {
|
||||
// size: 20,// 图标大小,不手动设置则会使用主题的iconSize配置
|
||||
// color: '',// 图标颜色,不手动设置则会使用节点文本的颜色
|
||||
}
|
||||
},
|
||||
// 自定义节点超链接图标
|
||||
hyperlinkIcon: {
|
||||
icon: '', // svg字符串,如果不是确定要使用svg自带的样式,否则请去除其中的fill等样式属性
|
||||
style: {
|
||||
// size: 20,// 图标大小,不手动设置则会使用主题的iconSize配置
|
||||
// color: '',// 图标颜色,不手动设置则会使用节点文本的颜色
|
||||
}
|
||||
},
|
||||
// 自定义节点附件图标
|
||||
attachmentIcon: {
|
||||
icon: '', // svg字符串,如果不是确定要使用svg自带的样式,否则请去除其中的fill等样式属性
|
||||
style: {
|
||||
// size: 20,// 图标大小,不手动设置则会使用主题的iconSize配置
|
||||
// color: '',// 图标颜色,不手动设置则会使用节点文本的颜色
|
||||
}
|
||||
},
|
||||
|
||||
// 【Select插件】
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
@@ -348,6 +393,8 @@ export const defaultOpt = {
|
||||
},
|
||||
// 是否允许调整关联线两个端点的位置
|
||||
enableAdjustAssociativeLinePoints: true,
|
||||
// 关联线连接即将完成时执行,如果要阻止本次连接可以返回true,函数接收一个参数:node(目标节点实例)
|
||||
beforeAssociativeLineConnection: null,
|
||||
|
||||
// 【TouchEvent插件】
|
||||
// 禁止双指缩放,你仍旧可以使用api进行缩放
|
||||
@@ -414,9 +461,6 @@ export const defaultOpt = {
|
||||
transformRichTextOnEnterEdit: null,
|
||||
// 可以传递一个函数,即将结束富文本编辑前会执行该函数,函数接收richText实例,所以你可以在此时机更新quill文档数据
|
||||
beforeHideRichTextEdit: null,
|
||||
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
|
||||
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
|
||||
richTextEditFakeInPlace: false,
|
||||
|
||||
// 【OuterFrame】插件
|
||||
outerFramePaddingX: 10,
|
||||
@@ -428,5 +472,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
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
transformTreeDataToObject
|
||||
} from '../../utils'
|
||||
import { ERROR_TYPES } from '../../constants/constant'
|
||||
import pkg from '../../../package.json'
|
||||
|
||||
// 命令类
|
||||
class Command {
|
||||
@@ -18,6 +19,7 @@ class Command {
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
this.originAddHistory = this.addHistory.bind(this)
|
||||
this.addHistory = throttle(
|
||||
this.addHistory,
|
||||
this.mindMap.opt.addHistoryTime,
|
||||
@@ -60,6 +62,7 @@ class Command {
|
||||
this.commands[name].forEach(fn => {
|
||||
fn(...args)
|
||||
})
|
||||
this.mindMap.emit('afterExecCommand', name, ...args)
|
||||
if (
|
||||
['BACK', 'FORWARD', 'SET_NODE_ACTIVE', 'CLEAR_ACTIVE_NODE'].includes(
|
||||
name
|
||||
@@ -170,7 +173,9 @@ class Command {
|
||||
// 获取渲染树数据副本
|
||||
getCopyData() {
|
||||
if (!this.mindMap.renderer.renderTree) return null
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree, true)
|
||||
const res = copyRenderTree({}, this.mindMap.renderer.renderTree, true)
|
||||
res.smmVersion = pkg.version
|
||||
return res
|
||||
}
|
||||
|
||||
// 移除节点数据中的uid
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { keyMap } from './keyMap'
|
||||
import { CONSTANTS } from '../../constants/constant'
|
||||
|
||||
// 快捷按键、命令处理类
|
||||
export default class KeyCommand {
|
||||
@@ -15,6 +16,18 @@ export default class KeyCommand {
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
// 扩展按键映射
|
||||
extendKeyMap(key, code) {
|
||||
keyMap[key] = code
|
||||
}
|
||||
|
||||
// 从按键映射中删除某个键
|
||||
removeKeyMap(key) {
|
||||
if (typeof keyMap[key] !== 'undefined') {
|
||||
delete keyMap[key]
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停快捷键响应
|
||||
pause() {
|
||||
this.isPause = true
|
||||
@@ -73,10 +86,29 @@ export default class KeyCommand {
|
||||
window.removeEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
|
||||
// 根据事件目标判断是否响应快捷键事件
|
||||
defaultEnableCheck(e) {
|
||||
const target = e.target
|
||||
return (
|
||||
target === document.body ||
|
||||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP) ||
|
||||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP) ||
|
||||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.ASSOCIATIVE_LINE_TEXT_EDIT_WRAP)
|
||||
)
|
||||
}
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
const { enableShortcutOnlyWhenMouseInSvg, beforeShortcutRun } =
|
||||
this.mindMap.opt
|
||||
const {
|
||||
enableShortcutOnlyWhenMouseInSvg,
|
||||
beforeShortcutRun,
|
||||
customCheckEnableShortcut
|
||||
} = this.mindMap.opt
|
||||
const checkFn =
|
||||
typeof customCheckEnableShortcut === 'function'
|
||||
? customCheckEnableShortcut
|
||||
: this.defaultEnableCheck
|
||||
if (!checkFn(e)) return
|
||||
if (this.isPause || (enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -30,13 +30,15 @@ import {
|
||||
createSmmFormatData,
|
||||
checkSmmFormatData,
|
||||
checkIsNodeStyleDataKey,
|
||||
removeRichTextStyes,
|
||||
formatGetNodeGeneralization,
|
||||
sortNodeList,
|
||||
throttle
|
||||
throttle,
|
||||
debounce,
|
||||
checkClipboardReadEnable,
|
||||
isNodeNotNeedRenderData
|
||||
} from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../themes/default'
|
||||
import { lineStyleProps } from '../../theme/default'
|
||||
import { CONSTANTS, ERROR_TYPES } from '../../constants/constant'
|
||||
import { Polygon } from '@svgdotjs/svg.js'
|
||||
|
||||
@@ -92,17 +94,11 @@ class Render {
|
||||
// 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改
|
||||
this.textEdit = new TextEdit(this)
|
||||
// 当前复制的数据
|
||||
this.lastBeingCopyData = null
|
||||
this.beingCopyData = null
|
||||
this.beingPasteText = ''
|
||||
this.beingPasteImgSize = 0
|
||||
this.currentBeingPasteType = ''
|
||||
this.pasteData = { text: null, img: null }
|
||||
// 节点高亮框
|
||||
this.highlightBoxNode = null
|
||||
this.highlightBoxNodeStyle = null
|
||||
// 上一次节点激活数据
|
||||
this.lastActiveNode = null
|
||||
this.lastActiveNodeList = []
|
||||
// 布局
|
||||
this.setLayout()
|
||||
@@ -116,24 +112,26 @@ class Render {
|
||||
|
||||
// 设置布局结构
|
||||
setLayout() {
|
||||
const { layout } = this.mindMap.opt
|
||||
this.layout = new (
|
||||
layouts[this.mindMap.opt.layout]
|
||||
? layouts[this.mindMap.opt.layout]
|
||||
layouts[layout]
|
||||
? layouts[layout]
|
||||
: layouts[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]
|
||||
)(this, this.mindMap.opt.layout)
|
||||
)(this, layout)
|
||||
}
|
||||
|
||||
// 重新设置思维导图数据
|
||||
setData(data) {
|
||||
if (this.mindMap.richText) {
|
||||
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
|
||||
} else {
|
||||
this.renderTree = data
|
||||
}
|
||||
this.renderTree = data || null
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
const {
|
||||
openPerformance,
|
||||
performanceConfig,
|
||||
openRealtimeRenderOnNodeTextEdit
|
||||
} = this.mindMap.opt
|
||||
// 画布点击事件清除当前激活节点列表
|
||||
this.mindMap.on('draw_click', e => {
|
||||
this.clearActiveNodeListOnDrawClick(e, 'click')
|
||||
@@ -148,34 +146,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()
|
||||
})
|
||||
})
|
||||
}
|
||||
// 处理非https下的复制黏贴问题
|
||||
// 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡
|
||||
// if (!navigator.clipboard) {
|
||||
// this.handlePaste = this.handlePaste.bind(this)
|
||||
// window.addEventListener('paste', this.handlePaste)
|
||||
// this.mindMap.on('beforeDestroy', () => {
|
||||
// window.removeEventListener('paste', this.handlePaste)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
// 性能模式,懒加载节点
|
||||
performanceMode() {
|
||||
const { openPerformance, performanceConfig } = this.mindMap.opt
|
||||
const onViewDataChange = throttle(() => {
|
||||
if (this.root) {
|
||||
this.mindMap.emit('node_tree_render_start')
|
||||
@@ -188,24 +158,56 @@ class Render {
|
||||
)
|
||||
}
|
||||
}, performanceConfig.time)
|
||||
let lastOpen = false
|
||||
this.mindMap.on('before_update_config', opt => {
|
||||
lastOpen = opt.openPerformance
|
||||
})
|
||||
this.mindMap.on('after_update_config', opt => {
|
||||
if (opt.openPerformance && !lastOpen) {
|
||||
// 动态开启性能模式
|
||||
this.mindMap.on('view_data_change', onViewDataChange)
|
||||
if (openPerformance) {
|
||||
this.mindMap.on('view_data_change', onViewDataChange)
|
||||
}
|
||||
// 文本编辑时实时更新节点大小
|
||||
this.onNodeTextEditChange = debounce(this.onNodeTextEditChange, 100, this)
|
||||
if (openRealtimeRenderOnNodeTextEdit) {
|
||||
this.mindMap.on('node_text_edit_change', this.onNodeTextEditChange)
|
||||
}
|
||||
// 监听配置改变事件
|
||||
this.mindMap.on('after_update_config', (opt, lastOpt) => {
|
||||
// 更新openPerformance配置
|
||||
if (opt.openPerformance !== lastOpt.openPerformance) {
|
||||
this.mindMap[opt.openPerformance ? 'on' : 'off'](
|
||||
'view_data_change',
|
||||
onViewDataChange
|
||||
)
|
||||
this.forceLoadNode()
|
||||
}
|
||||
if (!opt.openPerformance && lastOpen) {
|
||||
// 动态关闭性能模式
|
||||
this.mindMap.off('view_data_change', onViewDataChange)
|
||||
this.forceLoadNode()
|
||||
// 更新openRealtimeRenderOnNodeTextEdit配置
|
||||
if (
|
||||
opt.openRealtimeRenderOnNodeTextEdit !==
|
||||
lastOpt.openRealtimeRenderOnNodeTextEdit
|
||||
) {
|
||||
this.mindMap[opt.openRealtimeRenderOnNodeTextEdit ? 'on' : 'off'](
|
||||
'node_text_edit_change',
|
||||
this.onNodeTextEditChange
|
||||
)
|
||||
}
|
||||
})
|
||||
if (!openPerformance) return
|
||||
this.mindMap.on('view_data_change', onViewDataChange)
|
||||
// 处理非https下的复制黏贴问题
|
||||
// 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡
|
||||
// if (!checkClipboardReadEnable()) {
|
||||
// this.handlePaste = this.handlePaste.bind(this)
|
||||
// window.addEventListener('paste', this.handlePaste)
|
||||
// this.mindMap.on('beforeDestroy', () => {
|
||||
// window.removeEventListener('paste', this.handlePaste)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
// 监听文本编辑事件,实时更新节点大小
|
||||
onNodeTextEditChange({ node, text }) {
|
||||
node._textData = node.createTextNode(text)
|
||||
const { width, height } = node.getNodeRect()
|
||||
node.width = width
|
||||
node.height = height
|
||||
node.layout()
|
||||
this.mindMap.render(() => {
|
||||
this.textEdit.updateTextEditNode()
|
||||
})
|
||||
}
|
||||
|
||||
// 强制渲染节点,不考虑是否在画布可视区域内
|
||||
@@ -425,7 +427,7 @@ class Render {
|
||||
})
|
||||
// 粘贴节点
|
||||
this.mindMap.keyCommand.addShortcut('Control+v', () => {
|
||||
if (navigator.clipboard) this.paste()
|
||||
this.paste()
|
||||
})
|
||||
// 根节点居中显示
|
||||
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
|
||||
@@ -435,13 +437,11 @@ class Render {
|
||||
|
||||
// 派发节点激活事件
|
||||
emitNodeActiveEvent(node = null, activeNodeList = [...this.activeNodeList]) {
|
||||
let isChange = false
|
||||
isChange = this.lastActiveNode !== node
|
||||
if (!isChange) {
|
||||
isChange = !checkNodeListIsEqual(this.lastActiveNodeList, activeNodeList)
|
||||
}
|
||||
const isChange = !checkNodeListIsEqual(
|
||||
this.lastActiveNodeList,
|
||||
activeNodeList
|
||||
)
|
||||
if (!isChange) return
|
||||
this.lastActiveNode = node
|
||||
this.lastActiveNodeList = [...activeNodeList]
|
||||
this.mindMap.batchExecution.push('emitNodeActiveEvent', () => {
|
||||
this.mindMap.emit('node_active', node, activeNodeList)
|
||||
@@ -547,13 +547,6 @@ class Render {
|
||||
if (this.reRender) {
|
||||
this.reRender = false
|
||||
}
|
||||
// 触发一次保存,因为修改了渲染树的数据
|
||||
if (
|
||||
this.mindMap.richText &&
|
||||
[CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)
|
||||
) {
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
}
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
})
|
||||
@@ -561,13 +554,14 @@ class Render {
|
||||
this.emitNodeActiveEvent()
|
||||
}
|
||||
|
||||
// 给当前被收起来的节点数据添加文本复位标志
|
||||
// 给当前被收起来的节点数据添加更新标志
|
||||
resetUnExpandNodeStyle() {
|
||||
if (!this.renderTree) return
|
||||
walk(this.renderTree, null, node => {
|
||||
if (!node.data.expand) {
|
||||
walk(node, null, node2 => {
|
||||
node2.data.resetRichText = true
|
||||
// 主要是触发数据新旧对比,不一样则会重新创建节点
|
||||
node2.data['needUpdate'] = true
|
||||
})
|
||||
return true
|
||||
}
|
||||
@@ -740,7 +734,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
|
||||
@@ -748,17 +742,18 @@ class Render {
|
||||
const params = {
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
isActive: focusNewNode // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态
|
||||
}
|
||||
if (isRichText) params.resetRichText = true
|
||||
// 动态指定的子节点数据也需要添加相关属性
|
||||
appointChildren = addDataToAppointNodes(appointChildren, {
|
||||
...params
|
||||
})
|
||||
appointChildren = addDataToAppointNodes(appointChildren, params)
|
||||
const alreadyIsRichText = appointData && appointData.richText
|
||||
let createNewId = false
|
||||
list.forEach(node => {
|
||||
if (node.isGeneralization || node.isRoot) {
|
||||
return
|
||||
}
|
||||
appointChildren = simpleDeepClone(appointChildren)
|
||||
const parent = node.parent
|
||||
const isOneLayer = node.layerIndex === 1
|
||||
// 新插入节点的默认文本
|
||||
@@ -767,6 +762,10 @@ class Render {
|
||||
: defaultInsertBelowSecondLevelNodeText
|
||||
// 计算插入位置
|
||||
const index = getNodeDataIndex(node)
|
||||
// 如果指定的数据就是富文本格式,那么不需要重新创建
|
||||
if (alreadyIsRichText && params.resetRichText) {
|
||||
delete params.resetRichText
|
||||
}
|
||||
const newNodeData = {
|
||||
inserting,
|
||||
data: {
|
||||
@@ -775,8 +774,9 @@ class Render {
|
||||
uid: createUid(),
|
||||
...(appointData || {})
|
||||
},
|
||||
children: [...createUidForAppointNodes(appointChildren)]
|
||||
children: [...createUidForAppointNodes(appointChildren, createNewId)]
|
||||
}
|
||||
createNewId = true
|
||||
parent.nodeData.children.splice(index + 1, 0, newNodeData)
|
||||
})
|
||||
// 如果同时对多个节点插入子节点,需要清除原来激活的节点
|
||||
@@ -795,23 +795,26 @@ 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 = true
|
||||
nodeList = addDataToAppointNodes(nodeList, params)
|
||||
let createNewId = false
|
||||
list.forEach(node => {
|
||||
if (node.isGeneralization || node.isRoot) {
|
||||
return
|
||||
}
|
||||
nodeList = simpleDeepClone(nodeList)
|
||||
const parent = node.parent
|
||||
// 计算插入位置
|
||||
const index = getNodeDataIndex(node)
|
||||
const newNodeList = createUidForAppointNodes(simpleDeepClone(nodeList))
|
||||
const newNodeList = createUidForAppointNodes(nodeList, createNewId)
|
||||
createNewId = true
|
||||
parent.nodeData.children.splice(index + 1, 0, ...newNodeList)
|
||||
})
|
||||
if (focusNewNode) {
|
||||
@@ -838,7 +841,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
|
||||
@@ -846,23 +849,28 @@ class Render {
|
||||
const params = {
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
isActive: focusNewNode
|
||||
}
|
||||
if (isRichText) params.resetRichText = true
|
||||
// 动态指定的子节点数据也需要添加相关属性
|
||||
appointChildren = addDataToAppointNodes(appointChildren, {
|
||||
...params
|
||||
})
|
||||
appointChildren = addDataToAppointNodes(appointChildren, params)
|
||||
const alreadyIsRichText = appointData && appointData.richText
|
||||
let createNewId = false
|
||||
list.forEach(node => {
|
||||
if (node.isGeneralization) {
|
||||
return
|
||||
}
|
||||
appointChildren = simpleDeepClone(appointChildren)
|
||||
if (!node.nodeData.children) {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
const text = node.isRoot
|
||||
? defaultInsertSecondLevelNodeText
|
||||
: defaultInsertBelowSecondLevelNodeText
|
||||
// 如果指定的数据就是富文本格式,那么不需要重新创建
|
||||
if (alreadyIsRichText && params.resetRichText) {
|
||||
delete params.resetRichText
|
||||
}
|
||||
const newNode = {
|
||||
inserting,
|
||||
data: {
|
||||
@@ -871,8 +879,9 @@ class Render {
|
||||
...params,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: [...createUidForAppointNodes(appointChildren)]
|
||||
children: [...createUidForAppointNodes(appointChildren, createNewId)]
|
||||
}
|
||||
createNewId = true
|
||||
node.nodeData.children.push(newNode)
|
||||
// 插入子节点时自动展开子节点
|
||||
node.setData({
|
||||
@@ -895,23 +904,27 @@ 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 = true
|
||||
childList = addDataToAppointNodes(childList, params)
|
||||
let createNewId = false
|
||||
list.forEach(node => {
|
||||
if (node.isGeneralization) {
|
||||
return
|
||||
}
|
||||
childList = simpleDeepClone(childList)
|
||||
if (!node.nodeData.children) {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
childList = createUidForAppointNodes(childList)
|
||||
childList = createUidForAppointNodes(childList, createNewId)
|
||||
// 第一个引用不需要重新创建uid,后面的需要重新创建,否则id会重复
|
||||
createNewId = true
|
||||
node.nodeData.children.push(...childList)
|
||||
// 插入子节点时自动展开子节点
|
||||
node.setData({
|
||||
@@ -937,7 +950,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
|
||||
@@ -945,9 +958,10 @@ class Render {
|
||||
const params = {
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
isActive: focusNewNode
|
||||
}
|
||||
if (isRichText) params.resetRichText = true
|
||||
const alreadyIsRichText = appointData && appointData.richText
|
||||
list.forEach(node => {
|
||||
if (node.isGeneralization || node.isRoot) {
|
||||
return
|
||||
@@ -956,6 +970,10 @@ class Render {
|
||||
node.layerIndex === 1
|
||||
? defaultInsertSecondLevelNodeText
|
||||
: defaultInsertBelowSecondLevelNodeText
|
||||
// 如果指定的数据就是富文本格式,那么不需要重新创建
|
||||
if (alreadyIsRichText && params.resetRichText) {
|
||||
delete params.resetRichText
|
||||
}
|
||||
const newNode = {
|
||||
inserting,
|
||||
data: {
|
||||
@@ -966,9 +984,6 @@ class Render {
|
||||
},
|
||||
children: [node.nodeData]
|
||||
}
|
||||
node.setData({
|
||||
resetRichText: true
|
||||
})
|
||||
const parent = node.parent
|
||||
// 获取当前节点所在位置
|
||||
const index = getNodeDataIndex(node)
|
||||
@@ -982,11 +997,12 @@ class Render {
|
||||
}
|
||||
|
||||
// 上移节点,多个节点只会操作第一个节点
|
||||
upNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
upNode(appointNode) {
|
||||
if (this.activeNodeList.length <= 0 && !appointNode) {
|
||||
return
|
||||
}
|
||||
let node = this.activeNodeList[0]
|
||||
const list = appointNode ? [appointNode] : this.activeNodeList
|
||||
const node = list[0]
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
@@ -1007,11 +1023,12 @@ class Render {
|
||||
}
|
||||
|
||||
// 下移节点,多个节点只会操作第一个节点
|
||||
downNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
downNode(appointNode) {
|
||||
if (this.activeNodeList.length <= 0 && !appointNode) {
|
||||
return
|
||||
}
|
||||
let node = this.activeNodeList[0]
|
||||
const list = appointNode ? [appointNode] : this.activeNodeList
|
||||
const node = list[0]
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
@@ -1042,7 +1059,6 @@ class Render {
|
||||
const index = getNodeIndexInNodeList(node, parent.children)
|
||||
const parentIndex = getNodeIndexInNodeList(parent, grandpa.children)
|
||||
// 节点数据
|
||||
this.checkNodeLayerChange(node, parent)
|
||||
parent.nodeData.children.splice(index, 1)
|
||||
grandpa.nodeData.children.splice(parentIndex + 1, 0, node.nodeData)
|
||||
this.mindMap.render()
|
||||
@@ -1057,10 +1073,10 @@ class Render {
|
||||
delete nodeData[key]
|
||||
}
|
||||
})
|
||||
// 如果是富文本,那么还要处理富文本内容
|
||||
if (hasCustomStyles && this.mindMap.richText) {
|
||||
// 如果是富文本,那么直接全部重新创建,因为有些样式是通过标签来渲染的
|
||||
if (this.hasRichTextPlugin()) {
|
||||
hasCustomStyles = true
|
||||
nodeData.resetRichText = true
|
||||
nodeData.text = removeRichTextStyes(nodeData.text)
|
||||
}
|
||||
return hasCustomStyles
|
||||
}
|
||||
@@ -1145,8 +1161,6 @@ class Render {
|
||||
text = clipboardData.getData('text')
|
||||
}
|
||||
})
|
||||
this.pasteData.img = img
|
||||
this.pasteData.text = text
|
||||
this.paste()
|
||||
}
|
||||
|
||||
@@ -1156,136 +1170,127 @@ class Render {
|
||||
errorHandler,
|
||||
handleIsSplitByWrapOnPasteCreateNewNode,
|
||||
handleNodePasteImg,
|
||||
disabledClipboard
|
||||
disabledClipboard,
|
||||
onlyPasteTextWhenHasImgAndText
|
||||
} = this.mindMap.opt
|
||||
// 读取剪贴板的文字和图片
|
||||
let text = ''
|
||||
let img = null
|
||||
if (!disabledClipboard) {
|
||||
// 如果支持剪贴板操作,那么以剪贴板数据为准
|
||||
if (!disabledClipboard && checkClipboardReadEnable()) {
|
||||
try {
|
||||
const res = navigator.clipboard
|
||||
? await getDataFromClipboard()
|
||||
: this.pasteData
|
||||
text = res.text || ''
|
||||
img = res.img || null
|
||||
const res = await getDataFromClipboard()
|
||||
let text = res.text || ''
|
||||
let img = res.img || null
|
||||
// 存在文本,则创建子节点
|
||||
if (text) {
|
||||
// 判断粘贴的是否是simple-mind-map的数据
|
||||
let smmData = null
|
||||
let useDefault = true
|
||||
// 用户自定义处理
|
||||
if (this.mindMap.opt.customHandleClipboardText) {
|
||||
try {
|
||||
const res = await this.mindMap.opt.customHandleClipboardText(text)
|
||||
if (!isUndef(res)) {
|
||||
useDefault = false
|
||||
const checkRes = checkSmmFormatData(res)
|
||||
if (checkRes.isSmm) {
|
||||
smmData = checkRes.data
|
||||
} else {
|
||||
text = checkRes.data
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(
|
||||
ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
// 默认处理
|
||||
if (useDefault) {
|
||||
const checkRes = checkSmmFormatData(text)
|
||||
if (checkRes.isSmm) {
|
||||
smmData = checkRes.data
|
||||
} else {
|
||||
text = checkRes.data
|
||||
}
|
||||
}
|
||||
if (smmData) {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
[],
|
||||
Array.isArray(smmData) ? smmData : [smmData]
|
||||
)
|
||||
} else {
|
||||
// 如果是富文本模式,那么需要转义特殊字符
|
||||
if (this.hasRichTextPlugin()) {
|
||||
text = htmlEscape(text)
|
||||
}
|
||||
const textArr = text
|
||||
.split(new RegExp('\r?\n|(?<!\n)\r', 'g'))
|
||||
.filter(item => {
|
||||
return !!item
|
||||
})
|
||||
// 判断是否需要根据换行自动分割节点
|
||||
if (textArr.length > 1 && handleIsSplitByWrapOnPasteCreateNewNode) {
|
||||
handleIsSplitByWrapOnPasteCreateNewNode()
|
||||
.then(() => {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
[],
|
||||
textArr.map(item => {
|
||||
return {
|
||||
data: {
|
||||
text: item
|
||||
},
|
||||
children: []
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// 存在图片,则添加到当前激活节点
|
||||
if (img && (!text || !onlyPasteTextWhenHasImgAndText)) {
|
||||
try {
|
||||
let imgData = null
|
||||
// 自定义图片处理函数
|
||||
if (
|
||||
handleNodePasteImg &&
|
||||
typeof handleNodePasteImg === 'function'
|
||||
) {
|
||||
imgData = await handleNodePasteImg(img)
|
||||
} else {
|
||||
imgData = await loadImage(img)
|
||||
}
|
||||
if (this.activeNodeList.length > 0) {
|
||||
this.activeNodeList.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
|
||||
url: imgData.url,
|
||||
title: '',
|
||||
width: imgData.size.width,
|
||||
height: imgData.size.height
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.READ_CLIPBOARD_ERROR, error)
|
||||
}
|
||||
}
|
||||
// 检查剪切板数据是否有变化
|
||||
// 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法
|
||||
const imgSize = img ? img.size : 0
|
||||
if (this.beingPasteText !== text || this.beingPasteImgSize !== imgSize) {
|
||||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CLIP_BOARD
|
||||
this.beingPasteText = text
|
||||
this.beingPasteImgSize = imgSize
|
||||
}
|
||||
// 检查要粘贴的节点数据是否有变化,节点优先级高于剪切板
|
||||
if (this.lastBeingCopyData !== this.beingCopyData) {
|
||||
this.lastBeingCopyData = this.beingCopyData
|
||||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CANVAS
|
||||
}
|
||||
// 粘贴剪切板的数据
|
||||
if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) {
|
||||
// 存在文本,则创建子节点
|
||||
if (text) {
|
||||
// 判断粘贴的是否是simple-mind-map的数据
|
||||
let smmData = null
|
||||
let useDefault = true
|
||||
// 用户自定义处理
|
||||
if (this.mindMap.opt.customHandleClipboardText) {
|
||||
try {
|
||||
const res = await this.mindMap.opt.customHandleClipboardText(text)
|
||||
if (!isUndef(res)) {
|
||||
useDefault = false
|
||||
const checkRes = checkSmmFormatData(res)
|
||||
if (checkRes.isSmm) {
|
||||
smmData = checkRes.data
|
||||
} else {
|
||||
text = checkRes.data
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR, error)
|
||||
}
|
||||
}
|
||||
// 默认处理
|
||||
if (useDefault) {
|
||||
const checkRes = checkSmmFormatData(text)
|
||||
if (checkRes.isSmm) {
|
||||
smmData = checkRes.data
|
||||
} else {
|
||||
text = checkRes.data
|
||||
}
|
||||
}
|
||||
if (smmData) {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
[],
|
||||
Array.isArray(smmData) ? smmData : [smmData]
|
||||
)
|
||||
} else {
|
||||
text = htmlEscape(text)
|
||||
const textArr = text
|
||||
.split(new RegExp('\r?\n|(?<!\n)\r', 'g'))
|
||||
.filter(item => {
|
||||
return !!item
|
||||
})
|
||||
// 判断是否需要根据换行自动分割节点
|
||||
if (textArr.length > 1 && handleIsSplitByWrapOnPasteCreateNewNode) {
|
||||
handleIsSplitByWrapOnPasteCreateNewNode()
|
||||
.then(() => {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
[],
|
||||
textArr.map(item => {
|
||||
return {
|
||||
data: {
|
||||
text: item
|
||||
},
|
||||
children: []
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// 存在图片,则添加到当前激活节点
|
||||
if (img) {
|
||||
try {
|
||||
let imgData = null
|
||||
// 自定义图片处理函数
|
||||
if (handleNodePasteImg && typeof handleNodePasteImg === 'function') {
|
||||
imgData = await handleNodePasteImg(img)
|
||||
} else {
|
||||
imgData = await loadImage(img)
|
||||
}
|
||||
if (this.activeNodeList.length > 0) {
|
||||
this.activeNodeList.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
|
||||
url: imgData.url,
|
||||
title: '',
|
||||
width: imgData.size.width,
|
||||
height: imgData.size.height
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 粘贴节点数据
|
||||
// 禁用剪贴板或不支持剪贴板时
|
||||
// 粘贴画布内的节点数据
|
||||
if (this.beingCopyData) {
|
||||
this.mindMap.execCommand('PASTE_NODE', this.beingCopyData)
|
||||
}
|
||||
@@ -1312,7 +1317,6 @@ class Render {
|
||||
nodeList.reverse()
|
||||
}
|
||||
nodeList.forEach(item => {
|
||||
this.checkNodeLayerChange(item, exist)
|
||||
// 移动节点
|
||||
let nodeParent = item.parent
|
||||
let nodeBorthers = nodeParent.children
|
||||
@@ -1339,25 +1343,6 @@ class Render {
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 如果是富文本模式,那么某些层级变化需要更新样式
|
||||
checkNodeLayerChange(node, toNode, toNodeIsParent = false) {
|
||||
if (this.mindMap.richText) {
|
||||
// 如果设置了自定义样式那么不需要更新
|
||||
if (this.mindMap.richText.checkNodeHasCustomRichTextStyle(node)) {
|
||||
return
|
||||
}
|
||||
const toIndex = toNodeIsParent ? toNode.layerIndex + 1 : toNode.layerIndex
|
||||
let nodeLayerChanged =
|
||||
(node.layerIndex === 1 && toIndex !== 1) ||
|
||||
(node.layerIndex !== 1 && toIndex === 1)
|
||||
if (nodeLayerChanged) {
|
||||
node.setData({
|
||||
resetRichText: true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除节点
|
||||
removeNode(appointNodes = []) {
|
||||
appointNodes = formatDataToArray(appointNodes)
|
||||
@@ -1541,7 +1526,6 @@ class Render {
|
||||
return !item.isRoot
|
||||
})
|
||||
nodeList.forEach(item => {
|
||||
this.checkNodeLayerChange(item, toNode, true)
|
||||
this.removeNodeFromActiveList(item)
|
||||
removeFromParentNodeData(item)
|
||||
toNode.setData({
|
||||
@@ -1556,60 +1540,24 @@ class Render {
|
||||
// 粘贴节点到节点
|
||||
pasteNode(data) {
|
||||
data = formatDataToArray(data)
|
||||
if (this.activeNodeList.length <= 0 || data.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.activeNodeList.forEach(node => {
|
||||
node.setData({
|
||||
expand: true
|
||||
})
|
||||
node.nodeData.children.push(
|
||||
...data.map(item => {
|
||||
const newData = simpleDeepClone(item)
|
||||
createUidForAppointNodes([newData], true, node => {
|
||||
// 可能跨层级复制,那么富文本样式需要更新
|
||||
if (this.mindMap.richText) {
|
||||
// 如果设置了自定义样式那么不需要更新
|
||||
if (
|
||||
this.mindMap.richText.checkNodeHasCustomRichTextStyle(node.data)
|
||||
) {
|
||||
return
|
||||
}
|
||||
node.data.resetRichText = true
|
||||
}
|
||||
})
|
||||
return newData
|
||||
})
|
||||
)
|
||||
})
|
||||
this.mindMap.render()
|
||||
this.mindMap.execCommand('INSERT_MULTI_CHILD_NODE', [], data)
|
||||
}
|
||||
|
||||
// 设置节点样式
|
||||
setNodeStyle(node, prop, value) {
|
||||
let data = {
|
||||
const data = {
|
||||
[prop]: value
|
||||
}
|
||||
// 如果开启了富文本,则需要应用到富文本上
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.setNotActiveNodeStyle(node, {
|
||||
[prop]: value
|
||||
})
|
||||
}
|
||||
this.setNodeDataRender(node, data)
|
||||
// 更新了连线的样式
|
||||
if (lineStyleProps.includes(prop)) {
|
||||
;(node.parent || node).renderLine(true)
|
||||
(node.parent || node).renderLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置节点多个样式
|
||||
setNodeStyles(node, style) {
|
||||
let data = { ...style }
|
||||
// 如果开启了富文本,则需要应用到富文本上
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.setNotActiveNodeStyle(node, style)
|
||||
}
|
||||
const data = { ...style }
|
||||
this.setNodeDataRender(node, data)
|
||||
// 更新了连线的样式
|
||||
let props = Object.keys(style)
|
||||
@@ -1620,7 +1568,7 @@ class Render {
|
||||
}
|
||||
})
|
||||
if (hasLineStyleProps) {
|
||||
;(node.parent || node).renderLine(true)
|
||||
(node.parent || node).renderLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1802,7 +1750,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 => {
|
||||
@@ -1824,12 +1772,13 @@ 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
|
||||
)
|
||||
let needRender = false
|
||||
const alreadyIsRichText = data && data.richText
|
||||
list.forEach(item => {
|
||||
const newData = {
|
||||
inserting,
|
||||
@@ -1839,9 +1788,9 @@ class Render {
|
||||
range: item.range || null,
|
||||
uid: createUid(),
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
isActive: focusNewNode
|
||||
}
|
||||
if (isRichText && !alreadyIsRichText) newData.resetRichText = isRichText
|
||||
let generalization = item.node.getData('generalization')
|
||||
generalization = generalization
|
||||
? Array.isArray(generalization)
|
||||
@@ -1971,6 +1920,10 @@ class Render {
|
||||
// 设置节点数据,并判断是否渲染
|
||||
setNodeDataRender(node, data, notRender = false) {
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, data)
|
||||
if (isNodeNotNeedRenderData(data)) {
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
return
|
||||
}
|
||||
this.reRenderNodeCheckChange(node, notRender)
|
||||
}
|
||||
|
||||
@@ -2171,6 +2124,11 @@ class Render {
|
||||
if (!this.highlightBoxNode) return
|
||||
this.highlightBoxNode.remove()
|
||||
}
|
||||
|
||||
// 是否存在富文本插件
|
||||
hasRichTextPlugin() {
|
||||
return !!this.mindMap.richText
|
||||
}
|
||||
}
|
||||
|
||||
export default Render
|
||||
|
||||
@@ -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 {
|
||||
@@ -27,6 +33,7 @@ export default class TextEdit {
|
||||
this.hasBodyMousedown = false
|
||||
this.textNodePaddingX = 5
|
||||
this.textNodePaddingY = 3
|
||||
this.isNeedUpdateTextEditNode = false
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
@@ -85,13 +92,51 @@ export default class TextEdit {
|
||||
})
|
||||
})
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
// // 监听按键事件,判断是否自动进入文本编辑模式
|
||||
// 监听按键事件,判断是否自动进入文本编辑模式
|
||||
if (this.mindMap.opt.enableAutoEnterTextEditWhenKeydown) {
|
||||
window.addEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
// 正在编辑文本时,给节点添加了图标等其他内容时需要更新编辑框的位置
|
||||
this.mindMap.on('afterExecCommand', () => {
|
||||
if (!this.isShowTextEdit()) return
|
||||
this.isNeedUpdateTextEditNode = true
|
||||
})
|
||||
this.mindMap.on('node_tree_render_end', () => {
|
||||
if (!this.isShowTextEdit()) return
|
||||
if (this.isNeedUpdateTextEditNode) {
|
||||
this.isNeedUpdateTextEditNode = false
|
||||
this.updateTextEditNode()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -101,11 +146,15 @@ export default class TextEdit {
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
if (e.target !== document.body) return
|
||||
const activeNodeList = this.mindMap.renderer.activeNodeList
|
||||
if (activeNodeList.length <= 0 || activeNodeList.length > 1) return
|
||||
const node = activeNodeList[0]
|
||||
// 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式
|
||||
if (node && this.checkIsAutoEnterTextEditKey(e)) {
|
||||
// 忽略第一个键值,避免中文输入法时进入编辑会导致第一个键值变成字母的问题
|
||||
// 带来的问题是按的第一下纯粹是进入文本编辑,但没有变成输入
|
||||
e.preventDefault()
|
||||
this.show({
|
||||
node,
|
||||
e,
|
||||
@@ -128,7 +177,6 @@ export default class TextEdit {
|
||||
|
||||
// 注册临时快捷键
|
||||
registerTmpShortcut() {
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
@@ -145,7 +193,7 @@ export default class TextEdit {
|
||||
return this.showTextEdit
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
// 显示文本编辑框
|
||||
// isInserting:是否是刚创建的节点
|
||||
// isFromKeyDown:是否是在按键事件进入的编辑
|
||||
async show({
|
||||
@@ -158,7 +206,13 @@ export default class TextEdit {
|
||||
if (node.isUseCustomNodeContent()) {
|
||||
return
|
||||
}
|
||||
const { beforeTextEdit } = this.mindMap.opt
|
||||
// 如果有正在编辑中的节点,那么先结束它
|
||||
const currentEditNode = this.getCurrentEditNode()
|
||||
if (currentEditNode) {
|
||||
this.hideEditTextBox()
|
||||
}
|
||||
const { beforeTextEdit, openRealtimeRenderOnNodeTextEdit } =
|
||||
this.mindMap.opt
|
||||
if (typeof beforeTextEdit === 'function') {
|
||||
let isShow = false
|
||||
try {
|
||||
@@ -169,10 +223,18 @@ export default class TextEdit {
|
||||
}
|
||||
if (!isShow) return
|
||||
}
|
||||
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
|
||||
// 需要先显示,不然宽高获取到的可能是0
|
||||
if (openRealtimeRenderOnNodeTextEdit) {
|
||||
g.show()
|
||||
}
|
||||
const rect = g.node.getBoundingClientRect()
|
||||
// 如果开启了大小实时更新,那么直接隐藏节点原文本
|
||||
if (openRealtimeRenderOnNodeTextEdit) {
|
||||
g.hide()
|
||||
}
|
||||
const params = {
|
||||
node,
|
||||
rect,
|
||||
@@ -184,9 +246,25 @@ export default class TextEdit {
|
||||
this.mindMap.richText.showEditText(params)
|
||||
return
|
||||
}
|
||||
this.currentNode = node
|
||||
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 +286,37 @@ export default class TextEdit {
|
||||
// 显示文本编辑框
|
||||
showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
|
||||
if (this.showTextEdit) return
|
||||
const { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText } =
|
||||
this.mindMap.opt
|
||||
const {
|
||||
nodeTextEditZIndex,
|
||||
textAutoWrapWidth,
|
||||
selectTextOnEnterEditText,
|
||||
openRealtimeRenderOnNodeTextEdit,
|
||||
autoEmptyTextWhenKeydownEnterEdit
|
||||
} = this.mindMap.opt
|
||||
if (!isFromScale) {
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
}
|
||||
this.registerTmpShortcut()
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
||||
this.textEditNode.classList.add(
|
||||
CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP
|
||||
)
|
||||
this.textEditNode.style.cssText = `
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
${
|
||||
openRealtimeRenderOnNodeTextEdit
|
||||
? ''
|
||||
: `box-shadow: 0 0 20px rgba(0,0,0,.5);`
|
||||
}
|
||||
padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;
|
||||
margin-left: -${this.textNodePaddingX}px;
|
||||
margin-top: -${this.textNodePaddingY}px;
|
||||
outline: none;
|
||||
word-break: break-all;
|
||||
line-break: anywhere;
|
||||
`
|
||||
this.textEditNode.setAttribute('contenteditable', true)
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
e.stopPropagation()
|
||||
@@ -241,42 +341,47 @@ export default class TextEdit {
|
||||
} else {
|
||||
handleInputPasteText(e)
|
||||
}
|
||||
this.emitTextChangeEvent()
|
||||
})
|
||||
this.textEditNode.addEventListener('input', () => {
|
||||
this.mindMap.emit('node_text_edit_change', {
|
||||
node: this.currentNode,
|
||||
text: this.getEditText(),
|
||||
richText: false
|
||||
})
|
||||
this.emitTextChangeEvent()
|
||||
})
|
||||
const targetNode =
|
||||
this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
let fontSize = node.style.merge('fontSize')
|
||||
let textLines = (this.cacheEditingText || node.getData('text'))
|
||||
const scale = this.mindMap.view.scale
|
||||
const fontSize = node.style.merge('fontSize')
|
||||
const textLines = (this.cacheEditingText || node.getData('text'))
|
||||
.split(/\n/gim)
|
||||
.map(item => {
|
||||
return htmlEscape(item)
|
||||
})
|
||||
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||
const isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale)
|
||||
if (!openRealtimeRenderOnNodeTextEdit) {
|
||||
this.textEditNode.style.background = this.getBackground(node)
|
||||
}
|
||||
this.textEditNode.style.zIndex = nodeTextEditZIndex
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
if (isFromKeyDown && autoEmptyTextWhenKeydownEnterEdit) {
|
||||
this.textEditNode.innerHTML = ''
|
||||
} else {
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
}
|
||||
this.textEditNode.style.minWidth =
|
||||
rect.width + this.textNodePaddingX * 2 + 'px'
|
||||
this.textEditNode.style.minHeight =
|
||||
rect.height + this.textNodePaddingY * 2 + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 'px'
|
||||
this.textEditNode.style.left = Math.floor(rect.left) + 'px'
|
||||
this.textEditNode.style.top = Math.floor(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
|
||||
// 选中文本
|
||||
@@ -291,6 +396,15 @@ export default class TextEdit {
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 派发节点文本编辑事件
|
||||
emitTextChangeEvent() {
|
||||
this.mindMap.emit('node_text_edit_change', {
|
||||
node: this.currentNode,
|
||||
text: this.getEditText(),
|
||||
richText: false
|
||||
})
|
||||
}
|
||||
|
||||
// 更新文本编辑框的大小和位置
|
||||
updateTextEditNode() {
|
||||
if (this.mindMap.richText) {
|
||||
@@ -305,8 +419,29 @@ 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'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.left = Math.floor(rect.left) + 'px'
|
||||
this.textEditNode.style.top = Math.floor(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
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文本编辑元素
|
||||
@@ -333,16 +468,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 +478,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,
|
||||
|
||||
@@ -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,17 @@ class MindMapNode {
|
||||
proto[item] = nodeCooperateMethods[item]
|
||||
})
|
||||
}
|
||||
// 拖拽调整节点宽度
|
||||
Object.keys(nodeModifyWidthMethods).forEach(item => {
|
||||
proto[item] = nodeModifyWidthMethods[item]
|
||||
})
|
||||
proto.bindEvent = true
|
||||
}
|
||||
// 初始化
|
||||
this.getSize()
|
||||
// 初始需要计算一下概要节点的大小,否则计算布局时获取不到概要的大小
|
||||
this.updateGeneralization()
|
||||
this.initDragHandle()
|
||||
}
|
||||
|
||||
// 支持自定义位置
|
||||
@@ -197,15 +206,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 +257,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 +311,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 +331,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 +407,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 +421,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 +438,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 +491,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 +538,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 +650,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 +676,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 +795,7 @@ class MindMapNode {
|
||||
this.renderExpandBtn()
|
||||
}
|
||||
} else {
|
||||
let { isActive, expand } = this.getData()
|
||||
const { isActive, expand } = this.getData()
|
||||
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
|
||||
if (childrenLength <= 0) {
|
||||
this.removeExpandBtn()
|
||||
@@ -735,23 +806,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 +846,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 +870,7 @@ class MindMapNode {
|
||||
this.hideExpandBtn()
|
||||
}
|
||||
this.updateNodeActiveClass()
|
||||
this.updateDragHandle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,10 +994,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 +1019,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 +1037,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 +1076,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')
|
||||
}
|
||||
|
||||
// 连线
|
||||
@@ -1188,7 +1265,7 @@ class MindMapNode {
|
||||
|
||||
// 获取某个样式
|
||||
getStyle(prop, root) {
|
||||
let v = this.style.merge(prop, root)
|
||||
const v = this.style.merge(prop, root)
|
||||
return v === undefined ? '' : v
|
||||
}
|
||||
|
||||
@@ -1248,16 +1325,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 {
|
||||
@@ -1297,6 +1374,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
|
||||
|
||||
@@ -12,6 +12,7 @@ const backgroundStyleProps = [
|
||||
class Style {
|
||||
// 设置背景样式
|
||||
static setBackgroundStyle(el, themeConfig) {
|
||||
if (!el) return
|
||||
// 缓存容器元素原本的样式
|
||||
if (!Style.cacheStyle) {
|
||||
Style.cacheStyle = {}
|
||||
@@ -183,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
|
||||
@@ -191,14 +192,15 @@ class Style {
|
||||
}
|
||||
|
||||
// 生成内联样式
|
||||
createStyleText() {
|
||||
createStyleText(customStyle = {}) {
|
||||
const styles = {
|
||||
color: this.merge('color'),
|
||||
fontFamily: this.merge('fontFamily'),
|
||||
fontSize: this.merge('fontSize'),
|
||||
fontWeight: this.merge('fontWeight'),
|
||||
fontStyle: this.merge('fontStyle'),
|
||||
textDecoration: this.merge('textDecoration')
|
||||
textDecoration: this.merge('textDecoration'),
|
||||
...customStyle
|
||||
}
|
||||
return `
|
||||
color: ${styles.color};
|
||||
@@ -229,20 +231,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
|
||||
}
|
||||
|
||||
@@ -268,14 +270,18 @@ class Style {
|
||||
}
|
||||
|
||||
// 内置图标
|
||||
iconNode(node) {
|
||||
iconNode(node, color) {
|
||||
node.attr({
|
||||
fill: this.merge('color')
|
||||
fill: color || this.merge('color')
|
||||
})
|
||||
}
|
||||
|
||||
// 连线
|
||||
line(line, { width, color, dasharray } = {}, enableMarker, childNode) {
|
||||
const { customHandleLine } = this.ctx.mindMap.opt
|
||||
if (typeof customHandleLine === 'function') {
|
||||
customHandleLine(this.ctx, line, { width, color, dasharray })
|
||||
}
|
||||
line.stroke({ color, dasharray, width }).fill({ color: 'none' })
|
||||
// 可以显示箭头
|
||||
if (enableMarker) {
|
||||
@@ -338,7 +344,7 @@ class Style {
|
||||
node2.fill({ color: color })
|
||||
fillNode.fill({ color: fill })
|
||||
if (this.ctx.mindMap.opt.isShowExpandNum) {
|
||||
node.attr({ 'font-size': fontSize, 'font-color': fontColor })
|
||||
node.attr({ 'font-size': fontSize + 'px', 'font-color': fontColor })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,6 +359,17 @@ class Style {
|
||||
return res
|
||||
}
|
||||
|
||||
// 获取自定义的样式
|
||||
getCustomStyle() {
|
||||
const customStyle = {}
|
||||
Object.keys(this.ctx.getData()).forEach(item => {
|
||||
if (checkIsNodeStyleDataKey(item)) {
|
||||
customStyle[item] = this.ctx.getData(item)
|
||||
}
|
||||
})
|
||||
return customStyle
|
||||
}
|
||||
|
||||
// hover和激活节点
|
||||
hoverNode(node) {
|
||||
const hoverRectColor =
|
||||
|
||||
@@ -28,7 +28,7 @@ function createTextAvatar(item) {
|
||||
color: '#fff'
|
||||
})
|
||||
.css({
|
||||
'font-size': fontSize
|
||||
'font-size': fontSize + 'px'
|
||||
})
|
||||
.dx(-fontSize / 2)
|
||||
.dy((avatarSize - fontSize) / 2)
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import {
|
||||
measureText,
|
||||
resizeImgSize,
|
||||
removeHtmlStyle,
|
||||
addHtmlStyle,
|
||||
removeRichTextStyes,
|
||||
checkIsRichText,
|
||||
isUndef,
|
||||
createForeignObjectNode,
|
||||
addXmlns,
|
||||
generateColorByContent
|
||||
generateColorByContent,
|
||||
camelCaseToHyphen,
|
||||
getNodeRichTextStyles
|
||||
} 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 { 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,46 +124,37 @@ 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 g = new G()
|
||||
// 重新设置富文本节点内容
|
||||
let { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
|
||||
textAutoWrapWidth = hasCustomWidth ? this.customTextWidth : textAutoWrapWidth
|
||||
const g = new G()
|
||||
// 创建富文本结构,或复位富文本样式
|
||||
let recoverText = false
|
||||
if (this.getData('resetRichText')) {
|
||||
delete this.nodeData.data.resetRichText
|
||||
recoverText = true
|
||||
}
|
||||
if ([CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
|
||||
// 如果自定义过样式则不允许覆盖
|
||||
if (!this.hasCustomStyle()) {
|
||||
recoverText = true
|
||||
}
|
||||
}
|
||||
if (recoverText && !isUndef(text)) {
|
||||
// 判断节点内容是否是富文本
|
||||
let isRichText = checkIsRichText(text)
|
||||
// 样式字符串
|
||||
let style = this.style.createStyleText()
|
||||
if (isRichText) {
|
||||
// 如果是富文本那么线移除内联样式
|
||||
text = removeHtmlStyle(text)
|
||||
// 再添加新的内联样式
|
||||
let _text = text
|
||||
text = addHtmlStyle(text, 'span', style)
|
||||
// 给span添加样式没有成功,则尝试给strong标签添加样式
|
||||
if (text === _text) {
|
||||
text = addHtmlStyle(text, 'strong', style)
|
||||
}
|
||||
if (checkIsRichText(text)) {
|
||||
// 如果是富文本那么移除内联样式
|
||||
text = removeRichTextStyes(text)
|
||||
} else {
|
||||
// 非富文本
|
||||
text = `<p><span style="${style}">${text}</span></p>`
|
||||
// 非富文本则改为富文本结构
|
||||
text = `<p>${text}</p>`
|
||||
}
|
||||
this.setData({
|
||||
text: text
|
||||
text
|
||||
})
|
||||
}
|
||||
let html = `<div>${text}</div>`
|
||||
// 节点的富文本样式数据
|
||||
const nodeTextStyleList = []
|
||||
const nodeRichTextStyles = getNodeRichTextStyles(this)
|
||||
Object.keys(nodeRichTextStyles).forEach(prop => {
|
||||
nodeTextStyleList.push([prop, nodeRichTextStyles[prop]])
|
||||
})
|
||||
// 测量文本大小
|
||||
if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) {
|
||||
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl =
|
||||
document.createElement('div')
|
||||
@@ -166,12 +166,23 @@ function createRichTextNode(specifyText) {
|
||||
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl
|
||||
)
|
||||
}
|
||||
let div = this.mindMap.commonCaches.measureRichtextNodeTextSizeEl
|
||||
const div = this.mindMap.commonCaches.measureRichtextNodeTextSizeEl
|
||||
// 应用节点的文本样式
|
||||
nodeTextStyleList.forEach(([prop, value]) => {
|
||||
div.style[prop] = value
|
||||
})
|
||||
div.style.lineHeight = 1.2
|
||||
const html = `<div>${text}</div>`
|
||||
div.innerHTML = html
|
||||
let el = div.children[0]
|
||||
const el = div.children[0]
|
||||
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) {
|
||||
@@ -190,6 +201,15 @@ function createRichTextNode(specifyText) {
|
||||
width,
|
||||
height
|
||||
})
|
||||
// 应用节点文本样式
|
||||
// 进入文本编辑时,这个样式也会同样添加到文本编辑框的元素上
|
||||
const foreignObjectStyle = {
|
||||
'line-height': 1.2
|
||||
}
|
||||
nodeTextStyleList.forEach(([prop, value]) => {
|
||||
foreignObjectStyle[camelCaseToHyphen(prop)] = value
|
||||
})
|
||||
foreignObject.css(foreignObjectStyle)
|
||||
g.add(foreignObject)
|
||||
return {
|
||||
node: g,
|
||||
@@ -201,6 +221,10 @@ function createRichTextNode(specifyText) {
|
||||
|
||||
// 创建文本节点
|
||||
function createTextNode(specifyText) {
|
||||
if (this.getData('needUpdate')) {
|
||||
delete this.nodeData.data.needUpdate
|
||||
}
|
||||
// 如果是富文本内容,那么转给富文本函数
|
||||
if (this.getData('richText')) {
|
||||
return this.createRichTextNode(specifyText)
|
||||
}
|
||||
@@ -211,16 +235,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 +250,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(''))
|
||||
@@ -243,11 +265,20 @@ function createTextNode(specifyText) {
|
||||
}
|
||||
textArr[index] = lines.join('\n')
|
||||
})
|
||||
textArr = textArr.join('\n').split(/\n/gim)
|
||||
textArr = textArr.join('\n').replace(/\n$/g, '').split(/\n/gim)
|
||||
textArr.forEach((item, index) => {
|
||||
let node = new Text().text(item)
|
||||
// 避免尾部的空行不占宽度
|
||||
// 同时解决该问题:https://github.com/wanglin2/mind-map/issues/1037
|
||||
if (item === '') {
|
||||
item = ''
|
||||
}
|
||||
const node = new Text().text(item)
|
||||
node.addClass('smm-text-node-wrap')
|
||||
this.style.text(node)
|
||||
node.y(fontSize * lineHeight * index)
|
||||
node.y(
|
||||
fontSize * noneRichTextNodeLineHeight * index +
|
||||
((noneRichTextNodeLineHeight - 1) * fontSize) / 2
|
||||
)
|
||||
g.add(node)
|
||||
})
|
||||
let { width, height } = g.bbox()
|
||||
@@ -272,15 +303,16 @@ function createTextNode(specifyText) {
|
||||
|
||||
// 创建超链接节点
|
||||
function createHyperlinkNode() {
|
||||
let { hyperlink, hyperlinkTitle } = this.getData()
|
||||
const { hyperlink, hyperlinkTitle } = this.getData()
|
||||
if (!hyperlink) {
|
||||
return
|
||||
}
|
||||
const { customHyperlinkJump } = this.mindMap.opt
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
let node = new SVG().size(iconSize, iconSize)
|
||||
const { customHyperlinkJump, hyperlinkIcon } = this.mindMap.opt
|
||||
const { icon, style } = hyperlinkIcon
|
||||
const iconSize = this.getNodeIconSize('hyperlinkIcon')
|
||||
const node = new SVG().size(iconSize, iconSize)
|
||||
// 超链接节点
|
||||
let a = new A().to(hyperlink).target('_blank')
|
||||
const a = new A().to(hyperlink).target('_blank')
|
||||
a.node.addEventListener('click', e => {
|
||||
if (typeof customHyperlinkJump === 'function') {
|
||||
e.preventDefault()
|
||||
@@ -293,8 +325,8 @@ function createHyperlinkNode() {
|
||||
// 添加一个透明的层,作为鼠标区域
|
||||
a.rect(iconSize, iconSize).fill({ color: 'transparent' })
|
||||
// 超链接图标
|
||||
let iconNode = SVG(iconsSvg.hyperlink).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
const iconNode = SVG(icon || iconsSvg.hyperlink).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode, style.color)
|
||||
a.add(iconNode)
|
||||
node.add(a)
|
||||
return {
|
||||
@@ -379,16 +411,17 @@ function createNoteNode() {
|
||||
if (!this.getData('note')) {
|
||||
return null
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
let node = new SVG()
|
||||
const { icon, style } = this.mindMap.opt.noteIcon
|
||||
const iconSize = this.getNodeIconSize('noteIcon')
|
||||
const node = new SVG()
|
||||
.attr('cursor', 'pointer')
|
||||
.addClass('smm-node-note')
|
||||
.size(iconSize, iconSize)
|
||||
// 透明的层,用来作为鼠标区域
|
||||
node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' }))
|
||||
// 备注图标
|
||||
let iconNode = SVG(iconsSvg.note).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
const iconNode = SVG(icon || iconsSvg.note).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode, style.color)
|
||||
node.add(iconNode)
|
||||
// 备注tooltip
|
||||
if (!this.mindMap.opt.customNoteContentShow) {
|
||||
@@ -434,6 +467,9 @@ function createNoteNode() {
|
||||
node.on('click', e => {
|
||||
this.mindMap.emit('node_note_click', this, e, node)
|
||||
})
|
||||
node.on('dblclick', e => {
|
||||
this.mindMap.emit('node_note_dblclick', this, e, node)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
@@ -447,7 +483,8 @@ function createAttachmentNode() {
|
||||
if (!attachmentUrl) {
|
||||
return
|
||||
}
|
||||
const iconSize = this.mindMap.themeConfig.iconSize
|
||||
const iconSize = this.getNodeIconSize('attachmentIcon')
|
||||
const { icon, style } = this.mindMap.opt.attachmentIcon
|
||||
const node = new SVG().attr('cursor', 'pointer').size(iconSize, iconSize)
|
||||
if (attachmentName) {
|
||||
node.add(SVG(`<title>${attachmentName}</title>`))
|
||||
@@ -455,8 +492,8 @@ function createAttachmentNode() {
|
||||
// 透明的层,用来作为鼠标区域
|
||||
node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' }))
|
||||
// 备注图标
|
||||
const iconNode = SVG(iconsSvg.attachment).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode)
|
||||
const iconNode = SVG(icon || iconsSvg.attachment).size(iconSize, iconSize)
|
||||
this.style.iconNode(iconNode, style.color)
|
||||
node.add(iconNode)
|
||||
node.on('click', e => {
|
||||
this.mindMap.emit('node_attachmentClick', this, e, node)
|
||||
@@ -471,9 +508,15 @@ function createAttachmentNode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点图标大小
|
||||
function getNodeIconSize(prop) {
|
||||
const { style } = this.mindMap.opt[prop]
|
||||
return isUndef(style.size) ? this.mindMap.themeConfig.iconSize : style.size
|
||||
}
|
||||
|
||||
// 获取节点备注显示位置
|
||||
function getNoteContentPosition() {
|
||||
const iconSize = this.mindMap.themeConfig.iconSize
|
||||
const iconSize = this.getNodeIconSize('noteIcon')
|
||||
const { scaleY } = this.mindMap.view.getTransformData().transform
|
||||
const iconSizeAddScale = iconSize * scaleY
|
||||
let { left, top } = this._noteData.node.node.getBoundingClientRect()
|
||||
@@ -524,6 +567,7 @@ export default {
|
||||
createNoteNode,
|
||||
createAttachmentNode,
|
||||
getNoteContentPosition,
|
||||
getNodeIconSize,
|
||||
measureCustomNodeContentSize,
|
||||
isUseCustomNodeContent
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ function createExpandNodeContent() {
|
||||
if (this.mindMap.opt.isShowExpandNum) {
|
||||
// 展开的节点
|
||||
this._openExpandNode = new Text()
|
||||
this._openExpandNode.addClass('smm-expand-btn-text')
|
||||
// 文本垂直居中
|
||||
this._openExpandNode.attr({
|
||||
'text-anchor': 'middle',
|
||||
|
||||
153
simple-mind-map/src/core/render/node/nodeModifyWidth.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import { Rect } from '@svgdotjs/svg.js'
|
||||
|
||||
// 初始化拖拽
|
||||
function initDragHandle() {
|
||||
if (!this.checkEnableDragModifyNodeWidth()) {
|
||||
return
|
||||
}
|
||||
// 拖拽手柄元素
|
||||
this._dragHandleNodes = null
|
||||
// 手柄元素的宽度
|
||||
this.dragHandleWidth = 4
|
||||
// 鼠标按下时的x坐标
|
||||
this.dragHandleMousedownX = 0
|
||||
// 鼠标是否处于按下状态
|
||||
this.isDragHandleMousedown = false
|
||||
// 当前拖拽的手柄序号
|
||||
this.dragHandleIndex = 0
|
||||
// 鼠标按下时记录当前的customTextWidth值
|
||||
this.dragHandleMousedownCustomTextWidth = 0
|
||||
// 鼠标按下时记录当前的手型样式
|
||||
this.dragHandleMousedownBodyCursor = ''
|
||||
// 鼠标按下时记录当前节点的left值
|
||||
this.dragHandleMousedownLeft = 0
|
||||
|
||||
this.onDragMousemoveHandle = this.onDragMousemoveHandle.bind(this)
|
||||
window.addEventListener('mousemove', this.onDragMousemoveHandle)
|
||||
this.onDragMouseupHandle = this.onDragMouseupHandle.bind(this)
|
||||
window.addEventListener('mouseup', this.onDragMouseupHandle)
|
||||
this.mindMap.on('node_mouseup', this.onDragMouseupHandle)
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
function onDragMousemoveHandle(e) {
|
||||
if (!this.isDragHandleMousedown) return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let {
|
||||
minNodeTextModifyWidth,
|
||||
maxNodeTextModifyWidth,
|
||||
isUseCustomNodeContent,
|
||||
customCreateNodeContent
|
||||
} = this.mindMap.opt
|
||||
const useCustomContent =
|
||||
isUseCustomNodeContent && customCreateNodeContent && this._customNodeContent
|
||||
document.body.style.cursor = 'ew-resize'
|
||||
this.group.css({
|
||||
cursor: 'ew-resize'
|
||||
})
|
||||
const { scaleX } = this.mindMap.draw.transform()
|
||||
const ox = e.clientX - this.dragHandleMousedownX
|
||||
let newWidth =
|
||||
this.dragHandleMousedownCustomTextWidth +
|
||||
(this.dragHandleIndex === 0 ? -ox : ox) / scaleX
|
||||
newWidth = Math.max(newWidth, minNodeTextModifyWidth)
|
||||
if (maxNodeTextModifyWidth !== -1) {
|
||||
newWidth = Math.min(newWidth, maxNodeTextModifyWidth)
|
||||
}
|
||||
// 如果存在图片,那么最小值需要考虑图片宽度
|
||||
if (!useCustomContent && this.getData('image')) {
|
||||
const imgSize = this.getImgShowSize()
|
||||
if (
|
||||
this._rectInfo.textContentWidth - this.customTextWidth + newWidth <=
|
||||
imgSize[0]
|
||||
) {
|
||||
newWidth =
|
||||
imgSize[0] + this.customTextWidth - this._rectInfo.textContentWidth
|
||||
}
|
||||
}
|
||||
this.customTextWidth = newWidth
|
||||
if (this.dragHandleIndex === 0) {
|
||||
this.left = this.dragHandleMousedownLeft + ox / scaleX
|
||||
}
|
||||
// 自定义内容不重新渲染,交给开发者
|
||||
this.reRender(useCustomContent ? [] : ['text'], {
|
||||
ignoreUpdateCustomTextWidth: true
|
||||
})
|
||||
}
|
||||
|
||||
// 鼠标松开事件
|
||||
function onDragMouseupHandle() {
|
||||
if (!this.isDragHandleMousedown) return
|
||||
document.body.style.cursor = this.dragHandleMousedownBodyCursor
|
||||
this.group.css({
|
||||
cursor: 'default'
|
||||
})
|
||||
this.isDragHandleMousedown = false
|
||||
this.dragHandleMousedownX = 0
|
||||
this.dragHandleIndex = 0
|
||||
this.dragHandleMousedownCustomTextWidth = 0
|
||||
this.setData({
|
||||
customTextWidth: this.customTextWidth
|
||||
})
|
||||
this.mindMap.render()
|
||||
this.mindMap.emit('dragModifyNodeWidthEnd', this)
|
||||
}
|
||||
|
||||
// 插件拖拽手柄元素
|
||||
function createDragHandleNode() {
|
||||
const list = [new Rect(), new Rect()]
|
||||
list.forEach((node, index) => {
|
||||
node
|
||||
.size(this.dragHandleWidth, this.height)
|
||||
.fill({
|
||||
color: 'transparent'
|
||||
})
|
||||
.css({
|
||||
cursor: 'ew-resize'
|
||||
})
|
||||
node.on('mousedown', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.dragHandleMousedownX = e.clientX
|
||||
this.dragHandleIndex = index
|
||||
this.dragHandleMousedownCustomTextWidth =
|
||||
this.customTextWidth === undefined
|
||||
? this._textData
|
||||
? this._textData.width
|
||||
: this.width
|
||||
: this.customTextWidth
|
||||
this.dragHandleMousedownBodyCursor = document.body.style.cursor
|
||||
this.dragHandleMousedownLeft = this.left
|
||||
this.isDragHandleMousedown = true
|
||||
})
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
// 更新拖拽按钮的显隐和位置尺寸
|
||||
function updateDragHandle() {
|
||||
if (!this.checkEnableDragModifyNodeWidth()) return
|
||||
if (!this._dragHandleNodes) {
|
||||
this._dragHandleNodes = this.createDragHandleNode()
|
||||
}
|
||||
if (this.getData('isActive')) {
|
||||
this._dragHandleNodes.forEach(node => {
|
||||
node.height(this.height)
|
||||
this.group.add(node)
|
||||
})
|
||||
this._dragHandleNodes[1].x(this.width - this.dragHandleWidth)
|
||||
} else {
|
||||
this._dragHandleNodes.forEach(node => {
|
||||
node.remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
initDragHandle,
|
||||
onDragMousemoveHandle,
|
||||
onDragMouseupHandle,
|
||||
createDragHandleNode,
|
||||
updateDragHandle
|
||||
}
|
||||
@@ -354,6 +354,10 @@ class View {
|
||||
|
||||
// 判断是否需要将思维导图限制在画布内
|
||||
checkNeedMindMapInCanvas() {
|
||||
// 如果当前在演示模式,那么不需要限制
|
||||
if (this.mindMap.demonstrate && this.mindMap.demonstrate.isInDemonstrate) {
|
||||
return false
|
||||
}
|
||||
const { isLimitMindMapInCanvasWhenHasScrollbar, isLimitMindMapInCanvas } =
|
||||
this.mindMap.opt
|
||||
// 如果注册了滚动条插件,那么使用isLimitMindMapInCanvasWhenHasScrollbar配置
|
||||
|
||||
@@ -49,10 +49,7 @@ class Base {
|
||||
|
||||
// 检查当前来源是否需要重新计算节点大小
|
||||
checkIsNeedResizeSources() {
|
||||
return [
|
||||
CONSTANTS.CHANGE_THEME,
|
||||
CONSTANTS.TRANSFORM_TO_NORMAL_NODE
|
||||
].includes(this.renderer.renderSource)
|
||||
return [CONSTANTS.CHANGE_THEME].includes(this.renderer.renderSource)
|
||||
}
|
||||
|
||||
// 层级类型改变
|
||||
@@ -69,43 +66,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 +114,36 @@ 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
|
||||
newNode.getData('needUpdate') ||
|
||||
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 +155,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 +173,26 @@ 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
|
||||
newNode.getData('needUpdate') ||
|
||||
isNodeInnerPrefixChange
|
||||
) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
@@ -200,7 +210,7 @@ class Base {
|
||||
layerIndex,
|
||||
isRoot,
|
||||
parent: !isRoot ? parent._node : null,
|
||||
number: newNumberStr
|
||||
...nodeInnerPrefixData
|
||||
})
|
||||
// uid保存到数据上,为了节点复用
|
||||
data.data.uid = newUid
|
||||
@@ -441,8 +451,10 @@ class Base {
|
||||
const end = list[len - 1]
|
||||
// 如果三点在一条直线,那么不用处理
|
||||
const isOneLine =
|
||||
(start[0] === center[0] && center[0] === end[0]) ||
|
||||
(start[1] === center[1] && center[1] === end[1])
|
||||
(start[0].toFixed(0) === center[0].toFixed(0) &&
|
||||
center[0].toFixed(0) === end[0].toFixed(0)) ||
|
||||
(start[1].toFixed(0) === center[1].toFixed(0) &&
|
||||
center[1].toFixed(0) === end[1].toFixed(0))
|
||||
if (!isOneLine) {
|
||||
const cStart = this.computeNewPoint(start, center, lineRadius)
|
||||
const cEnd = this.computeNewPoint(end, center, lineRadius)
|
||||
|
||||
@@ -35,7 +35,14 @@ class MindMap extends Base {
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(cur, parent, isRoot, layerIndex, index, ancestors) => {
|
||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex, index, ancestors)
|
||||
let newNode = this.createNode(
|
||||
cur,
|
||||
parent,
|
||||
isRoot,
|
||||
layerIndex,
|
||||
index,
|
||||
ancestors
|
||||
)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
@@ -47,9 +54,10 @@ class MindMap extends Base {
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
newNode.getData('dir') ||
|
||||
(index % 2 === 0
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.LEFT)
|
||||
}
|
||||
// 根据生长方向定位到父节点的左侧或右侧
|
||||
newNode.left =
|
||||
|
||||
@@ -11,11 +11,25 @@ import {
|
||||
import associativeLineControlsMethods from './associativeLine/associativeLineControls'
|
||||
import associativeLineTextMethods from './associativeLine/associativeLineText'
|
||||
|
||||
const styleProps = [
|
||||
'associativeLineWidth',
|
||||
'associativeLineColor',
|
||||
'associativeLineActiveWidth',
|
||||
'associativeLineActiveColor',
|
||||
'associativeLineDasharray',
|
||||
'associativeLineTextColor',
|
||||
'associativeLineTextFontSize',
|
||||
'associativeLineTextLineHeight',
|
||||
'associativeLineTextFontFamily'
|
||||
]
|
||||
|
||||
// 关联线插件
|
||||
class AssociativeLine {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.associativeLineDraw = this.mindMap.associativeLineDraw
|
||||
// 本次不要重新渲染连线
|
||||
this.isNotRenderAllLines = false
|
||||
// 当前所有连接线
|
||||
this.lineList = []
|
||||
// 当前激活的连接线
|
||||
@@ -27,9 +41,6 @@ class AssociativeLine {
|
||||
this.overlapNode = null // 创建过程中的目标节点
|
||||
// 是否有节点正在被拖拽
|
||||
this.isNodeDragging = false
|
||||
// 箭头图标
|
||||
this.markerPath = null
|
||||
this.marker = this.createMarker()
|
||||
// 控制点
|
||||
this.controlLine1 = null
|
||||
this.controlLine2 = null
|
||||
@@ -112,6 +123,25 @@ class AssociativeLine {
|
||||
this.mindMap.off('beforeDestroy', this.onBeforeDestroy)
|
||||
}
|
||||
|
||||
// 获取关联线的样式配置
|
||||
// 优先级:关联线自定义样式、节点自定义样式、主题的节点层级样式、主题的最外层样式
|
||||
getStyleConfig(node, toNode) {
|
||||
let lineStyle = {}
|
||||
if (toNode) {
|
||||
const associativeLineStyle = node.getData('associativeLineStyle') || {}
|
||||
lineStyle = associativeLineStyle[toNode.getData('uid')] || {}
|
||||
}
|
||||
const res = {}
|
||||
styleProps.forEach(prop => {
|
||||
if (typeof lineStyle[prop] !== 'undefined') {
|
||||
res[prop] = lineStyle[prop]
|
||||
} else {
|
||||
res[prop] = node.getStyle(prop)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 实例销毁时清除关联线文字编辑框
|
||||
onBeforeDestroy() {
|
||||
this.hideEditTextBox()
|
||||
@@ -140,12 +170,12 @@ class AssociativeLine {
|
||||
}
|
||||
|
||||
// 创建箭头
|
||||
createMarker() {
|
||||
createMarker(callback = () => {}) {
|
||||
return this.associativeLineDraw.marker(20, 20, add => {
|
||||
add.ref(12, 5)
|
||||
add.size(10, 10)
|
||||
add.attr('orient', 'auto-start-reverse')
|
||||
this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z')
|
||||
callback(add.path('M0,0 L2,5 L0,10 L10,5 Z'))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -172,6 +202,10 @@ class AssociativeLine {
|
||||
|
||||
// 渲染所有连线
|
||||
renderAllLines() {
|
||||
if (this.isNotRenderAllLines) {
|
||||
this.isNotRenderAllLines = false
|
||||
return
|
||||
}
|
||||
// 先移除
|
||||
this.removeAllLines()
|
||||
this.removeControls()
|
||||
@@ -223,11 +257,14 @@ class AssociativeLine {
|
||||
associativeLineWidth,
|
||||
associativeLineColor,
|
||||
associativeLineActiveWidth,
|
||||
associativeLineActiveColor,
|
||||
associativeLineDasharray
|
||||
} = this.mindMap.themeConfig
|
||||
} = this.getStyleConfig(node, toNode)
|
||||
// 箭头
|
||||
this.markerPath
|
||||
let markerPath = null
|
||||
const marker = this.createMarker(p => {
|
||||
markerPath = p
|
||||
})
|
||||
markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
// 路径
|
||||
@@ -247,7 +284,7 @@ class AssociativeLine {
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
path.plot(pathStr)
|
||||
path.marker('end', this.marker)
|
||||
path.marker('end', marker)
|
||||
// 不可见的点击线
|
||||
let clickPath = this.associativeLineDraw.path()
|
||||
clickPath
|
||||
@@ -258,6 +295,7 @@ class AssociativeLine {
|
||||
let text = this.createText({
|
||||
path,
|
||||
clickPath,
|
||||
markerPath,
|
||||
node,
|
||||
toNode,
|
||||
startPoint,
|
||||
@@ -270,6 +308,7 @@ class AssociativeLine {
|
||||
this.setActiveLine({
|
||||
path,
|
||||
clickPath,
|
||||
markerPath,
|
||||
text,
|
||||
node,
|
||||
toNode,
|
||||
@@ -284,14 +323,72 @@ class AssociativeLine {
|
||||
this.showEditTextBox(text)
|
||||
})
|
||||
// 渲染关联线文字
|
||||
this.renderText(this.getText(node, toNode), path, text)
|
||||
this.renderText(this.getText(node, toNode), path, text, node, toNode)
|
||||
this.lineList.push([path, clickPath, text, node, toNode])
|
||||
}
|
||||
|
||||
// 更新当前激活连线的样式,一般在自定义了节点关联线的样式后调用
|
||||
// 直接调用node.setStyle方法更新样式会直接触发关联线更新,但是关联线的激活状态会丢失
|
||||
// 所以可以调用node.setData方法更新数据,然后再调用该方法更新样式,这样关联线激活状态不会丢失
|
||||
updateActiveLineStyle() {
|
||||
if (!this.activeLine) return
|
||||
this.isNotRenderAllLines = true
|
||||
const [path, clickPath, text, node, toNode, markerPath] = this.activeLine
|
||||
const {
|
||||
associativeLineWidth,
|
||||
associativeLineColor,
|
||||
associativeLineDasharray,
|
||||
associativeLineActiveWidth,
|
||||
associativeLineActiveColor,
|
||||
associativeLineTextColor,
|
||||
associativeLineTextFontFamily,
|
||||
associativeLineTextFontSize
|
||||
} = this.getStyleConfig(node, toNode)
|
||||
path
|
||||
.stroke({
|
||||
width: associativeLineWidth,
|
||||
color: associativeLineColor,
|
||||
dasharray: associativeLineDasharray || [6, 4]
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
clickPath
|
||||
.stroke({
|
||||
width: associativeLineActiveWidth,
|
||||
color: associativeLineActiveColor
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
text.find('text').forEach(textNode => {
|
||||
textNode
|
||||
.fill({
|
||||
color: associativeLineTextColor
|
||||
})
|
||||
.css({
|
||||
'font-family': associativeLineTextFontFamily,
|
||||
'font-size': associativeLineTextFontSize + 'px'
|
||||
})
|
||||
})
|
||||
if (this.controlLine1) {
|
||||
this.controlLine1.stroke({ color: associativeLineActiveColor })
|
||||
}
|
||||
if (this.controlLine2) {
|
||||
this.controlLine2.stroke({ color: associativeLineActiveColor })
|
||||
}
|
||||
if (this.controlPoint1) {
|
||||
this.controlPoint1.stroke({ color: associativeLineActiveColor })
|
||||
}
|
||||
if (this.controlPoint2) {
|
||||
this.controlPoint2.stroke({ color: associativeLineActiveColor })
|
||||
}
|
||||
}
|
||||
|
||||
// 激活某根关联线
|
||||
setActiveLine({
|
||||
path,
|
||||
clickPath,
|
||||
markerPath,
|
||||
text,
|
||||
node,
|
||||
toNode,
|
||||
@@ -299,25 +396,33 @@ class AssociativeLine {
|
||||
endPoint,
|
||||
controlPoints
|
||||
}) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
|
||||
// 如果当前存在激活节点,那么取消激活节点
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
// 否则清除当前的关联线的激活状态,如果有的话
|
||||
this.clearActiveLine()
|
||||
// 保存当前激活的关联线信息
|
||||
this.activeLine = [path, clickPath, text, node, toNode]
|
||||
this.activeLine = [path, clickPath, text, node, toNode, markerPath]
|
||||
// 让不可见的点击线显示
|
||||
clickPath.stroke({ color: associativeLineActiveColor })
|
||||
// 如果没有输入过关联线文字,那么显示默认文字
|
||||
if (!this.getText(node, toNode)) {
|
||||
this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text)
|
||||
this.renderText(
|
||||
this.mindMap.opt.defaultAssociativeLineText,
|
||||
path,
|
||||
text,
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
}
|
||||
// 渲染控制点和连线
|
||||
this.renderControls(
|
||||
startPoint,
|
||||
endPoint,
|
||||
controlPoints[0],
|
||||
controlPoints[1]
|
||||
controlPoints[1],
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
|
||||
this.front()
|
||||
@@ -346,7 +451,7 @@ class AssociativeLine {
|
||||
associativeLineWidth,
|
||||
associativeLineColor,
|
||||
associativeLineDasharray
|
||||
} = this.mindMap.themeConfig
|
||||
} = this.getStyleConfig(fromNode)
|
||||
if (this.isCreatingLine || !fromNode) return
|
||||
this.front()
|
||||
this.isCreatingLine = true
|
||||
@@ -360,10 +465,14 @@ class AssociativeLine {
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
// 箭头
|
||||
this.markerPath
|
||||
let markerPath = null
|
||||
const marker = this.createMarker(p => {
|
||||
markerPath = p
|
||||
})
|
||||
markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
this.creatingLine.marker('end', this.marker)
|
||||
this.creatingLine.marker('end', marker)
|
||||
}
|
||||
|
||||
// 取消创建关联线
|
||||
@@ -446,6 +555,12 @@ class AssociativeLine {
|
||||
// 完成创建连接线
|
||||
completeCreateLine(node) {
|
||||
if (this.creatingStartNode.uid === node.uid) return
|
||||
const { beforeAssociativeLineConnection } = this.mindMap.opt
|
||||
let stop = false
|
||||
if (typeof beforeAssociativeLineConnection === 'function') {
|
||||
stop = beforeAssociativeLineConnection(node)
|
||||
}
|
||||
if (stop) return
|
||||
this.addLine(this.creatingStartNode, node)
|
||||
if (this.overlapNode && this.overlapNode.getData('isActive')) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, false)
|
||||
@@ -523,7 +638,8 @@ class AssociativeLine {
|
||||
associativeLineTargets,
|
||||
associativeLinePoint,
|
||||
associativeLineTargetControlOffsets,
|
||||
associativeLineText
|
||||
associativeLineText,
|
||||
associativeLineStyle
|
||||
} = node.getData()
|
||||
associativeLinePoint = associativeLinePoint || []
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
@@ -536,6 +652,15 @@ class AssociativeLine {
|
||||
}
|
||||
})
|
||||
}
|
||||
// 更新关联线样式数据
|
||||
let newAssociativeLineStyle = {}
|
||||
if (associativeLineStyle) {
|
||||
Object.keys(associativeLineStyle).forEach(item => {
|
||||
if (item !== toNode.getData('uid')) {
|
||||
newAssociativeLineStyle[item] = associativeLineStyle[item]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
// 目标
|
||||
associativeLineTargets: associativeLineTargets.filter((_, index) => {
|
||||
@@ -552,7 +677,9 @@ class AssociativeLine {
|
||||
})
|
||||
: [],
|
||||
// 文本
|
||||
associativeLineText: newAssociativeLineText
|
||||
associativeLineText: newAssociativeLineText,
|
||||
// 样式
|
||||
associativeLineStyle: newAssociativeLineStyle
|
||||
})
|
||||
}
|
||||
|
||||
@@ -572,6 +699,7 @@ class AssociativeLine {
|
||||
this.activeLine = null
|
||||
this.removeControls()
|
||||
this.back()
|
||||
this.mindMap.emit('associative_line_deactivate')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ class Drag extends Base {
|
||||
) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
this.isMousedown = true
|
||||
// 记录鼠标按下时的节点
|
||||
this.mousedownNode = node
|
||||
|
||||
@@ -107,14 +107,13 @@ class Formula {
|
||||
|
||||
// 给指定的节点插入指定公式
|
||||
insertFormulaToNode(node, formula) {
|
||||
let richTextPlugin = this.mindMap.richText
|
||||
const richTextPlugin = this.mindMap.richText
|
||||
richTextPlugin.showEditText({ node })
|
||||
richTextPlugin.quill.insertEmbed(
|
||||
richTextPlugin.quill.getLength() - 1,
|
||||
'formula',
|
||||
formula
|
||||
)
|
||||
richTextPlugin.setTextStyleIfNotRichText(richTextPlugin.node)
|
||||
richTextPlugin.hideEditText([node])
|
||||
}
|
||||
|
||||
@@ -127,8 +126,18 @@ class Formula {
|
||||
for (const el of els)
|
||||
nodeText = nodeText.replace(
|
||||
el.outerHTML,
|
||||
`\$${el.getAttribute('data-value')}\$`
|
||||
`$${el.getAttribute('data-value')}$`
|
||||
)
|
||||
// 如果开启了实时渲染,那么意味公式转换为源码时会影响节点尺寸,需要派发事件触发渲染
|
||||
if (this.mindMap.opt.openRealtimeRenderOnNodeTextEdit) {
|
||||
setTimeout(() => {
|
||||
this.mindMap.emit('node_text_edit_change', {
|
||||
node: this.mindMap.richText.node,
|
||||
text: this.mindMap.richText.getEditText(),
|
||||
richText: true
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
return nodeText
|
||||
}
|
||||
|
||||
117
simple-mind-map/src/plugins/MindMapLayoutPro.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 该插件会向节点数据的data中添加dir字段
|
||||
/*
|
||||
需要更新数据的情况:
|
||||
|
||||
1.实例化时的数据
|
||||
2.调用setData和updateData方法
|
||||
3.执行完命令
|
||||
4.切换结构
|
||||
*/
|
||||
|
||||
class MindMapLayoutPro {
|
||||
constructor(opt) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
this.updateNodeTree = this.updateNodeTree.bind(this)
|
||||
this.afterExecCommand = this.afterExecCommand.bind(this)
|
||||
this.layoutChange = this.layoutChange.bind(this)
|
||||
|
||||
// 处理实例化时传入的数据
|
||||
if (this.mindMap.opt.data && this.isMindMapLayout()) {
|
||||
this.updateNodeTree(this.mindMap.opt.data)
|
||||
}
|
||||
|
||||
this.mindMap.on('layout_change', this.layoutChange)
|
||||
this.mindMap.on('afterExecCommand', this.afterExecCommand)
|
||||
this.mindMap.on('before_update_data', this.updateNodeTree)
|
||||
this.mindMap.on('before_set_data', this.updateNodeTree)
|
||||
}
|
||||
|
||||
restore() {
|
||||
this.mindMap.off('layout_change', this.layoutChange)
|
||||
this.mindMap.off('afterExecCommand', this.afterExecCommand)
|
||||
this.mindMap.off('before_update_data', this.updateNodeTree)
|
||||
this.mindMap.off('before_set_data', this.updateNodeTree)
|
||||
}
|
||||
|
||||
// 监听命令执行后的事件
|
||||
afterExecCommand(name) {
|
||||
if (!this.isMindMapLayout()) return
|
||||
if (
|
||||
![
|
||||
'BACK',
|
||||
'FORWARD',
|
||||
'INSERT_NODE',
|
||||
'INSERT_MULTI_NODE',
|
||||
'INSERT_CHILD_NODE',
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
'INSERT_PARENT_NODE',
|
||||
'UP_NODE',
|
||||
'DOWN_NODE',
|
||||
'MOVE_UP_ONE_LEVEL',
|
||||
'INSERT_AFTER',
|
||||
'INSERT_BEFORE',
|
||||
'MOVE_NODE_TO',
|
||||
'REMOVE_NODE',
|
||||
'REMOVE_CURRENT_NODE',
|
||||
'PASTE_NODE',
|
||||
'CUT_NODE'
|
||||
].includes(name)
|
||||
)
|
||||
return
|
||||
this.updateRenderTree()
|
||||
}
|
||||
|
||||
// 更新布局结构
|
||||
layoutChange(layout) {
|
||||
if (layout === CONSTANTS.LAYOUT.MIND_MAP) {
|
||||
this.updateRenderTree()
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前的渲染树
|
||||
updateRenderTree() {
|
||||
this.updateNodeTree(this.mindMap.renderer.renderTree)
|
||||
}
|
||||
|
||||
// 更新节点树,修改二级节点的排列位置
|
||||
updateNodeTree(tree) {
|
||||
if (!this.isMindMapLayout()) return
|
||||
const root = tree
|
||||
const childrenLength = root.children.length
|
||||
if (childrenLength <= 0) return
|
||||
const center = Math.ceil(childrenLength / 2)
|
||||
root.children.forEach((item, index) => {
|
||||
if (index + 1 <= center) {
|
||||
item.data.dir = CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
} else {
|
||||
item.data.dir = CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 判断当前是否是思维导图布局结构
|
||||
isMindMapLayout() {
|
||||
return this.mindMap.opt.layout === CONSTANTS.LAYOUT.MIND_MAP
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.restore()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
this.restore()
|
||||
}
|
||||
}
|
||||
|
||||
MindMapLayoutPro.instanceName = 'mindMapLayoutPro'
|
||||
|
||||
export default MindMapLayoutPro
|
||||
@@ -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
|
||||
}
|
||||
|
||||
// 渲染完成事件
|
||||
|
||||
@@ -4,15 +4,15 @@ import 'quill/dist/quill.snow.css'
|
||||
import {
|
||||
walk,
|
||||
getTextFromHtml,
|
||||
isWhite,
|
||||
getVisibleColorFromTheme,
|
||||
isUndef,
|
||||
checkSmmFormatData,
|
||||
removeHtmlNodeByClass,
|
||||
formatGetNodeGeneralization,
|
||||
nodeRichTextToTextWithWrap
|
||||
nodeRichTextToTextWithWrap,
|
||||
getNodeRichTextStyles,
|
||||
htmlEscape,
|
||||
compareVersion
|
||||
} from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
import { CONSTANTS, richTextSupportStyleList } from '../constants/constant'
|
||||
import MindMapNode from '../core/render/node/MindMapNode'
|
||||
import { Scope } from 'parchment'
|
||||
|
||||
@@ -55,7 +55,6 @@ class RichText {
|
||||
this.isInserting = false
|
||||
this.styleEl = null
|
||||
this.cacheEditingText = ''
|
||||
this.lostStyle = false
|
||||
this.isCompositing = false
|
||||
this.textNodePaddingX = 6
|
||||
this.textNodePaddingY = 4
|
||||
@@ -64,10 +63,7 @@ class RichText {
|
||||
this.appendCss()
|
||||
this.bindEvent()
|
||||
|
||||
// 处理数据,转成富文本格式
|
||||
if (this.mindMap.opt.data) {
|
||||
this.mindMap.opt.data = this.handleSetData(this.mindMap.opt.data)
|
||||
}
|
||||
this.handleDataToRichTextOnInit()
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
@@ -75,9 +71,12 @@ class RichText {
|
||||
this.onCompositionStart = this.onCompositionStart.bind(this)
|
||||
this.onCompositionUpdate = this.onCompositionUpdate.bind(this)
|
||||
this.onCompositionEnd = this.onCompositionEnd.bind(this)
|
||||
this.handleSetData = this.handleSetData.bind(this)
|
||||
window.addEventListener('compositionstart', this.onCompositionStart)
|
||||
window.addEventListener('compositionupdate', this.onCompositionUpdate)
|
||||
window.addEventListener('compositionend', this.onCompositionEnd)
|
||||
this.mindMap.on('before_update_data', this.handleSetData)
|
||||
this.mindMap.on('before_set_data', this.handleSetData)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -85,16 +84,27 @@ class RichText {
|
||||
window.removeEventListener('compositionstart', this.onCompositionStart)
|
||||
window.removeEventListener('compositionupdate', this.onCompositionUpdate)
|
||||
window.removeEventListener('compositionend', this.onCompositionEnd)
|
||||
this.mindMap.off('before_update_data', this.handleSetData)
|
||||
this.mindMap.off('before_set_data', this.handleSetData)
|
||||
}
|
||||
|
||||
// 插入样式
|
||||
appendCss() {
|
||||
this.mindMap.appendCss(
|
||||
'richText',
|
||||
`
|
||||
.smm-richtext-node-wrap {
|
||||
word-break: break-all;
|
||||
user-select: none;
|
||||
}
|
||||
`
|
||||
)
|
||||
let cssText = `
|
||||
.ql-editor {
|
||||
.${CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP} {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
line-height: 1.2;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
@@ -106,19 +116,6 @@ class RichText {
|
||||
.ql-container.ql-snow {
|
||||
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;
|
||||
}
|
||||
`
|
||||
this.styleEl = document.createElement('style')
|
||||
this.styleEl.type = 'text/css'
|
||||
@@ -180,14 +177,18 @@ class RichText {
|
||||
if (this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
const {
|
||||
richTextEditFakeInPlace,
|
||||
let {
|
||||
customInnerElsAppendTo,
|
||||
nodeTextEditZIndex,
|
||||
textAutoWrapWidth,
|
||||
selectTextOnEnterEditText,
|
||||
transformRichTextOnEnterEdit
|
||||
transformRichTextOnEnterEdit,
|
||||
openRealtimeRenderOnNodeTextEdit,
|
||||
autoEmptyTextWhenKeydownEnterEdit
|
||||
} = this.mindMap.opt
|
||||
textAutoWrapWidth = node.hasCustomWidth()
|
||||
? node.customTextWidth
|
||||
: textAutoWrapWidth
|
||||
this.node = node
|
||||
this.isInserting = isInserting
|
||||
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
|
||||
@@ -200,27 +201,26 @@ class RichText {
|
||||
let originWidth = g.attr('data-width')
|
||||
let originHeight = g.attr('data-height')
|
||||
// 缩放值
|
||||
let scaleX = rect.width / originWidth
|
||||
let scaleY = rect.height / originHeight
|
||||
const scaleX = Math.ceil(rect.width) / originWidth
|
||||
const scaleY = Math.ceil(rect.height) / originHeight
|
||||
// 内边距
|
||||
let paddingX = this.textNodePaddingX
|
||||
let paddingY = this.textNodePaddingY
|
||||
if (richTextEditFakeInPlace) {
|
||||
let paddingValue = node.getPaddingVale()
|
||||
paddingX = paddingValue.paddingX
|
||||
paddingY = paddingValue.paddingY
|
||||
}
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.classList.add('smm-richtext-node-edit-wrap')
|
||||
this.textEditNode.style.cssText = `
|
||||
position:fixed;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,.5);
|
||||
outline: none;
|
||||
word-break:
|
||||
break-all;
|
||||
position:fixed;
|
||||
box-sizing: border-box;
|
||||
${
|
||||
openRealtimeRenderOnNodeTextEdit
|
||||
? ''
|
||||
: 'box-shadow: 0 0 20px rgba(0,0,0,.5);'
|
||||
}
|
||||
outline: none;
|
||||
word-break: break-all;
|
||||
padding: ${paddingY}px ${paddingX}px;
|
||||
line-height: 1.2;
|
||||
`
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
@@ -236,10 +236,14 @@ class RichText {
|
||||
const targetNode = customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
this.addNodeTextStyleToTextEditNode(node)
|
||||
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'
|
||||
@@ -248,13 +252,6 @@ class RichText {
|
||||
this.textEditNode.style.maxWidth = textAutoWrapWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.transform = `scale(${scaleX}, ${scaleY})`
|
||||
this.textEditNode.style.transformOrigin = 'left top'
|
||||
if (richTextEditFakeInPlace) {
|
||||
this.textEditNode.style.borderRadius =
|
||||
(node.style.merge('borderRadius') || 5) + 'px'
|
||||
if (node.style.merge('shape') == 'roundedRectangle') {
|
||||
this.textEditNode.style.borderRadius = (node.height || 50) + 'px'
|
||||
}
|
||||
}
|
||||
// 节点文本内容
|
||||
let nodeText = node.getData('text')
|
||||
if (typeof transformRichTextOnEnterEdit === 'function') {
|
||||
@@ -264,11 +261,9 @@ class RichText {
|
||||
const isEmptyText = isUndef(nodeText)
|
||||
// 是否是非空的非富文本
|
||||
const noneEmptyNoneRichText = !node.getData('richText') && !isEmptyText
|
||||
// 如果是空文本,那么设置为丢失样式状态,否则输入不会带上样式
|
||||
if (isEmptyText) {
|
||||
this.lostStyle = true
|
||||
}
|
||||
if (noneEmptyNoneRichText) {
|
||||
if (isFromKeyDown && autoEmptyTextWhenKeydownEnterEdit) {
|
||||
this.textEditNode.innerHTML = ''
|
||||
} else if (noneEmptyNoneRichText) {
|
||||
// 还不是富文本
|
||||
let text = String(nodeText).split(/\n/gim).join('<br>')
|
||||
let html = `<p>${text}</p>`
|
||||
@@ -278,25 +273,51 @@ class RichText {
|
||||
this.textEditNode.innerHTML = this.cacheEditingText || nodeText
|
||||
}
|
||||
this.initQuillEditor()
|
||||
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
|
||||
this.setQuillContainerMinHeight(originHeight)
|
||||
this.showTextEdit = true
|
||||
// 如果是刚创建的节点,那么默认全选,否则普通激活不全选,除非selectTextOnEnterEditText配置为true
|
||||
// 在selectTextOnEnterEditText时,如果是在keydown事件进入的节点编辑,也不需要全选
|
||||
this.focus(
|
||||
isInserting || (selectTextOnEnterEditText && !isFromKeyDown) ? 0 : null
|
||||
)
|
||||
if (noneEmptyNoneRichText) {
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
this.setTextStyleIfNotRichText(node)
|
||||
}
|
||||
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)'
|
||||
}
|
||||
|
||||
// 将指定节点的文本样式添加到编辑框元素上
|
||||
addNodeTextStyleToTextEditNode(node) {
|
||||
const style = getNodeRichTextStyles(node)
|
||||
Object.keys(style).forEach(prop => {
|
||||
this.textEditNode.style[prop] = style[prop]
|
||||
})
|
||||
}
|
||||
|
||||
// 设置quill编辑器容器的最小高度
|
||||
setQuillContainerMinHeight(minHeight) {
|
||||
document.querySelector(
|
||||
'.' + CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP
|
||||
).style.minHeight = minHeight + 'px'
|
||||
}
|
||||
|
||||
// 更新文本编辑框的大小和位置
|
||||
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 =
|
||||
@@ -304,6 +325,7 @@ class RichText {
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.setQuillContainerMinHeight(originHeight)
|
||||
}
|
||||
|
||||
// 删除文本编辑框元素
|
||||
@@ -313,62 +335,18 @@ 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 = {
|
||||
font: node.style.merge('fontFamily'),
|
||||
color: node.style.merge('color'),
|
||||
italic: node.style.merge('fontStyle') === 'italic',
|
||||
bold: node.style.merge('fontWeight') === 'bold',
|
||||
size: node.style.merge('fontSize') + 'px',
|
||||
underline: node.style.merge('textDecoration') === 'underline',
|
||||
strike: node.style.merge('textDecoration') === 'line-through'
|
||||
}
|
||||
this.pureFormatAllText(style)
|
||||
}
|
||||
|
||||
// 获取当前正在编辑的内容
|
||||
getEditText() {
|
||||
let html = this.quill.container.firstChild.innerHTML
|
||||
// https://github.com/slab/quill/issues/4509
|
||||
return this.quill.container.firstChild.innerHTML.replaceAll(/ +/g, match =>
|
||||
' '.repeat(match.length)
|
||||
)
|
||||
// 去除ql-cursor节点
|
||||
// https://github.com/wanglin2/mind-map/commit/138cc4b3e824671143f0bf70e5c46796f48520d0
|
||||
// https://github.com/wanglin2/mind-map/commit/0760500cebe8ec4e8ad84ab63f877b8b2a193aa1
|
||||
// html = removeHtmlNodeByClass(html, '.ql-cursor')
|
||||
// 去除最后的空行
|
||||
return html.replace(/<p><br><\/p>$/, '')
|
||||
}
|
||||
|
||||
// 给html字符串中的节点样式按样式名首字母排序
|
||||
sortHtmlNodeStyles(html) {
|
||||
return html.replace(/(<[^<>]+\s+style=")([^"]+)("\s*>)/g, (_, a, b, c) => {
|
||||
let arr = b.match(/[^:]+:[^:]+;/g) || []
|
||||
arr = arr.map(item => {
|
||||
return item.trim()
|
||||
})
|
||||
arr.sort()
|
||||
return a + arr.join('') + c
|
||||
})
|
||||
// return html.replace(/<p><br><\/p>$/, '')
|
||||
}
|
||||
|
||||
// 隐藏文本编辑控件,即完成编辑
|
||||
@@ -380,10 +358,14 @@ class RichText {
|
||||
if (typeof beforeHideRichTextEdit === 'function') {
|
||||
beforeHideRichTextEdit(this)
|
||||
}
|
||||
let html = this.getEditText()
|
||||
html = this.sortHtmlNodeStyles(html)
|
||||
let list =
|
||||
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
|
||||
const html = this.getEditText()
|
||||
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 +374,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)
|
||||
}
|
||||
|
||||
@@ -459,6 +435,17 @@ class RichText {
|
||||
}
|
||||
}
|
||||
},
|
||||
formats: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strike',
|
||||
'color',
|
||||
'background',
|
||||
'font',
|
||||
'size',
|
||||
'formula'
|
||||
], // 明确指定允许的格式,不包含有序列表,无序列表等
|
||||
theme: 'snow'
|
||||
})
|
||||
// 拦截复制事件,即Ctrl + c,去除多余的空行
|
||||
@@ -511,18 +498,6 @@ class RichText {
|
||||
}
|
||||
})
|
||||
this.quill.on('text-change', () => {
|
||||
let contents = this.quill.getContents()
|
||||
let len = contents.ops.length
|
||||
// 如果编辑过程中删除所有字符,那么会丢失主题的样式
|
||||
if (len <= 0 || (len === 1 && contents.ops[0].insert === '\n')) {
|
||||
this.lostStyle = true
|
||||
// 需要删除节点的样式数据
|
||||
this.syncFormatToNodeConfig(null, true)
|
||||
} else if (this.lostStyle && !this.isCompositing) {
|
||||
// 如果处于样式丢失状态,那么需要进行格式化加回样式
|
||||
this.setTextStyleIfNotRichText(this.node)
|
||||
this.lostStyle = false
|
||||
}
|
||||
this.mindMap.emit('node_text_edit_change', {
|
||||
node: this.node,
|
||||
text: this.getEditText(),
|
||||
@@ -534,6 +509,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,7 +525,7 @@ class RichText {
|
||||
delta.ops = ops
|
||||
return delta
|
||||
})
|
||||
// 拦截图片的粘贴
|
||||
// 拦截图片的粘贴,当剪贴板里是纯图片,或文本+图片都可以拦截到,但是带来的问题是文本+图片时里面的文本也无法粘贴
|
||||
this.quill.root.addEventListener(
|
||||
'paste',
|
||||
e => {
|
||||
@@ -612,10 +588,6 @@ class RichText {
|
||||
return
|
||||
}
|
||||
this.isCompositing = false
|
||||
if (!this.lostStyle) {
|
||||
return
|
||||
}
|
||||
this.setTextStyleIfNotRichText(this.node)
|
||||
}
|
||||
|
||||
// 选中全部
|
||||
@@ -625,16 +597,15 @@ class RichText {
|
||||
|
||||
// 聚焦
|
||||
focus(start) {
|
||||
let len = this.quill.getLength()
|
||||
const len = this.quill.getLength()
|
||||
this.quill.setSelection(typeof start === 'number' ? start : len, len)
|
||||
}
|
||||
|
||||
// 格式化当前选中的文本
|
||||
formatText(config = {}, clear = false, pure = false) {
|
||||
formatText(config = {}, clear = false) {
|
||||
if (!this.range && !this.lastRange) return
|
||||
if (!pure) this.syncFormatToNodeConfig(config, clear)
|
||||
let rangeLost = !this.range
|
||||
let range = rangeLost ? this.lastRange : this.range
|
||||
const rangeLost = !this.range
|
||||
const range = rangeLost ? this.lastRange : this.range
|
||||
clear
|
||||
? this.quill.removeFormat(range.index, range.length)
|
||||
: this.quill.formatText(range.index, range.length, config)
|
||||
@@ -645,70 +616,25 @@ class RichText {
|
||||
|
||||
// 清除当前选中文本的样式
|
||||
removeFormat() {
|
||||
// 先移除全部样式
|
||||
this.formatText({}, true)
|
||||
// 再将样式恢复为当前主题改节点的默认样式
|
||||
const style = {}
|
||||
if (this.node) {
|
||||
;[
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
].forEach(key => {
|
||||
style[key] = this.node.style.merge(key)
|
||||
})
|
||||
}
|
||||
const config = this.normalStyleToRichTextStyle(style)
|
||||
this.formatText(config, false, true)
|
||||
}
|
||||
|
||||
// 格式化指定范围的文本
|
||||
formatRangeText(range, config = {}) {
|
||||
if (!range) return
|
||||
this.syncFormatToNodeConfig(config)
|
||||
this.quill.formatText(range.index, range.length, config)
|
||||
}
|
||||
|
||||
// 格式化所有文本
|
||||
formatAllText(config = {}) {
|
||||
this.syncFormatToNodeConfig(config)
|
||||
this.pureFormatAllText(config)
|
||||
}
|
||||
|
||||
// 纯粹的格式化所有文本
|
||||
pureFormatAllText(config = {}) {
|
||||
this.quill.formatText(0, this.quill.getLength(), config)
|
||||
}
|
||||
|
||||
// 同步格式化到节点样式配置
|
||||
syncFormatToNodeConfig(config, clear) {
|
||||
if (!this.node) return
|
||||
if (clear) {
|
||||
// 清除文本样式
|
||||
;[
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
].forEach(prop => {
|
||||
delete this.node.nodeData.data[prop]
|
||||
})
|
||||
} else {
|
||||
let data = this.richTextStyleToNormalStyle(config)
|
||||
this.mindMap.execCommand('SET_NODE_DATA', this.node, data)
|
||||
}
|
||||
}
|
||||
|
||||
// 将普通节点样式对象转换成富文本样式对象
|
||||
normalStyleToRichTextStyle(style) {
|
||||
let config = {}
|
||||
const config = {}
|
||||
Object.keys(style).forEach(prop => {
|
||||
let value = style[prop]
|
||||
const value = style[prop]
|
||||
switch (prop) {
|
||||
case 'fontFamily':
|
||||
config.font = value
|
||||
@@ -738,9 +664,9 @@ class RichText {
|
||||
|
||||
// 将富文本样式对象转换成普通节点样式对象
|
||||
richTextStyleToNormalStyle(config) {
|
||||
let data = {}
|
||||
const data = {}
|
||||
Object.keys(config).forEach(prop => {
|
||||
let value = config[prop]
|
||||
const value = config[prop]
|
||||
switch (prop) {
|
||||
case 'font':
|
||||
data.fontFamily = value
|
||||
@@ -770,40 +696,55 @@ class RichText {
|
||||
return data
|
||||
}
|
||||
|
||||
// 给未激活的节点设置富文本样式
|
||||
setNotActiveNodeStyle(node, style) {
|
||||
const config = this.normalStyleToRichTextStyle(style)
|
||||
if (Object.keys(config).length > 0) {
|
||||
this.showEditText({ node })
|
||||
this.formatAllText(config)
|
||||
this.hideEditText([node])
|
||||
}
|
||||
}
|
||||
|
||||
// 检查指定节点是否存在自定义的富文本样式
|
||||
checkNodeHasCustomRichTextStyle(node) {
|
||||
const list = [
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
]
|
||||
const nodeData = node instanceof MindMapNode ? node.getData() : node
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (nodeData[list[i]] !== undefined) {
|
||||
// 判断一个对象是否包含了富文本支持的样式字段
|
||||
isHasRichTextStyle(obj) {
|
||||
const keys = Object.keys(obj)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (richTextSupportStyleList.includes(key)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查指定节点是否存在自定义的富文本样式
|
||||
checkNodeHasCustomRichTextStyle(node) {
|
||||
const nodeData = node instanceof MindMapNode ? node.getData() : node
|
||||
for (let i = 0; i < richTextSupportStyleList.length; i++) {
|
||||
if (nodeData[richTextSupportStyleList[i]] !== undefined) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 转换数据后的渲染操作
|
||||
afterHandleData() {
|
||||
// 清空历史数据,并且触发数据变化
|
||||
this.mindMap.command.clearHistory()
|
||||
this.mindMap.command.addHistory()
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 插件实例化时处理思维导图数据,转换为富文本数据
|
||||
handleDataToRichTextOnInit() {
|
||||
// 处理数据,转成富文本格式
|
||||
if (this.mindMap.renderer.renderTree) {
|
||||
// 如果已经存在渲染树了,那么直接更新渲染树,并且触发重新渲染
|
||||
this.handleSetData(this.mindMap.renderer.renderTree)
|
||||
this.afterHandleData()
|
||||
} else if (this.mindMap.opt.data) {
|
||||
this.handleSetData(this.mindMap.opt.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 将所有节点转换成非富文本节点
|
||||
transformAllNodesToNormalNode() {
|
||||
if (!this.mindMap.renderer.renderTree) return
|
||||
const renderTree = this.mindMap.renderer.renderTree
|
||||
if (!renderTree) return
|
||||
walk(
|
||||
this.mindMap.renderer.renderTree,
|
||||
renderTree,
|
||||
null,
|
||||
node => {
|
||||
if (node.data.richText) {
|
||||
@@ -824,25 +765,35 @@ class RichText {
|
||||
0,
|
||||
0
|
||||
)
|
||||
// 清空历史数据,并且触发数据变化
|
||||
this.mindMap.command.clearHistory()
|
||||
this.mindMap.command.addHistory()
|
||||
this.mindMap.render(null, CONSTANTS.TRANSFORM_TO_NORMAL_NODE)
|
||||
this.afterHandleData()
|
||||
}
|
||||
|
||||
handleDataToRichText(data) {
|
||||
const oldIsRichText = data.richText
|
||||
data.richText = true
|
||||
data.resetRichText = true
|
||||
// 如果原本就是富文本,那么不能转换
|
||||
if (!oldIsRichText) {
|
||||
data.text = htmlEscape(data.text)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理导入数据
|
||||
handleSetData(data) {
|
||||
let walk = root => {
|
||||
if (root.data && !root.data.richText) {
|
||||
root.data.richText = true
|
||||
root.data.resetRichText = true
|
||||
// 短期处理,为了兼容老数据,长期会去除
|
||||
const isOldRichTextVersion =
|
||||
!data.smmVersion || compareVersion(data.smmVersion, '0.13.0') === '<'
|
||||
const walk = root => {
|
||||
if (root.data && (!root.data.richText || isOldRichTextVersion)) {
|
||||
this.handleDataToRichText(root.data)
|
||||
}
|
||||
// 概要
|
||||
if (root.data) {
|
||||
const generalizationList = formatGetNodeGeneralization(root.data)
|
||||
generalizationList.forEach(item => {
|
||||
item.richText = true
|
||||
item.resetRichText = true
|
||||
if (!item.richText || isOldRichTextVersion) {
|
||||
this.handleDataToRichText(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (root.children && root.children.length > 0) {
|
||||
@@ -860,6 +811,7 @@ class RichText {
|
||||
this.transformAllNodesToNormalNode()
|
||||
document.head.removeChild(this.styleEl)
|
||||
this.unbindEvent()
|
||||
this.mindMap.removeAppendCss('richText')
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
|
||||
@@ -107,6 +107,7 @@ class Search {
|
||||
|
||||
// 搜索匹配的节点
|
||||
doSearch() {
|
||||
this.clearHighlightOnReadonly()
|
||||
this.updateMatchNodeList([])
|
||||
this.currentIndex = -1
|
||||
const { isOnlySearchCurrentRenderNodes } = this.mindMap.opt
|
||||
@@ -174,14 +175,8 @@ class Search {
|
||||
}
|
||||
}
|
||||
const { readonly } = this.mindMap.opt
|
||||
// 只读模式下需要激活之前节点的高亮
|
||||
if (readonly) {
|
||||
this.matchNodeList.forEach(node => {
|
||||
if (this.isNodeInstance(node)) {
|
||||
node.closeHighlight()
|
||||
}
|
||||
})
|
||||
}
|
||||
// 只读模式下需要清除之前节点的高亮
|
||||
this.clearHighlightOnReadonly()
|
||||
const currentNode = this.matchNodeList[this.currentIndex]
|
||||
this.notResetSearchText = true
|
||||
const uid = this.isNodeInstance(currentNode)
|
||||
@@ -205,6 +200,18 @@ class Search {
|
||||
})
|
||||
}
|
||||
|
||||
// 只读模式下清除现有匹配节点的高亮
|
||||
clearHighlightOnReadonly() {
|
||||
const { readonly } = this.mindMap.opt
|
||||
if (readonly) {
|
||||
this.matchNodeList.forEach(node => {
|
||||
if (this.isNodeInstance(node)) {
|
||||
node.closeHighlight()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 定位到指定搜索结果索引的节点
|
||||
jump(index, callback = () => {}) {
|
||||
this.searchNext(callback, index)
|
||||
@@ -224,9 +231,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)
|
||||
currentNode.setText(text, currentNode.getData('richText'))
|
||||
if (keep) {
|
||||
this.updateMatchNodeList(this.matchNodeList)
|
||||
return
|
||||
}
|
||||
const newList = this.matchNodeList.filter(node => {
|
||||
return currentNode !== node
|
||||
})
|
||||
@@ -249,25 +262,27 @@ class Search {
|
||||
)
|
||||
return
|
||||
replaceText = String(replaceText)
|
||||
// 如果当前搜索文本是替换文本的子串,那么该节点还是符合搜索结果的
|
||||
const keep = replaceText.includes(this.searchText)
|
||||
this.notResetSearchText = true
|
||||
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
|
||||
}
|
||||
this.mindMap.renderer.setNodeDataRender(node, data, true)
|
||||
} else {
|
||||
node.data.text = text
|
||||
node.data.resetRichText = !!node.data.richText
|
||||
}
|
||||
})
|
||||
this.mindMap.render()
|
||||
this.mindMap.command.addHistory()
|
||||
this.endSearch()
|
||||
if (keep) {
|
||||
this.updateMatchNodeList(this.matchNodeList)
|
||||
} else {
|
||||
this.endSearch()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取某个节点替换后的文本
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
} from './associativeLineUtils'
|
||||
|
||||
// 创建控制点、连线节点
|
||||
function createControlNodes() {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
function createControlNodes(node, toNode) {
|
||||
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
|
||||
// 连线
|
||||
this.controlLine1 = this.associativeLineDraw
|
||||
.line()
|
||||
@@ -16,13 +16,13 @@ function createControlNodes() {
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
// 控制点
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1')
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2')
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1', node, toNode)
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2', node, toNode)
|
||||
}
|
||||
|
||||
// 创建控制点
|
||||
function createOneControlNode(pointKey) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
function createOneControlNode(pointKey, node, toNode) {
|
||||
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
|
||||
return this.associativeLineDraw
|
||||
.circle(this.controlPointDiameter)
|
||||
.stroke({ color: associativeLineActiveColor })
|
||||
@@ -202,6 +202,7 @@ function onControlPointMouseup(e) {
|
||||
associativeLineTargetControlOffsets: offsetList,
|
||||
associativeLinePoint
|
||||
})
|
||||
this.isNotRenderAllLines = true
|
||||
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||
setTimeout(() => {
|
||||
this.resetControlPoint()
|
||||
@@ -221,10 +222,10 @@ function resetControlPoint() {
|
||||
}
|
||||
|
||||
// 渲染控制点
|
||||
function renderControls(startPoint, endPoint, point1, point2) {
|
||||
function renderControls(startPoint, endPoint, point1, point2, node, toNode) {
|
||||
if (!this.mindMap.opt.enableAdjustAssociativeLinePoints) return
|
||||
if (!this.controlLine1) {
|
||||
this.createControlNodes()
|
||||
this.createControlNodes(node, toNode)
|
||||
}
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 控制点和起终点的连线
|
||||
|
||||
@@ -43,6 +43,7 @@ function showEditTextBox(g) {
|
||||
// 输入框元素没有创建过,则先创建
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.className = 'associative-line-text-edit-warp'
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
||||
this.textEditNode.setAttribute('contenteditable', true)
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
@@ -54,14 +55,14 @@ function showEditTextBox(g) {
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
let {
|
||||
associativeLineTextFontSize,
|
||||
associativeLineTextFontFamily,
|
||||
associativeLineTextLineHeight
|
||||
} = this.mindMap.themeConfig
|
||||
} = this.getStyleConfig(node, toNode)
|
||||
let { defaultAssociativeLineText, nodeTextEditZIndex } = this.mindMap.opt
|
||||
let scale = this.mindMap.view.scale
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
let text = this.getText(node, toNode)
|
||||
let textLines = (text || defaultAssociativeLineText).split(/\n/gim)
|
||||
this.textEditNode.style.fontFamily = associativeLineTextFontFamily
|
||||
@@ -124,7 +125,7 @@ function hideEditTextBox() {
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.showTextEdit = false
|
||||
this.renderText(str, path, text)
|
||||
this.renderText(str, path, text, node, toNode)
|
||||
this.mindMap.emit('hide_text_edit')
|
||||
}
|
||||
|
||||
@@ -138,35 +139,41 @@ function getText(node, toNode) {
|
||||
}
|
||||
|
||||
// 渲染关联线文字
|
||||
function renderText(str, path, text) {
|
||||
function renderText(str, path, text, node, toNode) {
|
||||
if (!str) return
|
||||
let { associativeLineTextFontSize, associativeLineTextLineHeight } =
|
||||
this.mindMap.themeConfig
|
||||
this.getStyleConfig(node, toNode)
|
||||
text.clear()
|
||||
let textArr = str.split(/\n/gim)
|
||||
let textArr = str.replace(/\n$/g, '').split(/\n/gim)
|
||||
textArr.forEach((item, index) => {
|
||||
let node = new Text().text(item)
|
||||
node.y(associativeLineTextFontSize * associativeLineTextLineHeight * index)
|
||||
this.styleText(node)
|
||||
text.add(node)
|
||||
// 避免尾部的空行不占宽度,导致文本编辑框定位异常的问题
|
||||
if (item === '') {
|
||||
item = ''
|
||||
}
|
||||
let textNode = new Text().text(item)
|
||||
textNode.y(
|
||||
associativeLineTextFontSize * associativeLineTextLineHeight * index
|
||||
)
|
||||
this.styleText(textNode, node, toNode)
|
||||
text.add(textNode)
|
||||
})
|
||||
updateTextPos(path, text)
|
||||
}
|
||||
|
||||
// 给文本设置样式
|
||||
function styleText(node) {
|
||||
function styleText(textNode, node, toNode) {
|
||||
let {
|
||||
associativeLineTextColor,
|
||||
associativeLineTextFontSize,
|
||||
associativeLineTextFontFamily
|
||||
} = this.mindMap.themeConfig
|
||||
node
|
||||
} = this.getStyleConfig(node, toNode)
|
||||
textNode
|
||||
.fill({
|
||||
color: associativeLineTextColor
|
||||
})
|
||||
.css({
|
||||
'font-family': associativeLineTextFontFamily,
|
||||
'font-size': associativeLineTextFontSize
|
||||
'font-size': associativeLineTextFontSize + 'px'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// 默认主题
|
||||
|
||||
export default {
|
||||
// 节点内边距
|
||||
paddingX: 15,
|
||||
paddingY: 5,
|
||||
// 图片显示的最大宽度
|
||||
imgMaxWidth: 100,
|
||||
imgMaxWidth: 200,
|
||||
// 图片显示的最大高度
|
||||
imgMaxHeight: 100,
|
||||
// icon的大小
|
||||
@@ -16,6 +15,12 @@ export default {
|
||||
lineColor: '#549688',
|
||||
// 连线样式
|
||||
lineDasharray: 'none',
|
||||
// 连线是否开启流动效果,仅在虚线时有效(需要注册LineFlow插件)
|
||||
lineFlow: false,
|
||||
// 流动效果一个周期的时间,单位:s
|
||||
lineFlowDuration: 1,
|
||||
// 流动方向是否是从父节点到子节点
|
||||
lineFlowForward: true,
|
||||
// 连线风格
|
||||
lineStyle: 'straight', // 曲线(curve)【仅支持logicalStructure、mindMap、verticalTimeline三种结构】、直线(straight)、直连(direct)【仅支持logicalStructure、mindMap、organizationStructure、verticalTimeline四种结构】
|
||||
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型,设为true后,都为 { 型。仅支持logicalStructure、mindMap两种结构
|
||||
@@ -73,7 +78,6 @@ export default {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
borderColor: 'transparent',
|
||||
borderWidth: 0,
|
||||
borderDasharray: 'none',
|
||||
@@ -90,8 +94,16 @@ export default {
|
||||
hoverRectColor: '',
|
||||
// 点鼠标hover和激活时显示的矩形边框的圆角大小
|
||||
hoverRectRadius: 5
|
||||
// paddingX: 15,
|
||||
// paddingY: 5
|
||||
// 下列样式也支持给节点设置,用于覆盖最外层的设置
|
||||
// paddingX,
|
||||
// paddingY,
|
||||
// lineWidth,
|
||||
// lineColor,
|
||||
// lineDasharray,
|
||||
// lineFlow,
|
||||
// lineFlowDuration,
|
||||
// lineFlowForward
|
||||
// 关联线的所有样式
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
@@ -104,7 +116,6 @@ export default {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
borderColor: '#549688',
|
||||
borderWidth: 1,
|
||||
borderDasharray: 'none',
|
||||
@@ -118,8 +129,6 @@ export default {
|
||||
lineMarkerDir: 'end',
|
||||
hoverRectColor: '',
|
||||
hoverRectRadius: 5
|
||||
// paddingX: 15,
|
||||
// paddingY: 5
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
@@ -132,7 +141,6 @@ export default {
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
borderColor: 'transparent',
|
||||
borderWidth: 0,
|
||||
borderRadius: 5,
|
||||
@@ -146,8 +154,6 @@ export default {
|
||||
lineMarkerDir: 'end',
|
||||
hoverRectColor: '',
|
||||
hoverRectRadius: 5
|
||||
// paddingX: 15,
|
||||
// paddingY: 5
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
@@ -160,7 +166,6 @@ export default {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
borderColor: '#549688',
|
||||
borderWidth: 1,
|
||||
borderDasharray: 'none',
|
||||
@@ -173,8 +178,6 @@ export default {
|
||||
endDir: [1, 0],
|
||||
hoverRectColor: '',
|
||||
hoverRectRadius: 5
|
||||
// paddingX: 15,
|
||||
// paddingY: 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,14 +205,12 @@ const nodeSizeIndependenceList = [
|
||||
'rootLineKeepSameInCurve',
|
||||
'rootLineStartPositionKeepSameInCurve',
|
||||
'showLineMarker',
|
||||
'gradientStyle',
|
||||
'lineRadius',
|
||||
'startColor',
|
||||
'endColor',
|
||||
'startDir',
|
||||
'endDir',
|
||||
'hoverRectColor',
|
||||
'hoverRectRadius'
|
||||
'hoverRectRadius',
|
||||
'lineFlow',
|
||||
'lineFlowDuration',
|
||||
'lineFlowForward'
|
||||
]
|
||||
export const checkIsNodeSizeIndependenceConfig = config => {
|
||||
let keys = Object.keys(config)
|
||||
@@ -225,9 +226,13 @@ export const checkIsNodeSizeIndependenceConfig = config => {
|
||||
return true
|
||||
}
|
||||
|
||||
// 连线的样式
|
||||
export const lineStyleProps = [
|
||||
'lineColor',
|
||||
'lineDasharray',
|
||||
'lineWidth',
|
||||
'lineMarkerDir'
|
||||
'lineMarkerDir',
|
||||
'lineFlow',
|
||||
'lineFlowDuration',
|
||||
'lineFlowForward'
|
||||
]
|
||||
5
simple-mind-map/src/theme/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import defaultTheme from './default'
|
||||
|
||||
export default {
|
||||
default: defaultTheme
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 秋天
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 牛油果
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 黑金
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 黑色幽默
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 天空蓝
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 脑残粉
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 脑图经典
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 经典2
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,46 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 经典3
|
||||
export default mergeTheme(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
|
||||
}
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 经典4
|
||||
export default mergeTheme(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
|
||||
}
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 经典蓝
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,39 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 经典绿
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 咖啡
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 课程绿
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 暗色
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 暗色2
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 泥土黄
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,31 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 清新绿
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 清新红
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 金色vip
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 绿叶
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 深夜办公室
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 小黄人
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 薄荷
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 橙汁
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 粉红葡萄
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 红色精神
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 浪漫紫
|
||||
export default mergeTheme(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'
|
||||
}
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 简约黑
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 天清绿
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
import defaultTheme from './default'
|
||||
import { mergeTheme } from '../utils'
|
||||
|
||||
// 活力橙
|
||||
export default mergeTheme(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)'
|
||||
}
|
||||
})
|
||||
@@ -1,11 +1,13 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import {
|
||||
nodeDataNoStylePropList,
|
||||
selfCloseTagList
|
||||
selfCloseTagList,
|
||||
richTextSupportStyleList
|
||||
} from '../constants/constant'
|
||||
import MersenneTwister from './mersenneTwister'
|
||||
import { ForeignObject } from '@svgdotjs/svg.js'
|
||||
import merge from 'deepmerge'
|
||||
import { lineStyleProps } from '../theme/default'
|
||||
|
||||
// 深度优先遍历树
|
||||
export const walk = (
|
||||
@@ -76,11 +78,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
|
||||
}
|
||||
@@ -95,11 +97,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) {
|
||||
@@ -172,6 +174,12 @@ export const copyRenderTree = (tree, root, removeActiveState = false) => {
|
||||
tree.children[index] = copyRenderTree({}, item, removeActiveState)
|
||||
})
|
||||
}
|
||||
// data、children外的其他字段
|
||||
Object.keys(root).forEach(key => {
|
||||
if (!['data', 'children'].includes(key) && !/^_/.test(key)) {
|
||||
tree[key] = root[key]
|
||||
}
|
||||
})
|
||||
return tree
|
||||
}
|
||||
|
||||
@@ -182,7 +190,8 @@ export const copyNodeTree = (
|
||||
removeActiveState = false,
|
||||
removeId = true
|
||||
) => {
|
||||
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
|
||||
const rootData = root.nodeData ? root.nodeData : root
|
||||
tree.data = simpleDeepClone(rootData.data)
|
||||
// 移除节点uid
|
||||
if (removeId) {
|
||||
delete tree.data.uid
|
||||
@@ -207,6 +216,12 @@ export const copyNodeTree = (
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState, removeId)
|
||||
})
|
||||
}
|
||||
// data、children外的其他字段
|
||||
Object.keys(rootData).forEach(key => {
|
||||
if (!['data', 'children'].includes(key) && !/^_/.test(key)) {
|
||||
tree[key] = rootData[key]
|
||||
}
|
||||
})
|
||||
return tree
|
||||
}
|
||||
|
||||
@@ -276,6 +291,21 @@ export const throttle = (fn, time = 300, ctx) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 防抖函数
|
||||
export const debounce = (fn, wait = 300, ctx) => {
|
||||
let timeout = null
|
||||
|
||||
return (...args) => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
const callNow = !timeout
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null
|
||||
fn.apply(ctx, args)
|
||||
}, wait)
|
||||
if (callNow) fn.apply(ctx, args)
|
||||
}
|
||||
}
|
||||
|
||||
// 异步执行任务队列
|
||||
export const asyncRun = (taskList, callback = () => {}) => {
|
||||
let index = 0
|
||||
@@ -480,7 +510,7 @@ export const loadImage = imgFile => {
|
||||
|
||||
// 移除字符串中的html实体
|
||||
export const removeHTMLEntities = str => {
|
||||
;[[' ', ' ']].forEach(item => {
|
||||
[[' ', ' ']].forEach(item => {
|
||||
str = str.replaceAll(item[0], item[1])
|
||||
})
|
||||
return str
|
||||
@@ -507,13 +537,14 @@ export const addHtmlStyle = (html, tag, style) => {
|
||||
if (!addHtmlStyleEl) {
|
||||
addHtmlStyleEl = document.createElement('div')
|
||||
}
|
||||
const tags = Array.isArray(tag) ? tag : [tag]
|
||||
addHtmlStyleEl.innerHTML = html
|
||||
let walk = root => {
|
||||
let childNodes = root.childNodes
|
||||
childNodes.forEach(node => {
|
||||
if (node.nodeType === 1) {
|
||||
// 元素节点
|
||||
if (node.tagName.toLowerCase() === tag) {
|
||||
if (tags.includes(node.tagName.toLowerCase())) {
|
||||
node.style.cssText = style
|
||||
} else {
|
||||
walk(node)
|
||||
@@ -790,6 +821,18 @@ export const checkIsNodeStyleDataKey = key => {
|
||||
return false
|
||||
}
|
||||
|
||||
// 判断一个对象是否不需要触发节点重新创建
|
||||
export const isNodeNotNeedRenderData = config => {
|
||||
const list = [...lineStyleProps] // 节点连线样式
|
||||
const keys = Object.keys(config)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (!list.includes(keys[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 合并图标数组
|
||||
// const data = [
|
||||
// { type: 'priority', name: '优先级图标', list: [{ name: '1', icon: 'a' }, { name: 2, icon: 'b' }] },
|
||||
@@ -935,6 +978,12 @@ export const selectAllInput = el => {
|
||||
|
||||
// 给指定的节点列表树数据添加附加数据,会修改原数据
|
||||
export const addDataToAppointNodes = (appointNodes, data = {}) => {
|
||||
data = { ...data }
|
||||
const alreadyIsRichText = data && data.richText
|
||||
// 如果指定的数据就是富文本格式,那么不需要重新创建
|
||||
if (alreadyIsRichText && data.resetRichText) {
|
||||
delete data.resetRichText
|
||||
}
|
||||
const walk = list => {
|
||||
list.forEach(node => {
|
||||
node.data = {
|
||||
@@ -1013,7 +1062,7 @@ export const generateColorByContent = str => {
|
||||
|
||||
// html转义
|
||||
export const htmlEscape = str => {
|
||||
;[
|
||||
[
|
||||
['&', '&'],
|
||||
['<', '<'],
|
||||
['>', '>']
|
||||
@@ -1069,9 +1118,14 @@ export const isSameObject = (a, b) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查navigator.clipboard对象的读取是否可用
|
||||
export const checkClipboardReadEnable = () => {
|
||||
return navigator.clipboard && typeof navigator.clipboard.read === 'function'
|
||||
}
|
||||
|
||||
// 将数据设置到用户剪切板中
|
||||
export const setDataToClipboard = data => {
|
||||
if (navigator.clipboard) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(JSON.stringify(data))
|
||||
}
|
||||
}
|
||||
@@ -1080,7 +1134,7 @@ export const setDataToClipboard = data => {
|
||||
export const getDataFromClipboard = async () => {
|
||||
let text = null
|
||||
let img = null
|
||||
if (navigator.clipboard) {
|
||||
if (checkClipboardReadEnable()) {
|
||||
const items = await navigator.clipboard.read()
|
||||
if (items && items.length > 0) {
|
||||
for (const clipboardItem of items) {
|
||||
@@ -1182,6 +1236,8 @@ export const handleInputPasteText = (e, text) => {
|
||||
if (!selection.rangeCount) return
|
||||
selection.deleteFromDocument()
|
||||
text = text || e.clipboardData.getData('text')
|
||||
// 转义特殊字符
|
||||
text = htmlEscape(text)
|
||||
// 去除格式
|
||||
text = getTextFromHtml(text)
|
||||
// 去除换行
|
||||
@@ -1620,3 +1676,38 @@ export const mergeTheme = (dest, source) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取节点实例的文本样式数据
|
||||
export const getNodeRichTextStyles = node => {
|
||||
const res = {}
|
||||
richTextSupportStyleList.forEach(prop => {
|
||||
let value = node.style.merge(prop)
|
||||
if (prop === 'fontSize') {
|
||||
value = value + 'px'
|
||||
}
|
||||
res[prop] = value
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 判断两个版本号的关系
|
||||
/*
|
||||
a > b 返回 >
|
||||
a < b 返回 <
|
||||
a = b 返回 =
|
||||
*/
|
||||
export const compareVersion = (a, b) => {
|
||||
const aArr = String(a).split('.')
|
||||
const bArr = String(b).split('.')
|
||||
const max = Math.max(aArr.length, bArr.length)
|
||||
for (let i = 0; i < max; i++) {
|
||||
const ai = aArr[i] || 0
|
||||
const bi = bArr[i] || 0
|
||||
if (ai > bi) {
|
||||
return '>'
|
||||
} else if (ai < bi) {
|
||||
return '<'
|
||||
}
|
||||
}
|
||||
return '='
|
||||
}
|
||||
|
||||
63
web/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -9,24 +9,6 @@ const SIMPLE_MIND_MAP_LOCAL_CONFIG = 'SIMPLE_MIND_MAP_LOCAL_CONFIG'
|
||||
|
||||
let mindMapData = null
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-02 22:36:48
|
||||
* @Desc: 克隆思维导图数据,去除激活状态
|
||||
*/
|
||||
const copyMindMapTreeData = (tree, root) => {
|
||||
if (!root) return null
|
||||
tree.data = simpleDeepClone(root.data)
|
||||
// tree.data.isActive = false
|
||||
tree.children = []
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((item, index) => {
|
||||
tree.children[index] = copyMindMapTreeData({}, item)
|
||||
})
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-01 10:10:49
|
||||
@@ -65,7 +47,7 @@ export const storeData = data => {
|
||||
} else {
|
||||
originData = getData()
|
||||
}
|
||||
originData.root = copyMindMapTreeData({}, data)
|
||||
originData.root = data
|
||||
if (window.takeOverApp) {
|
||||
mindMapData = originData
|
||||
window.takeOverAppMethods.saveMindMapData(originData)
|
||||
|
||||
BIN
web/src/assets/avatar/4399行星元帅.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
web/src/assets/avatar/Xavier.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
web/src/assets/avatar/h.r.w.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
web/src/assets/avatar/xbkkjbs0246658.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
web/src/assets/avatar/一亩三.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
web/src/assets/avatar/冒号括号.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
web/src/assets/avatar/广兴.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
web/src/assets/avatar/御风.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
web/src/assets/avatar/时光匆匆.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
web/src/assets/avatar/桌案.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
web/src/assets/avatar/梁辉.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
web/src/assets/avatar/海云.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
web/src/assets/avatar/皮老板.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web/src/assets/avatar/黄智彪@一米一栗科技.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |