Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fe94bfc21 | ||
|
|
6be6f6ee8a | ||
|
|
83ce090402 | ||
|
|
250ff30704 | ||
|
|
55d7d0a846 | ||
|
|
0b3d2cedbd | ||
|
|
de025d9bc7 | ||
|
|
79a39f993d | ||
|
|
0fa731c3a2 | ||
|
|
34eece8fcf | ||
|
|
790662ad0c | ||
|
|
7d83a3635f | ||
|
|
546cf27b33 | ||
|
|
e59d419708 | ||
|
|
2d50106ce7 | ||
|
|
70c32e3c74 | ||
|
|
a0f56473ee | ||
|
|
51dcb1f54f | ||
|
|
9e34fd6174 | ||
|
|
3b47d11b21 | ||
|
|
6e91df9d03 | ||
|
|
4c8f1bd69c | ||
|
|
1f993518fd | ||
|
|
7f9a1e9309 | ||
|
|
5df4a7edb8 | ||
|
|
ddd578d773 | ||
|
|
c8e63e433c | ||
|
|
cadd159a46 | ||
|
|
d32588763c | ||
|
|
6723250266 | ||
|
|
41ca8f8e46 | ||
|
|
a6d04ffa91 | ||
|
|
631892d785 | ||
|
|
3d7f0fcbe7 | ||
|
|
c716ec7294 | ||
|
|
4db6bda193 | ||
|
|
bc2a5b214f | ||
|
|
879de57b49 | ||
|
|
0d4cbc7344 | ||
|
|
217f5ee95d | ||
|
|
ad98c0f229 | ||
|
|
9a7f827301 | ||
|
|
211fa183d3 | ||
|
|
c0e0fb23e4 | ||
|
|
e0d46055a7 | ||
|
|
a1ec72b401 | ||
|
|
ffc7bf5b7e | ||
|
|
4e6e0e2221 | ||
|
|
a1bbe543ba | ||
|
|
48b5ff5f1c | ||
|
|
13dc61a585 | ||
|
|
6e476489f5 | ||
|
|
e6b82db674 | ||
|
|
4c60c52a8f | ||
|
|
29ed8f7a8d | ||
|
|
4b880e13a5 | ||
|
|
b5a08a2414 | ||
|
|
57e9379f49 | ||
|
|
134fd69ffa | ||
|
|
e2830ccfb1 | ||
|
|
b0dd90f5b3 | ||
|
|
32fc6937d2 | ||
|
|
23a3e26800 | ||
|
|
c7b0cbc128 | ||
|
|
9915479354 | ||
|
|
abddafa3cf | ||
|
|
5b057ff9de | ||
|
|
6dcbc0604d | ||
|
|
0e5fed6645 | ||
|
|
b6d91ec07a | ||
|
|
baf36acc97 | ||
|
|
e0db4ad4f6 | ||
|
|
4fc2d79616 | ||
|
|
9e4652b7b5 | ||
|
|
8466a3e99f | ||
|
|
e85f187199 | ||
|
|
77167c572d | ||
|
|
9aae8bf55c | ||
|
|
250fb2eb50 | ||
|
|
2a49dd9140 | ||
|
|
4dedaaea3b | ||
|
|
7bc666be36 | ||
|
|
93a56ef4ee | ||
|
|
83b916d3c9 | ||
|
|
20157fcc8d | ||
|
|
2c3fb4d7ea | ||
|
|
75ad40ffbc | ||
|
|
e263eb8252 | ||
|
|
8e43cd609f | ||
|
|
1a3401fd1a | ||
|
|
d1dcef2537 | ||
|
|
e732415aa3 | ||
|
|
ddbde0141a | ||
|
|
bc907f4b37 | ||
|
|
1caf2c7f15 | ||
|
|
22b56fb8dc | ||
|
|
87eccc298c | ||
|
|
ca9e47183d | ||
|
|
1fbfe6f5ac | ||
|
|
84d2a374d1 | ||
|
|
74a000723b | ||
|
|
5079ad2190 | ||
|
|
21053c43c9 | ||
|
|
4c6270881a | ||
|
|
c17e5430ed | ||
|
|
0a36555343 | ||
|
|
bce2bb8fc4 | ||
|
|
d6ae06dbd6 | ||
|
|
9221c404ee | ||
|
|
697e53ff7d | ||
|
|
9360aff6c9 | ||
|
|
c68d629b7a | ||
|
|
caedfb46a9 | ||
|
|
2e4c6bc08e | ||
|
|
777eafcd2f | ||
|
|
20780a0c59 | ||
|
|
5d433cce16 | ||
|
|
ba77fde93b | ||
|
|
45b8850493 | ||
|
|
39c2c15259 | ||
|
|
6d780c6c26 | ||
|
|
4cf66adc18 | ||
|
|
7bff14e1bb | ||
|
|
7986e0d0cc | ||
|
|
a316d0f0fe | ||
|
|
88fa6225eb | ||
|
|
7ec720823f | ||
|
|
c39daf72b4 | ||
|
|
9c4e72eb29 | ||
|
|
88e3c1f660 | ||
|
|
e6c92d4a5e | ||
|
|
745531f20f | ||
|
|
3acd425c09 | ||
|
|
8dcc7c985d | ||
|
|
253ded33bf | ||
|
|
2c6b8294f4 | ||
|
|
83a5ef8e2e | ||
|
|
b959e90723 | ||
|
|
89ebc9a1fa | ||
|
|
56d2e34fbd | ||
|
|
0f2aed7e8a | ||
|
|
288ceafa92 | ||
|
|
99dc443142 | ||
|
|
b6440eba1a | ||
|
|
545e46babc | ||
|
|
b95b6af1b1 | ||
|
|
ccef5fc581 | ||
|
|
ed82fe5a61 | ||
|
|
1550f032d9 | ||
|
|
7d2758a21c | ||
|
|
1beb03eaa6 |
45
README.md
@@ -39,6 +39,7 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`、`xmind`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条
|
||||
- [x] 提供丰富的配置,满足各种场景各种使用习惯
|
||||
- [x] 支持协同编辑
|
||||
|
||||
# 安装
|
||||
|
||||
@@ -93,11 +94,11 @@ const mindMap = new MindMap({
|
||||
|
||||
# 请作者喝杯咖啡
|
||||
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡哟~
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~
|
||||
|
||||
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
|
||||
|
||||
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。你的头像和名字将会出现在下面和[文档页面](https://wanglin2.github.io/mind-map/#/doc/zh/introduction/%E8%AF%B7%E4%BD%9C%E8%80%85%E5%96%9D%E6%9D%AF%E5%92%96%E5%95%A1)
|
||||
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
|
||||
|
||||
<p>
|
||||
<img src="./web/src/assets/img/alipay.jpg" style="width: 300px" />
|
||||
@@ -189,4 +190,44 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/沐风牧草.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>沐风牧草</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/有希.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>有希</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/樊笼.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>樊笼</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/达仁科技.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>达仁科技</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/小逗比.png" style="width: 50px;height: 50px;" />
|
||||
<span>小逗比</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/天清如愿.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>天清如愿</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/敬明朗.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>敬明朗</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>飞箭</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/戚永峰.png" style="width: 50px;height: 50px;" />
|
||||
<span>戚永峰</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/moom.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>moom</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/张扬.png" style="width: 50px;height: 50px;" />
|
||||
<span>张扬</span>
|
||||
</span>
|
||||
</p>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
|
||||
window.externalPublicPath = './dist/'
|
||||
// 接管应用
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?009f4c7305e0f9f13b31" rel="stylesheet"><link href="dist/css/app.css?009f4c7305e0f9f13b31" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?7820598bfce2a2653e5c" rel="stylesheet"><link href="dist/css/app.css?7820598bfce2a2653e5c" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
@@ -66,4 +66,4 @@
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}</script><script src="dist/js/chunk-vendors.js?009f4c7305e0f9f13b31"></script><script src="dist/js/app.js?009f4c7305e0f9f13b31"></script></body></html>
|
||||
}</script><script src="dist/js/chunk-vendors.js?7820598bfce2a2653e5c"></script><script src="dist/js/app.js?7820598bfce2a2653e5c"></script></body></html>
|
||||
152
simple-mind-map/bin/wsServer.mjs
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import ws from 'ws'
|
||||
import http from 'http'
|
||||
import * as map from 'lib0/map'
|
||||
|
||||
const wsReadyStateConnecting = 0
|
||||
const wsReadyStateOpen = 1
|
||||
const wsReadyStateClosing = 2 // eslint-disable-line
|
||||
const wsReadyStateClosed = 3 // eslint-disable-line
|
||||
|
||||
const pingTimeout = 30000
|
||||
|
||||
const port = process.env.PORT || 4444
|
||||
// @ts-ignore
|
||||
const wss = new ws.Server({ noServer: true })
|
||||
|
||||
const server = http.createServer((request, response) => {
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' })
|
||||
response.end('okay')
|
||||
})
|
||||
|
||||
/**
|
||||
* Map froms topic-name to set of subscribed clients.
|
||||
* @type {Map<string, Set<any>>}
|
||||
*/
|
||||
const topics = new Map()
|
||||
|
||||
/**
|
||||
* @param {any} conn
|
||||
* @param {object} message
|
||||
*/
|
||||
const send = (conn, message) => {
|
||||
if (
|
||||
conn.readyState !== wsReadyStateConnecting &&
|
||||
conn.readyState !== wsReadyStateOpen
|
||||
) {
|
||||
conn.close()
|
||||
}
|
||||
try {
|
||||
conn.send(JSON.stringify(message))
|
||||
} catch (e) {
|
||||
conn.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a new client
|
||||
* @param {any} conn
|
||||
*/
|
||||
const onconnection = conn => {
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const subscribedTopics = new Set()
|
||||
let closed = false
|
||||
// Check if connection is still alive
|
||||
let pongReceived = true
|
||||
const pingInterval = setInterval(() => {
|
||||
if (!pongReceived) {
|
||||
conn.close()
|
||||
clearInterval(pingInterval)
|
||||
} else {
|
||||
pongReceived = false
|
||||
try {
|
||||
conn.ping()
|
||||
} catch (e) {
|
||||
conn.close()
|
||||
}
|
||||
}
|
||||
}, pingTimeout)
|
||||
conn.on('pong', () => {
|
||||
pongReceived = true
|
||||
})
|
||||
conn.on('close', () => {
|
||||
subscribedTopics.forEach(topicName => {
|
||||
const subs = topics.get(topicName) || new Set()
|
||||
subs.delete(conn)
|
||||
if (subs.size === 0) {
|
||||
topics.delete(topicName)
|
||||
}
|
||||
})
|
||||
subscribedTopics.clear()
|
||||
closed = true
|
||||
})
|
||||
conn.on(
|
||||
'message',
|
||||
/** @param {object} message */ message => {
|
||||
if (typeof message === 'string') {
|
||||
message = JSON.parse(message)
|
||||
}
|
||||
if (message && message.type && !closed) {
|
||||
switch (message.type) {
|
||||
case 'subscribe':
|
||||
/** @type {Array<string>} */ ;(message.topics || []).forEach(
|
||||
topicName => {
|
||||
if (typeof topicName === 'string') {
|
||||
// add conn to topic
|
||||
const topic = map.setIfUndefined(
|
||||
topics,
|
||||
topicName,
|
||||
() => new Set()
|
||||
)
|
||||
topic.add(conn)
|
||||
// add topic to conn
|
||||
subscribedTopics.add(topicName)
|
||||
}
|
||||
}
|
||||
)
|
||||
break
|
||||
case 'unsubscribe':
|
||||
/** @type {Array<string>} */ ;(message.topics || []).forEach(
|
||||
topicName => {
|
||||
const subs = topics.get(topicName)
|
||||
if (subs) {
|
||||
subs.delete(conn)
|
||||
}
|
||||
}
|
||||
)
|
||||
break
|
||||
case 'publish':
|
||||
if (message.topic) {
|
||||
const receivers = topics.get(message.topic)
|
||||
if (receivers) {
|
||||
message.clients = receivers.size
|
||||
receivers.forEach(receiver => send(receiver, message))
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'ping':
|
||||
send(conn, { type: 'pong' })
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
wss.on('connection', onconnection)
|
||||
|
||||
server.on('upgrade', (request, socket, head) => {
|
||||
// You may check auth of request here..
|
||||
/**
|
||||
* @param {any} ws
|
||||
*/
|
||||
const handleAuth = ws => {
|
||||
wss.emit('connection', ws, request)
|
||||
}
|
||||
wss.handleUpgrade(request, socket, head, handleAuth)
|
||||
})
|
||||
|
||||
server.listen(port)
|
||||
|
||||
console.log('Signaling server running on localhost:', port)
|
||||
@@ -35,21 +35,16 @@ class MindMap {
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
if (!this.el) throw new Error('缺少容器元素el')
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
|
||||
// 画布宽高
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
if (this.width <= 0 || this.height <= 0)
|
||||
throw new Error('容器元素el的宽高不能为0')
|
||||
// 获取容器尺寸位置信息
|
||||
this.getElRectInfo()
|
||||
|
||||
// 添加css
|
||||
this.cssEl = null
|
||||
this.addCss()
|
||||
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.draw = this.svg.group()
|
||||
this.initContainer()
|
||||
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
@@ -79,8 +74,7 @@ class MindMap {
|
||||
|
||||
// 视图操作类
|
||||
this.view = new View({
|
||||
mindMap: this,
|
||||
draw: this.draw
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 批量执行类
|
||||
@@ -111,6 +105,46 @@ class MindMap {
|
||||
return opt
|
||||
}
|
||||
|
||||
// 创建容器元素
|
||||
initContainer() {
|
||||
const { associativeLineIsAlwaysAboveNode } = this.opt
|
||||
// 节点关联线容器
|
||||
const createAssociativeLineDraw = () => {
|
||||
this.associativeLineDraw = this.draw.group()
|
||||
this.associativeLineDraw.addClass('smm-associative-line-container')
|
||||
}
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
// 容器
|
||||
this.draw = this.svg.group()
|
||||
this.draw.addClass('smm-container')
|
||||
// 节点连线容器
|
||||
this.lineDraw = this.draw.group()
|
||||
this.lineDraw.addClass('smm-line-container')
|
||||
// 默认处于节点下方
|
||||
if (!associativeLineIsAlwaysAboveNode) {
|
||||
createAssociativeLineDraw()
|
||||
}
|
||||
// 节点容器
|
||||
this.nodeDraw = this.draw.group()
|
||||
this.nodeDraw.addClass('smm-node-container')
|
||||
// 关联线始终处于节点上方
|
||||
if (associativeLineIsAlwaysAboveNode) {
|
||||
createAssociativeLineDraw()
|
||||
}
|
||||
// 其他内容的容器
|
||||
this.otherDraw = this.draw.group()
|
||||
this.otherDraw.addClass('smm-other-container')
|
||||
}
|
||||
|
||||
// 清空各容器
|
||||
clearDraw() {
|
||||
this.lineDraw.clear()
|
||||
this.associativeLineDraw.clear()
|
||||
this.nodeDraw.clear()
|
||||
this.otherDraw.clear()
|
||||
}
|
||||
|
||||
// 添加必要的css样式到页面
|
||||
addCss() {
|
||||
this.cssEl = document.createElement('style')
|
||||
@@ -136,19 +170,27 @@ class MindMap {
|
||||
// 重新渲染
|
||||
reRender(callback, source = '') {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.draw.clear()
|
||||
this.clearDraw()
|
||||
this.initTheme()
|
||||
this.renderer.reRender = true
|
||||
this.renderer.render(callback, source)
|
||||
})
|
||||
}
|
||||
|
||||
// 容器尺寸变化,调整尺寸
|
||||
resize() {
|
||||
// 获取或更新容器尺寸位置信息
|
||||
getElRectInfo() {
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
if (this.width <= 0 || this.height <= 0)
|
||||
throw new Error('容器元素el的宽高不能为0')
|
||||
}
|
||||
|
||||
// 容器尺寸变化,调整尺寸
|
||||
resize() {
|
||||
this.getElRectInfo()
|
||||
this.svg.size(this.width, this.height)
|
||||
this.emit('resize')
|
||||
}
|
||||
|
||||
// 监听事件
|
||||
@@ -192,10 +234,12 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
setTheme(theme) {
|
||||
this.renderer.clearAllActive()
|
||||
setTheme(theme, notRender = false) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.opt.theme = theme
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
if (!notRender) {
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
this.emit('view_theme_change', theme)
|
||||
}
|
||||
|
||||
@@ -205,13 +249,15 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 设置主题配置
|
||||
setThemeConfig(config) {
|
||||
setThemeConfig(config, notRender = false) {
|
||||
// 计算改变了的配置
|
||||
const changedConfig = getObjectChangedProps(this.themeConfig, config)
|
||||
this.opt.themeConfig = config
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(changedConfig)
|
||||
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
|
||||
if (!notRender) {
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(changedConfig)
|
||||
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取自定义主题配置
|
||||
@@ -240,7 +286,7 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 设置布局结构
|
||||
setLayout(layout) {
|
||||
setLayout(layout, notRender = false) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(layout)) {
|
||||
layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
@@ -248,7 +294,9 @@ class MindMap {
|
||||
this.opt.layout = layout
|
||||
this.view.reset()
|
||||
this.renderer.setLayout()
|
||||
this.render(null, CONSTANTS.CHANGE_LAYOUT)
|
||||
if (!notRender) {
|
||||
this.render(null, CONSTANTS.CHANGE_LAYOUT)
|
||||
}
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
@@ -258,15 +306,13 @@ class MindMap {
|
||||
|
||||
// 动态设置思维导图数据,纯节点数据
|
||||
setData(data) {
|
||||
data = simpleDeepClone(data || {})
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.command.addHistory()
|
||||
if (this.richText) {
|
||||
this.renderer.renderTree = this.richText.handleSetData(data)
|
||||
} else {
|
||||
this.renderer.renderTree = data
|
||||
}
|
||||
this.renderer.setData(data)
|
||||
this.reRender(() => {}, CONSTANTS.SET_DATA)
|
||||
this.emit('set_data', data)
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
@@ -336,20 +382,20 @@ class MindMap {
|
||||
this.opt.readonly = mode === CONSTANTS.MODE.READONLY
|
||||
if (this.opt.readonly) {
|
||||
// 取消当前激活的元素
|
||||
this.renderer.clearAllActive()
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
this.emit('mode_change', mode)
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
getSvgData({ paddingX = 0, paddingY = 0 } = {}) {
|
||||
getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false } = {}) {
|
||||
const svg = this.svg
|
||||
const draw = this.draw
|
||||
// 保存原始信息
|
||||
const origWidth = svg.width()
|
||||
const origHeight = svg.height()
|
||||
const origTransform = draw.transform()
|
||||
const elRect = this.el.getBoundingClientRect()
|
||||
const elRect = this.elRect
|
||||
// 去除放大缩小的变换效果
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
@@ -364,10 +410,9 @@ class MindMap {
|
||||
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
|
||||
// 克隆一份数据
|
||||
let clone = svg.clone()
|
||||
// 添加必要的样式
|
||||
clone.add(SVG(`<style>${cssContent}</style>`))
|
||||
// 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题
|
||||
if (
|
||||
!ignoreWatermark &&
|
||||
(rect.width > origWidth || rect.height > origHeight) &&
|
||||
this.watermark &&
|
||||
this.watermark.hasWatermark()
|
||||
@@ -380,6 +425,16 @@ class MindMap {
|
||||
this.height = origHeight
|
||||
this.watermark.draw()
|
||||
}
|
||||
// 添加必要的样式
|
||||
clone.add(SVG(`<style>${cssContent}</style>`))
|
||||
// 修正关联线箭头marker的id
|
||||
const markerList = svg.find('marker')
|
||||
if (markerList && markerList.length > 0) {
|
||||
const id = markerList[0].attr('id')
|
||||
clone.find('marker').forEach(item => {
|
||||
item.attr('id', id)
|
||||
})
|
||||
}
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight)
|
||||
draw.transform(origTransform)
|
||||
@@ -431,6 +486,13 @@ class MindMap {
|
||||
|
||||
// 销毁
|
||||
destroy() {
|
||||
this.emit('beforeDestroy')
|
||||
// 清除节点编辑框
|
||||
this.renderer.textEdit.hideEditTextBox()
|
||||
// 清除关联线文字编辑框
|
||||
if (this.associativeLine) {
|
||||
this.associativeLine.hideEditTextBox()
|
||||
}
|
||||
// 移除插件
|
||||
;[...MindMap.pluginList].forEach(plugin => {
|
||||
if (this[plugin.instanceName].beforePluginDestroy) {
|
||||
|
||||
440
simple-mind-map/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0-fix.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.7.2",
|
||||
"version": "0.8.0-fix.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
@@ -18,7 +18,9 @@
|
||||
"quill": "^1.3.6",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
"xml-js": "^1.6.11",
|
||||
"y-webrtc": "^10.2.5",
|
||||
"yjs": "^13.6.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.25.0",
|
||||
@@ -26,11 +28,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
|
||||
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -158,9 +160,9 @@
|
||||
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
|
||||
},
|
||||
"node_modules/@types/raf": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz",
|
||||
"integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==",
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
@@ -290,6 +292,25 @@
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -310,6 +331,29 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
@@ -350,6 +394,12 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/canvg/node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -415,9 +465,9 @@
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.27.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz",
|
||||
"integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==",
|
||||
"version": "3.33.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz",
|
||||
"integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"funding": {
|
||||
@@ -555,9 +605,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
|
||||
"integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==",
|
||||
"version": "2.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz",
|
||||
"integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
@@ -574,6 +624,11 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/err-code": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz",
|
||||
"integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="
|
||||
},
|
||||
"node_modules/errno": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
|
||||
@@ -873,6 +928,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-browser-rtc": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz",
|
||||
"integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
@@ -1012,6 +1072,25 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
@@ -1150,6 +1229,15 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/isomorphic.js": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
|
||||
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sdsl": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
|
||||
@@ -1248,6 +1336,25 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lib0": {
|
||||
"version": "0.2.86",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.86.tgz",
|
||||
"integrity": "sha512-kxigQTM4Q7NwJkEgdqQvU21qiR37twcqqLmh+/SbiGbRLfPlLVbHyY9sWp7PwXh0Xus9ELDSjsUOwcrdt5yZ4w==",
|
||||
"dependencies": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"0gentesthtml": "bin/gentesthtml.js",
|
||||
"0serve": "bin/0serve.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
@@ -1960,7 +2067,6 @@
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2016,6 +2122,14 @@
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
@@ -2031,9 +2145,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.4.3",
|
||||
@@ -2176,10 +2290,51 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-peer": {
|
||||
"version": "9.11.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz",
|
||||
"integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"debug": "^4.3.2",
|
||||
"err-code": "^3.0.1",
|
||||
"get-browser-rtc": "^1.1.0",
|
||||
"queue-microtask": "^1.2.3",
|
||||
"randombytes": "^2.1.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-peer/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/stackblur-canvas": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz",
|
||||
"integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz",
|
||||
"integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.14"
|
||||
@@ -2410,6 +2565,27 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-js": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
|
||||
@@ -2421,6 +2597,65 @@
|
||||
"xml-js": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/y-protocols": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz",
|
||||
"integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.85"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"yjs": "^13.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/y-webrtc": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.5.tgz",
|
||||
"integrity": "sha512-ZyBNvTI5L28sQ2PQI0T/JvyWgvuTq05L21vGkIlcvNLNSJqAaLCBJRe3FHEqXoaogqWmRcEAKGfII4ErNXMnNw==",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.42",
|
||||
"simple-peer": "^9.11.0",
|
||||
"y-protocols": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"y-webrtc-signaling": "bin/server.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"ws": "^7.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yjs": {
|
||||
"version": "13.6.8",
|
||||
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.8.tgz",
|
||||
"integrity": "sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.74"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
@@ -2436,11 +2671,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
|
||||
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
}
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
@@ -2536,9 +2771,9 @@
|
||||
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
|
||||
},
|
||||
"@types/raf": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz",
|
||||
"integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==",
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"optional": true
|
||||
},
|
||||
"@types/unist": {
|
||||
@@ -2628,6 +2863,11 @@
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"optional": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -2642,6 +2882,15 @@
|
||||
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
|
||||
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
|
||||
},
|
||||
"buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
@@ -2671,6 +2920,14 @@
|
||||
"rgbcolor": "^1.0.1",
|
||||
"stackblur-canvas": "^2.0.0",
|
||||
"svg-pathdata": "^6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
@@ -2719,9 +2976,9 @@
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.27.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz",
|
||||
"integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==",
|
||||
"version": "3.33.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz",
|
||||
"integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==",
|
||||
"optional": true
|
||||
},
|
||||
"core-util-is": {
|
||||
@@ -2818,9 +3075,9 @@
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
|
||||
"integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==",
|
||||
"version": "2.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz",
|
||||
"integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==",
|
||||
"optional": true
|
||||
},
|
||||
"enhanced-resolve": {
|
||||
@@ -2834,6 +3091,11 @@
|
||||
"tapable": "^0.2.3"
|
||||
}
|
||||
},
|
||||
"err-code": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz",
|
||||
"integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="
|
||||
},
|
||||
"errno": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
|
||||
@@ -3066,6 +3328,11 @@
|
||||
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
|
||||
},
|
||||
"get-browser-rtc": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz",
|
||||
"integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
@@ -3163,6 +3430,11 @@
|
||||
"text-segmentation": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
@@ -3262,6 +3534,11 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"isomorphic.js": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
|
||||
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="
|
||||
},
|
||||
"js-sdsl": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
|
||||
@@ -3338,6 +3615,14 @@
|
||||
"type-check": "~0.4.0"
|
||||
}
|
||||
},
|
||||
"lib0": {
|
||||
"version": "0.2.86",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.86.tgz",
|
||||
"integrity": "sha512-kxigQTM4Q7NwJkEgdqQvU21qiR37twcqqLmh+/SbiGbRLfPlLVbHyY9sWp7PwXh0Xus9ELDSjsUOwcrdt5yZ4w==",
|
||||
"requires": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
}
|
||||
},
|
||||
"lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
@@ -3765,8 +4050,7 @@
|
||||
"queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
|
||||
},
|
||||
"quill": {
|
||||
"version": "1.3.6",
|
||||
@@ -3807,6 +4091,14 @@
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
@@ -3822,9 +4114,9 @@
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
|
||||
},
|
||||
"regexp.prototype.flags": {
|
||||
"version": "1.4.3",
|
||||
@@ -3916,10 +4208,36 @@
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
},
|
||||
"simple-peer": {
|
||||
"version": "9.11.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz",
|
||||
"integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==",
|
||||
"requires": {
|
||||
"buffer": "^6.0.3",
|
||||
"debug": "^4.3.2",
|
||||
"err-code": "^3.0.1",
|
||||
"get-browser-rtc": "^1.1.0",
|
||||
"queue-microtask": "^1.2.3",
|
||||
"randombytes": "^2.1.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"stackblur-canvas": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz",
|
||||
"integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz",
|
||||
"integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==",
|
||||
"optional": true
|
||||
},
|
||||
"string_decoder": {
|
||||
@@ -4088,6 +4406,13 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||
"optional": true,
|
||||
"requires": {}
|
||||
},
|
||||
"xml-js": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
|
||||
@@ -4096,6 +4421,33 @@
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"y-protocols": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz",
|
||||
"integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==",
|
||||
"requires": {
|
||||
"lib0": "^0.2.85"
|
||||
}
|
||||
},
|
||||
"y-webrtc": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.5.tgz",
|
||||
"integrity": "sha512-ZyBNvTI5L28sQ2PQI0T/JvyWgvuTq05L21vGkIlcvNLNSJqAaLCBJRe3FHEqXoaogqWmRcEAKGfII4ErNXMnNw==",
|
||||
"requires": {
|
||||
"lib0": "^0.2.42",
|
||||
"simple-peer": "^9.11.0",
|
||||
"ws": "^7.2.0",
|
||||
"y-protocols": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"yjs": {
|
||||
"version": "13.6.8",
|
||||
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.8.tgz",
|
||||
"integrity": "sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==",
|
||||
"requires": {
|
||||
"lib0": "^0.2.74"
|
||||
}
|
||||
},
|
||||
"yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.7.2",
|
||||
"version": "0.9.1",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -22,7 +22,8 @@
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"format": "prettier --write .",
|
||||
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017"
|
||||
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck",
|
||||
"wsServe": "node ./bin/wsServer.mjs"
|
||||
},
|
||||
"module": "index.js",
|
||||
"main": "./dist/simpleMindMap.umd.min.js",
|
||||
@@ -37,7 +38,9 @@
|
||||
"quill": "^1.3.6",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
"xml-js": "^1.6.11",
|
||||
"y-webrtc": "^10.2.5",
|
||||
"yjs": "^13.6.8"
|
||||
},
|
||||
"keywords": [
|
||||
"javascript",
|
||||
|
||||
@@ -229,6 +229,11 @@ export const CONSTANTS = {
|
||||
SCROLL_BAR_DIR: {
|
||||
VERTICAL: 'vertical',
|
||||
HORIZONTAL: 'horizontal'
|
||||
},
|
||||
CREATE_NEW_NODE_BEHAVIOR: {
|
||||
DEFAULT: 'default',
|
||||
NOT_ACTIVE: 'notActive',
|
||||
ACTIVE_ONLY: 'activeOnly'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,3 +356,14 @@ export const cssContent = `
|
||||
stroke-width: 2;
|
||||
}
|
||||
`
|
||||
|
||||
// html自闭合标签列表
|
||||
export const selfCloseTagList = [
|
||||
'img',
|
||||
'br',
|
||||
'hr',
|
||||
'input',
|
||||
'link',
|
||||
'meta',
|
||||
'area'
|
||||
]
|
||||
|
||||
@@ -56,11 +56,11 @@ export const defaultOpt = {
|
||||
// 可以传一个函数,回调参数为事件对象
|
||||
customHandleMousewheel: null,
|
||||
// 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效
|
||||
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM, // zoom(放大缩小)、move(上下移动)
|
||||
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.MOVE, // zoom(放大缩小)、move(上下移动)
|
||||
// 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
||||
mousewheelMoveStep: 100,
|
||||
// 当mousewheelAction设为zoom时,默认向前滚动是缩小,向后滚动是放大,如果该属性设为true,那么会反过来
|
||||
mousewheelZoomActionReverse: false,
|
||||
// 当mousewheelAction设为zoom时,或者按住Ctrl键时,默认向前滚动是缩小,向后滚动是放大,如果该属性设为true,那么会反过来
|
||||
mousewheelZoomActionReverse: true,
|
||||
// 默认插入的二级节点的文字
|
||||
defaultInsertSecondLevelNodeText: '二级节点',
|
||||
// 默认插入的二级以下节点的文字
|
||||
@@ -160,6 +160,9 @@ export const defaultOpt = {
|
||||
customHandleClipboardText: null,
|
||||
// 禁止鼠标滚轮缩放,你仍旧可以使用api进行缩放
|
||||
disableMouseWheelZoom: false,
|
||||
// 禁止双指缩放,你仍旧可以使用api进行缩放
|
||||
// 需要注册TouchEvent插件后生效
|
||||
disableTouchZoom: false,
|
||||
// 错误处理函数
|
||||
errorHandler: (code, error) => {
|
||||
console.error(code, error)
|
||||
@@ -173,8 +176,8 @@ export const defaultOpt = {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`,
|
||||
// 开启鼠标双击复位思维导图位置及缩放
|
||||
enableDblclickReset: false,
|
||||
// 是否在鼠标双击时回到根节点,也就是让根节点居中显示
|
||||
enableDblclickBackToRootNode: false,
|
||||
// 导出图片时canvas的缩放倍数,该配置会和window.devicePixelRatio值取最大值
|
||||
minExportImgCanvasScale: 2,
|
||||
// 节点鼠标hover和激活时显示的矩形边框的颜色
|
||||
@@ -204,5 +207,34 @@ export const defaultOpt = {
|
||||
},
|
||||
// 自定义标签的颜色
|
||||
// {pass: 'green, unpass: 'red'}
|
||||
tagsColorMap: {}
|
||||
tagsColorMap: {},
|
||||
// 节点协作样式配置
|
||||
cooperateStyle: {
|
||||
avatarSize: 22, // 头像大小
|
||||
fontSize: 12 // 如果是文字头像,那么文字的大小
|
||||
},
|
||||
// 关联线是否始终显示在节点上层
|
||||
// false:即创建关联线和激活关联线时处于最顶层,其他情况下处于节点下方
|
||||
associativeLineIsAlwaysAboveNode: true,
|
||||
// 插入概要的默认文本
|
||||
defaultGeneralizationText: '概要',
|
||||
// 粘贴文本的方式创建新节点时,控制是否按换行自动分割节点,即如果存在换行,那么会根据换行创建多个节点,否则只会创建一个节点
|
||||
// 可以传递一个函数,返回promise,resolve代表根据换行分割,reject代表忽略换行
|
||||
handleIsSplitByWrapOnPasteCreateNewNode: null,
|
||||
// 多少时间内只允许添加一次历史记录,避免添加没有必要的中间状态,单位:ms
|
||||
addHistoryTime: 100,
|
||||
// 是否禁止拖动画布
|
||||
isDisableDrag: false,
|
||||
// 鼠标移入概要高亮所属节点时的高亮框样式
|
||||
highlightNodeBoxStyle: {
|
||||
stroke: 'rgb(94, 200, 248)',
|
||||
fill: 'transparent'
|
||||
},
|
||||
// 创建新节点时的行为
|
||||
/*
|
||||
DEFAULT :默认会激活新创建的节点,并且进入编辑模式。如果同时创建了多个新节点,那么只会激活而不会进入编辑模式
|
||||
NOT_ACTIVE : 不激活新创建的节点
|
||||
ACTIVE_ONLY : 只激活新创建的节点,不进入编辑模式
|
||||
*/
|
||||
createNewNodeBehavior: CONSTANTS.CREATE_NEW_NODE_BEHAVIOR.DEFAULT
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { copyRenderTree, simpleDeepClone, nextTick } from '../../utils'
|
||||
import { copyRenderTree, simpleDeepClone, throttle } from '../../utils'
|
||||
|
||||
// 命令类
|
||||
class Command {
|
||||
@@ -11,7 +11,11 @@ class Command {
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
this.addHistory = nextTick(this.addHistory, this)
|
||||
this.addHistory = throttle(
|
||||
this.addHistory,
|
||||
this.mindMap.opt.addHistoryTime,
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
// 清空历史数据
|
||||
|
||||
@@ -38,6 +38,7 @@ export default class KeyCommand {
|
||||
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
this.onKeydown = this.onKeydown.bind(this)
|
||||
// 只有当鼠标在画布内才响应快捷键
|
||||
this.mindMap.on('svg_mouseenter', () => {
|
||||
this.isInSvg = true
|
||||
@@ -55,25 +56,36 @@ export default class KeyCommand {
|
||||
}
|
||||
this.isInSvg = false
|
||||
})
|
||||
window.addEventListener('keydown', e => {
|
||||
if (
|
||||
this.isPause ||
|
||||
(this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
|
||||
) {
|
||||
return
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach(key => {
|
||||
if (this.checkKey(e, key)) {
|
||||
// 粘贴事件不组织,因为要监听paste事件
|
||||
if (!this.checkKey(e, 'Control+v')) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
window.addEventListener('keydown', this.onKeydown)
|
||||
this.mindMap.on('beforeDestroy', () => {
|
||||
this.unBindEvent()
|
||||
})
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
unBindEvent() {
|
||||
window.removeEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
if (
|
||||
this.isPause ||
|
||||
(this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
|
||||
) {
|
||||
return
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach(key => {
|
||||
if (this.checkKey(e, key)) {
|
||||
// 粘贴事件不组织,因为要监听paste事件
|
||||
if (!this.checkKey(e, 'Control+v')) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -133,18 +133,10 @@ class Event extends EventEmitter {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let dir
|
||||
// 解决mac触控板双指缩放方向相反的问题
|
||||
if (e.ctrlKey) {
|
||||
if (e.deltaY > 0) dir = CONSTANTS.DIR.UP
|
||||
if (e.deltaY < 0) dir = CONSTANTS.DIR.DOWN
|
||||
if (e.deltaX > 0) dir = CONSTANTS.DIR.LEFT
|
||||
if (e.deltaX < 0) dir = CONSTANTS.DIR.RIGHT
|
||||
} else {
|
||||
if ((e.wheelDeltaY || e.detail) > 0) dir = CONSTANTS.DIR.UP
|
||||
if ((e.wheelDeltaY || e.detail) < 0) dir = CONSTANTS.DIR.DOWN
|
||||
if ((e.wheelDeltaX || e.detail) > 0) dir = CONSTANTS.DIR.LEFT
|
||||
if ((e.wheelDeltaX || e.detail) < 0) dir = CONSTANTS.DIR.RIGHT
|
||||
}
|
||||
if (e.deltaY < 0) dir = CONSTANTS.DIR.UP
|
||||
if (e.deltaY > 0) dir = CONSTANTS.DIR.DOWN
|
||||
if (e.deltaX < 0) dir = CONSTANTS.DIR.LEFT
|
||||
if (e.deltaX > 0) dir = CONSTANTS.DIR.RIGHT
|
||||
// 判断是否是触控板
|
||||
let isTouchPad = false
|
||||
// mac、windows
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
selectAllInput,
|
||||
htmlEscape
|
||||
} from '../../utils'
|
||||
import { ERROR_TYPES } from '../../constants/constant'
|
||||
import { ERROR_TYPES, CONSTANTS } from '../../constants/constant'
|
||||
|
||||
// 节点文字编辑类
|
||||
export default class TextEdit {
|
||||
@@ -28,8 +28,11 @@ export default class TextEdit {
|
||||
bindEvent() {
|
||||
this.show = this.show.bind(this)
|
||||
this.onScale = this.onScale.bind(this)
|
||||
this.onKeydown = this.onKeydown.bind(this)
|
||||
// 节点双击事件
|
||||
this.mindMap.on('node_dblclick', this.show)
|
||||
this.mindMap.on('node_dblclick', (node, e, isInserting) => {
|
||||
this.show({ node, e, isInserting })
|
||||
})
|
||||
// 点击事件
|
||||
this.mindMap.on('draw_click', () => {
|
||||
// 隐藏文本编辑框
|
||||
@@ -53,24 +56,50 @@ export default class TextEdit {
|
||||
this.mindMap.on('before_node_active', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 鼠标滚动事件
|
||||
this.mindMap.on('mousewheel', () => {
|
||||
if (
|
||||
this.mindMap.opt.mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.MOVE
|
||||
) {
|
||||
this.hideEditTextBox()
|
||||
}
|
||||
})
|
||||
// 注册编辑快捷键
|
||||
this.mindMap.keyCommand.addShortcut('F2', () => {
|
||||
if (this.renderer.activeNodeList.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.show(this.renderer.activeNodeList[0])
|
||||
this.show({
|
||||
node: this.renderer.activeNodeList[0]
|
||||
})
|
||||
})
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
// // 监听按键事件,判断是否自动进入文本编辑模式
|
||||
if (this.mindMap.opt.enableAutoEnterTextEditWhenKeydown) {
|
||||
window.addEventListener('keydown', e => {
|
||||
const activeNodeList = this.mindMap.renderer.activeNodeList
|
||||
if (activeNodeList.length <= 0 || activeNodeList.length > 1) return
|
||||
const node = activeNodeList[0]
|
||||
// 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式
|
||||
if (node && this.checkIsAutoEnterTextEditKey(e)) {
|
||||
this.show(node, e, false, true)
|
||||
}
|
||||
window.addEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
this.mindMap.on('beforeDestroy', () => {
|
||||
this.unBindEvent()
|
||||
})
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
unBindEvent() {
|
||||
window.removeEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
const activeNodeList = this.mindMap.renderer.activeNodeList
|
||||
if (activeNodeList.length <= 0 || activeNodeList.length > 1) return
|
||||
const node = activeNodeList[0]
|
||||
// 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式
|
||||
if (node && this.checkIsAutoEnterTextEditKey(e)) {
|
||||
this.show({
|
||||
node,
|
||||
e,
|
||||
isInserting: false,
|
||||
isFromKeyDown: true
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -100,12 +129,17 @@ export default class TextEdit {
|
||||
// 显示文本编辑框
|
||||
// isInserting:是否是刚创建的节点
|
||||
// isFromKeyDown:是否是在按键事件进入的编辑
|
||||
async show(node, e, isInserting = false, isFromKeyDown = false) {
|
||||
async show({
|
||||
node,
|
||||
isInserting = false,
|
||||
isFromKeyDown = false,
|
||||
isFromScale = false
|
||||
}) {
|
||||
// 使用了自定义节点内容那么不响应编辑事件
|
||||
if (node.isUseCustomNodeContent()) {
|
||||
return
|
||||
}
|
||||
let { beforeTextEdit } = this.mindMap.opt
|
||||
const { beforeTextEdit } = this.mindMap.opt
|
||||
if (typeof beforeTextEdit === 'function') {
|
||||
let isShow = false
|
||||
try {
|
||||
@@ -117,14 +151,21 @@ export default class TextEdit {
|
||||
if (!isShow) return
|
||||
}
|
||||
this.currentNode = node
|
||||
let { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
|
||||
const { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
|
||||
this.mindMap.view.translateXY(offsetLeft, offsetTop)
|
||||
let rect = node._textData.node.node.getBoundingClientRect()
|
||||
const rect = node._textData.node.node.getBoundingClientRect()
|
||||
const params = {
|
||||
node,
|
||||
rect,
|
||||
isInserting,
|
||||
isFromKeyDown,
|
||||
isFromScale
|
||||
}
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.showEditText(node, rect, isInserting, isFromKeyDown)
|
||||
this.mindMap.richText.showEditText(params)
|
||||
return
|
||||
}
|
||||
this.showEditTextBox(node, rect, isInserting, isFromKeyDown)
|
||||
this.showEditTextBox(params)
|
||||
}
|
||||
|
||||
// 处理画布缩放
|
||||
@@ -138,15 +179,20 @@ export default class TextEdit {
|
||||
this.cacheEditingText = this.getEditText()
|
||||
this.showTextEdit = false
|
||||
}
|
||||
this.show(this.currentNode)
|
||||
this.show({
|
||||
node: this.currentNode,
|
||||
isFromScale: true
|
||||
})
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
showEditTextBox(node, rect, isInserting, isFromKeyDown) {
|
||||
showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
|
||||
if (this.showTextEdit) return
|
||||
const { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText } =
|
||||
this.mindMap.opt
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
if (!isFromScale) {
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
}
|
||||
this.registerTmpShortcut()
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
@@ -173,7 +219,7 @@ export default class TextEdit {
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
let fontSize = node.style.merge('fontSize')
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text)
|
||||
let textLines = (this.cacheEditingText || node.getData('text'))
|
||||
.split(/\n/gim)
|
||||
.map(item => {
|
||||
return htmlEscape(item)
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { G, ForeignObject, SVG, Rect } from '@svgdotjs/svg.js'
|
||||
import { G, ForeignObject, Rect } 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 nodeCooperateMethods from './nodeCooperate'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
import { copyNodeTree } from '../../../utils/index'
|
||||
|
||||
// 节点类
|
||||
class Node {
|
||||
@@ -21,7 +23,9 @@ class Node {
|
||||
// 渲染实例
|
||||
this.renderer = opt.renderer
|
||||
// 渲染器
|
||||
this.draw = opt.draw || null
|
||||
this.draw = this.mindMap.draw
|
||||
this.nodeDraw = this.mindMap.nodeDraw
|
||||
this.lineDraw = this.mindMap.lineDraw
|
||||
// 样式实例
|
||||
this.style = new Style(this)
|
||||
// 形状实例
|
||||
@@ -55,6 +59,8 @@ class Node {
|
||||
this.parent = opt.parent || null
|
||||
// 子节点
|
||||
this.children = opt.children || []
|
||||
// 当前同时操作该节点的用户列表
|
||||
this.userList = []
|
||||
// 节点内容的容器
|
||||
this.group = null
|
||||
this.shapeNode = null // 节点形状节点
|
||||
@@ -68,15 +74,16 @@ class Node {
|
||||
this._tagData = null
|
||||
this._noteData = null
|
||||
this.noteEl = null
|
||||
this.noteContentIsShow = false
|
||||
this._expandBtn = null
|
||||
this._lastExpandBtnType = null
|
||||
this._showExpandBtn = false
|
||||
this._openExpandNode = null
|
||||
this._closeExpandNode = null
|
||||
this._fillExpandNode = null
|
||||
this._userListGroup = null
|
||||
this._lines = []
|
||||
this._generalizationLine = null
|
||||
this._generalizationNode = null
|
||||
this._generalizationList = []
|
||||
this._unVisibleRectRegionNode = null
|
||||
this._isMouseenter = false
|
||||
// 尺寸信息
|
||||
@@ -121,6 +128,12 @@ class Node {
|
||||
Object.keys(nodeCreateContentsMethods).forEach(item => {
|
||||
this[item] = nodeCreateContentsMethods[item].bind(this)
|
||||
})
|
||||
// 协同相关
|
||||
if (this.mindMap.cooperate) {
|
||||
Object.keys(nodeCooperateMethods).forEach(item => {
|
||||
this[item] = nodeCooperateMethods[item].bind(this)
|
||||
})
|
||||
}
|
||||
// 初始化
|
||||
this.getSize()
|
||||
}
|
||||
@@ -166,6 +179,7 @@ class Node {
|
||||
let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt
|
||||
if (isUseCustomNodeContent && customCreateNodeContent) {
|
||||
this._customNodeContent = customCreateNodeContent(this)
|
||||
this._customNodeContent.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
}
|
||||
// 如果没有返回内容,那么还是使用内置的节点内容
|
||||
if (this._customNodeContent) return
|
||||
@@ -283,6 +297,8 @@ class Node {
|
||||
this.group.add(this.shapeNode)
|
||||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
// 创建协同头像节点
|
||||
if (this.createUserListNode) this.createUserListNode()
|
||||
// 概要节点添加一个带所属节点id的类名
|
||||
if (this.isGeneralization && this.generalizationBelongNode) {
|
||||
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
|
||||
@@ -416,20 +432,17 @@ class Node {
|
||||
// 多选和取消多选
|
||||
if (e.ctrlKey && enableCtrlKeyNodeSelection) {
|
||||
this.isMultipleChoice = true
|
||||
let isActive = this.nodeData.data.isActive
|
||||
let isActive = this.getData('isActive')
|
||||
if (!isActive)
|
||||
this.mindMap.emit(
|
||||
'before_node_active',
|
||||
this,
|
||||
this.renderer.activeNodeList
|
||||
)
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this, !isActive)
|
||||
this.mindMap.renderer[isActive ? 'removeActiveNode' : 'addActiveNode'](
|
||||
this
|
||||
)
|
||||
this.mindMap.emit('node_active', isActive ? null : this, [
|
||||
...this.mindMap.renderer.activeNodeList
|
||||
])
|
||||
this.mindMap.renderer[
|
||||
isActive ? 'removeNodeFromActiveList' : 'addNodeToActiveList'
|
||||
](this)
|
||||
this.renderer.emitNodeActiveEvent(isActive ? null : this)
|
||||
}
|
||||
this.mindMap.emit('node_mousedown', this, e)
|
||||
})
|
||||
@@ -444,17 +457,23 @@ class Node {
|
||||
this._isMouseenter = true
|
||||
// 显示展开收起按钮
|
||||
this.showExpandBtn()
|
||||
if (this.isGeneralization) {
|
||||
this.handleGeneralizationMouseenter()
|
||||
}
|
||||
this.mindMap.emit('node_mouseenter', this, e)
|
||||
})
|
||||
this.group.on('mouseleave', e => {
|
||||
if (!this._isMouseenter) return
|
||||
this._isMouseenter = false
|
||||
this.hideExpandBtn()
|
||||
if (this.isGeneralization) {
|
||||
this.handleGeneralizationMouseleave()
|
||||
}
|
||||
this.mindMap.emit('node_mouseleave', this, e)
|
||||
})
|
||||
// 双击事件
|
||||
this.group.on('dblclick', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
@@ -477,10 +496,13 @@ class Node {
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (this.nodeData.data.isActive) {
|
||||
this.renderer.clearActive()
|
||||
// 如果有且只有当前节点激活了,那么不需要重新激活
|
||||
if (
|
||||
!(this.getData('isActive') && this.renderer.activeNodeList.length === 1)
|
||||
) {
|
||||
this.renderer.clearActiveNodeList()
|
||||
this.active(e)
|
||||
}
|
||||
this.active(e)
|
||||
this.mindMap.emit('node_contextmenu', e, this)
|
||||
})
|
||||
}
|
||||
@@ -491,14 +513,13 @@ class Node {
|
||||
return
|
||||
}
|
||||
e && e.stopPropagation()
|
||||
if (this.nodeData.data.isActive) {
|
||||
if (this.getData('isActive')) {
|
||||
return
|
||||
}
|
||||
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
|
||||
this.renderer.clearActive()
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this, true)
|
||||
this.renderer.addActiveNode(this)
|
||||
this.mindMap.emit('node_active', this, [...this.renderer.activeNodeList])
|
||||
this.renderer.clearActiveNodeList()
|
||||
this.renderer.addNodeToActiveList(this)
|
||||
this.renderer.emitNodeActiveEvent(this)
|
||||
}
|
||||
|
||||
// 更新节点
|
||||
@@ -506,7 +527,7 @@ class Node {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
this.updateNodeActive()
|
||||
this.updateNodeActiveClass()
|
||||
let { alwaysShowExpandBtn } = this.mindMap.opt
|
||||
if (alwaysShowExpandBtn) {
|
||||
// 需要移除展开收缩按钮
|
||||
@@ -517,7 +538,7 @@ class Node {
|
||||
this.renderExpandBtn()
|
||||
}
|
||||
} else {
|
||||
let { isActive, expand } = this.nodeData.data
|
||||
let { isActive, expand } = this.getData()
|
||||
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
|
||||
if (expand && !isActive && !this._isMouseenter) {
|
||||
this.hideExpandBtn()
|
||||
@@ -527,6 +548,8 @@ class Node {
|
||||
}
|
||||
// 更新概要
|
||||
this.renderGeneralization()
|
||||
// 更新协同头像
|
||||
if (this.updateUserListNode) this.updateUserListNode()
|
||||
// 更新节点位置
|
||||
let t = this.group.transform()
|
||||
// // 如果上次不在可视区内,且本次也不在,那么直接返回
|
||||
@@ -580,12 +603,25 @@ class Node {
|
||||
}
|
||||
|
||||
// 更新节点激活状态
|
||||
updateNodeActive() {
|
||||
updateNodeActiveClass() {
|
||||
if (!this.group) return
|
||||
const isActive = this.nodeData.data.isActive
|
||||
const isActive = this.getData('isActive')
|
||||
this.group[isActive ? 'addClass' : 'removeClass']('active')
|
||||
}
|
||||
|
||||
// 根据是否激活更新节点
|
||||
updateNodeByActive(active) {
|
||||
if (this.group) {
|
||||
// 切换激活状态,需要切换展开收起按钮的显隐
|
||||
if (active) {
|
||||
this.showExpandBtn()
|
||||
} else {
|
||||
this.hideExpandBtn()
|
||||
}
|
||||
this.updateNodeActiveClass()
|
||||
}
|
||||
}
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
// 节点
|
||||
@@ -599,11 +635,11 @@ class Node {
|
||||
cursor: 'default'
|
||||
})
|
||||
this.bindGroupEvent()
|
||||
this.draw.add(this.group)
|
||||
this.nodeDraw.add(this.group)
|
||||
this.layout()
|
||||
this.update()
|
||||
} else {
|
||||
this.draw.add(this.group)
|
||||
this.nodeDraw.add(this.group)
|
||||
if (this.needLayout) {
|
||||
this.needLayout = false
|
||||
this.layout()
|
||||
@@ -615,7 +651,7 @@ class Node {
|
||||
if (
|
||||
this.children &&
|
||||
this.children.length &&
|
||||
this.nodeData.data.expand !== false
|
||||
this.getData('expand') !== false
|
||||
) {
|
||||
let index = 0
|
||||
this.children.forEach(item => {
|
||||
@@ -660,6 +696,9 @@ class Node {
|
||||
this.removeGeneralization()
|
||||
this.removeLine()
|
||||
this.group = null
|
||||
if (this.parent) {
|
||||
this.parent.removeLine()
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏节点
|
||||
@@ -716,10 +755,7 @@ class Node {
|
||||
item.setOpacity(val)
|
||||
})
|
||||
// 概要节点
|
||||
if (this._generalizationNode) {
|
||||
this._generalizationLine.opacity(val)
|
||||
this._generalizationNode.group.opacity(val)
|
||||
}
|
||||
this.setGeneralizationOpacity(val)
|
||||
}
|
||||
|
||||
// 隐藏子节点
|
||||
@@ -760,7 +796,7 @@ class Node {
|
||||
|
||||
// 连线
|
||||
renderLine(deep = false) {
|
||||
if (this.nodeData.data.expand === false) {
|
||||
if (this.getData('expand') === false) {
|
||||
return
|
||||
}
|
||||
let childrenLen = this.nodeData.children.length
|
||||
@@ -774,7 +810,7 @@ class Node {
|
||||
if (childrenLen > this._lines.length) {
|
||||
// 创建缺少的线
|
||||
new Array(childrenLen - this._lines.length).fill(0).forEach(() => {
|
||||
this._lines.push(this.draw.path())
|
||||
this._lines.push(this.lineDraw.path())
|
||||
})
|
||||
} else if (childrenLen < this._lines.length) {
|
||||
// 删除多余的线
|
||||
@@ -814,7 +850,7 @@ class Node {
|
||||
return this.customLeft !== undefined && this.customTop !== undefined
|
||||
}
|
||||
|
||||
// 检查节点是否存在自定义位置的祖先节点
|
||||
// 检查节点是否存在自定义位置的祖先节点,包含自身
|
||||
ancestorHasCustomPosition() {
|
||||
let node = this
|
||||
while (node) {
|
||||
@@ -826,6 +862,18 @@ class Node {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否存在有概要的祖先节点
|
||||
ancestorHasGeneralization() {
|
||||
let node = this.parent
|
||||
while (node) {
|
||||
if (node.checkHasGeneralization()) {
|
||||
return true
|
||||
}
|
||||
node = node.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 添加子节点
|
||||
addChildren(node) {
|
||||
this.children.push(node)
|
||||
@@ -856,7 +904,7 @@ class Node {
|
||||
}
|
||||
|
||||
// 检测当前节点是否是某个节点的祖先节点
|
||||
isParent(node) {
|
||||
isAncestor(node) {
|
||||
if (this.uid === node.uid) {
|
||||
return false
|
||||
}
|
||||
@@ -870,6 +918,18 @@ class Node {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查当前节点是否是某个节点的父节点
|
||||
isParent(node) {
|
||||
if (this.uid === node.uid) {
|
||||
return false
|
||||
}
|
||||
const parent = node.parent
|
||||
if (parent && this.uid === parent.uid) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 检测当前节点是否是某个节点的兄弟节点
|
||||
isBrother(node) {
|
||||
if (!this.parent || this.uid === node.uid) {
|
||||
@@ -880,9 +940,18 @@ class Node {
|
||||
})
|
||||
}
|
||||
|
||||
// 获取该节点在兄弟节点列表中的索引
|
||||
getIndexInBrothers() {
|
||||
return this.parent && this.parent.children
|
||||
? this.parent.children.findIndex(item => {
|
||||
return item.uid === this.uid
|
||||
})
|
||||
: -1
|
||||
}
|
||||
|
||||
// 获取padding值
|
||||
getPaddingVale() {
|
||||
let { isActive } = this.nodeData.data
|
||||
let { isActive } = this.getData()
|
||||
return {
|
||||
paddingX: this.getStyle('paddingX', true, isActive),
|
||||
paddingY: this.getStyle('paddingY', true, isActive)
|
||||
@@ -925,13 +994,42 @@ class Node {
|
||||
|
||||
// 获取数据
|
||||
getData(key) {
|
||||
return key ? this.nodeData.data[key] || '' : this.nodeData.data
|
||||
return key ? this.nodeData.data[key] : this.nodeData.data
|
||||
}
|
||||
|
||||
// 获取该节点的纯数据,即不包含对节点实例的引用
|
||||
getPureData(removeActiveState = true, removeId = false) {
|
||||
return copyNodeTree({}, this, removeActiveState, removeId)
|
||||
}
|
||||
|
||||
// 是否存在自定义样式
|
||||
hasCustomStyle() {
|
||||
return this.style.hasCustomStyle()
|
||||
}
|
||||
|
||||
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置是相对于浏览器窗口左上角的位置
|
||||
getRect() {
|
||||
return this.group.rbox()
|
||||
}
|
||||
|
||||
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置信息相对于画布
|
||||
getRectInSvg() {
|
||||
let { 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
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
return {
|
||||
left,
|
||||
right,
|
||||
top,
|
||||
bottom,
|
||||
width: width * scaleX,
|
||||
height: height * scaleY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
|
||||
@@ -88,7 +88,7 @@ class Style {
|
||||
|
||||
// 获取自身自定义样式
|
||||
getSelfStyle(prop) {
|
||||
return this.ctx.nodeData.data[prop]
|
||||
return this.ctx.getData(prop)
|
||||
}
|
||||
|
||||
// 矩形
|
||||
@@ -107,7 +107,7 @@ class Style {
|
||||
// !this.ctx.isRoot &&
|
||||
// !this.ctx.isGeneralization &&
|
||||
// this.ctx.mindMap.themeConfig.nodeUseLineStyle &&
|
||||
// !this.ctx.nodeData.data.isActive
|
||||
// !this.ctx.getData('isActive')
|
||||
// ) {
|
||||
// return
|
||||
// }
|
||||
@@ -225,7 +225,7 @@ class Style {
|
||||
// 是否设置了自定义的样式
|
||||
hasCustomStyle() {
|
||||
let res = false
|
||||
Object.keys(this.ctx.nodeData.data).forEach(item => {
|
||||
Object.keys(this.ctx.getData()).forEach(item => {
|
||||
if (checkIsNodeStyleDataKey(item)) {
|
||||
res = true
|
||||
}
|
||||
|
||||
104
simple-mind-map/src/core/render/node/nodeCooperate.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Circle, G, Text, Image } from '@svgdotjs/svg.js'
|
||||
import { generateColorByContent } from '../../../utils/index'
|
||||
|
||||
// 协同相关功能
|
||||
|
||||
// 创建容器
|
||||
function createUserListNode() {
|
||||
// 如果没有注册协作插件,那么需要创建
|
||||
if (!this.mindMap.cooperate) return
|
||||
this._userListGroup = new G()
|
||||
this.group.add(this._userListGroup)
|
||||
}
|
||||
|
||||
// 创建文本头像
|
||||
function createTextAvatar(item) {
|
||||
const { avatarSize, fontSize } = this.mindMap.opt.cooperateStyle
|
||||
const g = new G()
|
||||
const str = item.isMore ? item.name : String(item.name)[0]
|
||||
// 圆
|
||||
const circle = new Circle().size(avatarSize, avatarSize)
|
||||
circle.fill({
|
||||
color: item.color || generateColorByContent(str)
|
||||
})
|
||||
// 文本
|
||||
const text = new Text()
|
||||
.text(str)
|
||||
.fill({
|
||||
color: '#fff'
|
||||
})
|
||||
.css({
|
||||
'font-size': fontSize
|
||||
})
|
||||
.dx(-fontSize / 2)
|
||||
.dy((avatarSize - fontSize) / 2)
|
||||
g.add(circle).add(text)
|
||||
return g
|
||||
}
|
||||
|
||||
// 创建图片头像
|
||||
function createImageAvatar(item) {
|
||||
const { avatarSize } = this.mindMap.opt.cooperateStyle
|
||||
return new Image().load(item.avatar).size(avatarSize, avatarSize)
|
||||
}
|
||||
|
||||
// 更新渲染
|
||||
function updateUserListNode() {
|
||||
if (!this._userListGroup) return
|
||||
const { avatarSize } = this.mindMap.opt.cooperateStyle
|
||||
this._userListGroup.clear()
|
||||
// 根据当前节点长度计算最多能显示几个
|
||||
const length = this.userList.length
|
||||
const maxShowCount = Math.floor(this.width / avatarSize)
|
||||
const list = []
|
||||
if (length > maxShowCount) {
|
||||
// 如果当前用户数量比最多能显示的多,最后需要显示一个提示信息
|
||||
list.push(...this.userList.slice(0, maxShowCount - 1), {
|
||||
isMore: true,
|
||||
name: '+' + (length - maxShowCount + 1)
|
||||
})
|
||||
} else {
|
||||
list.push(...this.userList)
|
||||
}
|
||||
list.forEach((item, index) => {
|
||||
let node = null
|
||||
if (item.avatar) {
|
||||
node = this.createImageAvatar(item)
|
||||
} else {
|
||||
node = this.createTextAvatar(item)
|
||||
}
|
||||
node.x(index * avatarSize).cy(-avatarSize / 2)
|
||||
this._userListGroup.add(node)
|
||||
})
|
||||
}
|
||||
|
||||
// 添加用户
|
||||
function addUser(userInfo) {
|
||||
if (
|
||||
this.userList.find(item => {
|
||||
return item.id == userInfo.id
|
||||
})
|
||||
)
|
||||
return
|
||||
this.userList.push(userInfo)
|
||||
this.updateUserListNode()
|
||||
}
|
||||
|
||||
// 移除用户
|
||||
function removeUser(userInfo) {
|
||||
const index = this.userList.findIndex(item => {
|
||||
return item.id == userInfo.id
|
||||
})
|
||||
if (index === -1) return
|
||||
this.userList.splice(index, 1)
|
||||
this.updateUserListNode()
|
||||
}
|
||||
|
||||
export default {
|
||||
createUserListNode,
|
||||
updateUserListNode,
|
||||
createTextAvatar,
|
||||
createImageAvatar,
|
||||
addUser,
|
||||
removeUser
|
||||
}
|
||||
@@ -12,14 +12,14 @@ import { CONSTANTS, commonCaches } from '../../../constants/constant'
|
||||
|
||||
// 创建图片节点
|
||||
function createImgNode() {
|
||||
let img = this.nodeData.data.image
|
||||
let img = this.getData('image')
|
||||
if (!img) {
|
||||
return
|
||||
}
|
||||
let imgSize = this.getImgShowSize()
|
||||
let node = new Image().load(img).size(...imgSize)
|
||||
if (this.nodeData.data.imageTitle) {
|
||||
node.attr('title', this.nodeData.data.imageTitle)
|
||||
if (this.getData('imageTitle')) {
|
||||
node.attr('title', this.getData('imageTitle'))
|
||||
}
|
||||
node.on('dblclick', e => {
|
||||
this.mindMap.emit('node_img_dblclick', this, e)
|
||||
@@ -42,7 +42,7 @@ function createImgNode() {
|
||||
|
||||
// 获取图片显示宽高
|
||||
function getImgShowSize() {
|
||||
const { custom, width, height } = this.nodeData.data.imageSize
|
||||
const { custom, width, height } = this.getData('imageSize')
|
||||
// 如果是自定义了图片的宽高,那么不受最大宽高限制
|
||||
if (custom) return [width, height]
|
||||
return resizeImgSize(
|
||||
@@ -55,7 +55,7 @@ function getImgShowSize() {
|
||||
|
||||
// 创建icon节点
|
||||
function createIconNode() {
|
||||
let _data = this.nodeData.data
|
||||
let _data = this.getData()
|
||||
if (!_data.icon || _data.icon.length <= 0) {
|
||||
return []
|
||||
}
|
||||
@@ -91,7 +91,7 @@ function createRichTextNode() {
|
||||
let g = new G()
|
||||
// 重新设置富文本节点内容
|
||||
let recoverText = false
|
||||
if (this.nodeData.data.resetRichText) {
|
||||
if (this.getData('resetRichText')) {
|
||||
delete this.nodeData.data.resetRichText
|
||||
recoverText = true
|
||||
}
|
||||
@@ -102,7 +102,7 @@ function createRichTextNode() {
|
||||
}
|
||||
}
|
||||
if (recoverText) {
|
||||
let text = this.nodeData.data.text
|
||||
let text = this.getData('text')
|
||||
// 判断节点内容是否是富文本
|
||||
let isRichText = checkIsRichText(text)
|
||||
// 样式字符串
|
||||
@@ -116,9 +116,11 @@ function createRichTextNode() {
|
||||
// 非富文本
|
||||
text = `<p><span style="${style}">${text}</span></p>`
|
||||
}
|
||||
this.nodeData.data.text = text
|
||||
this.setData({
|
||||
text: text
|
||||
})
|
||||
}
|
||||
let html = `<div>${this.nodeData.data.text}</div>`
|
||||
let html = `<div>${this.getData('text')}</div>`
|
||||
if (!commonCaches.measureRichtextNodeTextSizeEl) {
|
||||
commonCaches.measureRichtextNodeTextSizeEl = document.createElement('div')
|
||||
commonCaches.measureRichtextNodeTextSizeEl.style.position = 'fixed'
|
||||
@@ -157,7 +159,7 @@ function createRichTextNode() {
|
||||
|
||||
// 创建文本节点
|
||||
function createTextNode() {
|
||||
if (this.nodeData.data.richText) {
|
||||
if (this.getData('richText')) {
|
||||
return this.createRichTextNode()
|
||||
}
|
||||
let g = new G()
|
||||
@@ -166,8 +168,8 @@ function createTextNode() {
|
||||
// 文本超长自动换行
|
||||
let textStyle = this.style.getTextFontStyle()
|
||||
let textArr = []
|
||||
if (!isUndef(this.nodeData.data.text)) {
|
||||
textArr = String(this.nodeData.data.text).split(/\n/gim)
|
||||
if (!isUndef(this.getData('text'))) {
|
||||
textArr = String(this.getData('text')).split(/\n/gim)
|
||||
}
|
||||
let maxWidth = this.mindMap.opt.textAutoWrapWidth
|
||||
let isMultiLine = false
|
||||
@@ -215,7 +217,7 @@ function createTextNode() {
|
||||
|
||||
// 创建超链接节点
|
||||
function createHyperlinkNode() {
|
||||
let { hyperlink, hyperlinkTitle } = this.nodeData.data
|
||||
let { hyperlink, hyperlinkTitle } = this.getData()
|
||||
if (!hyperlink) {
|
||||
return
|
||||
}
|
||||
@@ -245,7 +247,7 @@ function createHyperlinkNode() {
|
||||
|
||||
// 创建标签节点
|
||||
function createTagNode() {
|
||||
let tagData = this.nodeData.data.tag
|
||||
let tagData = this.getData('tag')
|
||||
if (!tagData || tagData.length <= 0) {
|
||||
return []
|
||||
}
|
||||
@@ -274,7 +276,7 @@ function createTagNode() {
|
||||
|
||||
// 创建备注节点
|
||||
function createNoteNode() {
|
||||
if (!this.nodeData.data.note) {
|
||||
if (!this.getData('note')) {
|
||||
return null
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
@@ -302,19 +304,20 @@ function createNoteNode() {
|
||||
this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.noteEl)
|
||||
}
|
||||
this.noteEl.innerText = this.nodeData.data.note
|
||||
this.noteEl.innerText = this.getData('note')
|
||||
}
|
||||
node.on('mouseover', () => {
|
||||
let { left, top } = node.node.getBoundingClientRect()
|
||||
const { left, top } = this.getNoteContentPosition()
|
||||
if (!this.mindMap.opt.customNoteContentShow) {
|
||||
this.noteEl.style.left = left + 'px'
|
||||
this.noteEl.style.top = top + iconSize + 'px'
|
||||
this.noteEl.style.top = top + 'px'
|
||||
this.noteEl.style.display = 'block'
|
||||
} else {
|
||||
this.mindMap.opt.customNoteContentShow.show(
|
||||
this.nodeData.data.note,
|
||||
this.getData('note'),
|
||||
left,
|
||||
top + iconSize
|
||||
top,
|
||||
this
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -332,6 +335,19 @@ function createNoteNode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点备注显示位置
|
||||
function getNoteContentPosition() {
|
||||
const iconSize = this.mindMap.themeConfig.iconSize
|
||||
const { scaleY } = this.mindMap.view.getTransformData().transform
|
||||
const iconSizeAddScale = iconSize * scaleY
|
||||
let { left, top } = this._noteData.node.node.getBoundingClientRect()
|
||||
top += iconSizeAddScale
|
||||
return {
|
||||
left,
|
||||
top
|
||||
}
|
||||
}
|
||||
|
||||
// 测量自定义节点内容元素的宽高
|
||||
function measureCustomNodeContentSize(content) {
|
||||
if (!commonCaches.measureCustomNodeContentSizeEl) {
|
||||
@@ -366,6 +382,7 @@ export default {
|
||||
createHyperlinkNode,
|
||||
createTagNode,
|
||||
createNoteNode,
|
||||
getNoteContentPosition,
|
||||
measureCustomNodeContentSize,
|
||||
isUseCustomNodeContent
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ function sumNode(data = []) {
|
||||
}
|
||||
// 创建或更新展开收缩按钮内容
|
||||
function updateExpandBtnNode() {
|
||||
let { expand } = this.nodeData.data
|
||||
let { expand } = this.getData()
|
||||
// 如果本次和上次的展开状态一样则返回
|
||||
if (expand === this._lastExpandBtnType) return
|
||||
if (this._expandBtn) {
|
||||
@@ -129,7 +129,7 @@ function renderExpandBtn() {
|
||||
this.mindMap.execCommand(
|
||||
'SET_NODE_EXPAND',
|
||||
this,
|
||||
!this.nodeData.data.expand
|
||||
!this.getData('expand')
|
||||
)
|
||||
this.mindMap.emit('expand_btn_click', this)
|
||||
})
|
||||
@@ -164,7 +164,7 @@ function showExpandBtn() {
|
||||
function hideExpandBtn() {
|
||||
if (this.mindMap.opt.alwaysShowExpandBtn || this._isMouseenter) return
|
||||
// 非激活状态且展开状态鼠标移出才隐藏按钮
|
||||
let { isActive, expand } = this.nodeData.data
|
||||
let { isActive, expand } = this.getData()
|
||||
if (!isActive && expand) {
|
||||
setTimeout(() => {
|
||||
this.removeExpandBtn()
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
import Node from './Node'
|
||||
import { createUid } from '../../../utils/index'
|
||||
|
||||
// 获取节点概要数据
|
||||
function formatGetGeneralization() {
|
||||
const data = this.getData('generalization')
|
||||
return Array.isArray(data) ? data : data ? [data] : []
|
||||
}
|
||||
|
||||
// 检查是否存在概要
|
||||
function checkHasGeneralization() {
|
||||
return !!this.nodeData.data.generalization
|
||||
return this.formatGetGeneralization().length > 0
|
||||
}
|
||||
|
||||
// 检查是否存在自身的概要,非子节点区间
|
||||
function checkHasSelfGeneralization() {
|
||||
const list = this.formatGetGeneralization()
|
||||
return !!list.find(item => {
|
||||
return !item.range || item.range.length <= 0
|
||||
})
|
||||
}
|
||||
|
||||
// 获取概要节点所在的概要列表里的索引
|
||||
function getGeneralizationNodeIndex(node) {
|
||||
return this._generalizationList.findIndex(item => {
|
||||
return item.generalizationNode.uid === node.uid
|
||||
})
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
@@ -11,27 +32,47 @@ function createGeneralizationNode() {
|
||||
if (this.isGeneralization || !this.checkHasGeneralization()) {
|
||||
return
|
||||
}
|
||||
if (!this._generalizationLine) {
|
||||
this._generalizationLine = this.draw.path()
|
||||
}
|
||||
if (!this._generalizationNode) {
|
||||
this._generalizationNode = new Node({
|
||||
data: {
|
||||
data: this.nodeData.data.generalization
|
||||
},
|
||||
uid: createUid(),
|
||||
renderer: this.renderer,
|
||||
mindMap: this.mindMap,
|
||||
draw: this.draw,
|
||||
isGeneralization: true
|
||||
})
|
||||
this._generalizationNodeWidth = this._generalizationNode.width
|
||||
this._generalizationNodeHeight = this._generalizationNode.height
|
||||
this._generalizationNode.generalizationBelongNode = this
|
||||
if (this.nodeData.data.generalization.isActive) {
|
||||
this.renderer.addActiveNode(this._generalizationNode)
|
||||
let maxWidth = 0
|
||||
let maxHeight = 0
|
||||
const list = this.formatGetGeneralization()
|
||||
list.forEach((item, index) => {
|
||||
let cur = this._generalizationList[index]
|
||||
if (!cur) {
|
||||
cur = this._generalizationList[index] = {}
|
||||
}
|
||||
}
|
||||
// 所属节点
|
||||
cur.node = this
|
||||
// 区间范围
|
||||
cur.range = item.range
|
||||
// 线和节点
|
||||
if (!cur.generalizationLine) {
|
||||
cur.generalizationLine = this.lineDraw.path()
|
||||
}
|
||||
if (!cur.generalizationNode) {
|
||||
cur.generalizationNode = new Node({
|
||||
data: {
|
||||
data: item
|
||||
},
|
||||
uid: createUid(),
|
||||
renderer: this.renderer,
|
||||
mindMap: this.mindMap,
|
||||
isGeneralization: true
|
||||
})
|
||||
}
|
||||
// 关联所属节点
|
||||
cur.generalizationNode.generalizationBelongNode = this
|
||||
// 大小
|
||||
if (cur.generalizationNode.width > maxWidth)
|
||||
maxWidth = cur.generalizationNode.width
|
||||
if (cur.generalizationNode.height > maxHeight)
|
||||
maxHeight = cur.generalizationNode.height
|
||||
// 如果该概要为激活状态,那么加入激活节点列表
|
||||
if (item.isActive) {
|
||||
this.renderer.addNodeToActiveList(cur.generalizationNode)
|
||||
}
|
||||
})
|
||||
this._generalizationNodeWidth = maxWidth
|
||||
this._generalizationNodeHeight = maxHeight
|
||||
}
|
||||
|
||||
// 更新概要节点
|
||||
@@ -44,42 +85,67 @@ function updateGeneralization() {
|
||||
// 渲染概要节点
|
||||
function renderGeneralization() {
|
||||
if (this.isGeneralization) return
|
||||
if (!this.checkHasGeneralization()) {
|
||||
this.updateGeneralizationData()
|
||||
const list = this.formatGetGeneralization()
|
||||
if (list.length <= 0 || this.getData('expand') === false) {
|
||||
this.removeGeneralization()
|
||||
this._generalizationNodeWidth = 0
|
||||
this._generalizationNodeHeight = 0
|
||||
return
|
||||
}
|
||||
if (this.nodeData.data.expand === false) {
|
||||
if (list.length !== this._generalizationList.length) {
|
||||
this.removeGeneralization()
|
||||
return
|
||||
}
|
||||
this.createGeneralizationNode()
|
||||
this.renderer.layout.renderGeneralization(
|
||||
this,
|
||||
this._generalizationLine,
|
||||
this._generalizationNode
|
||||
)
|
||||
this.style.generalizationLine(this._generalizationLine)
|
||||
this._generalizationNode.render()
|
||||
this.renderer.layout.renderGeneralization(this._generalizationList)
|
||||
this._generalizationList.forEach(item => {
|
||||
this.style.generalizationLine(item.generalizationLine)
|
||||
item.generalizationNode.render()
|
||||
})
|
||||
}
|
||||
|
||||
// 更新节点概要数据
|
||||
function updateGeneralizationData() {
|
||||
const childrenLength = this.children.length
|
||||
const list = this.formatGetGeneralization()
|
||||
const newList = []
|
||||
list.forEach(item => {
|
||||
if (!item.range) {
|
||||
newList.push(item)
|
||||
return
|
||||
}
|
||||
if (
|
||||
item.range.length > 0 &&
|
||||
item.range[0] <= childrenLength - 1 &&
|
||||
item.range[1] <= childrenLength - 1
|
||||
) {
|
||||
newList.push(item)
|
||||
}
|
||||
})
|
||||
if (newList.length !== list.length) {
|
||||
this.setData({
|
||||
generalization: newList
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除概要节点
|
||||
function removeGeneralization() {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.remove()
|
||||
this._generalizationLine = null
|
||||
}
|
||||
if (this._generalizationNode) {
|
||||
// 删除概要节点时要同步从激活节点里删除
|
||||
this.renderer.removeActiveNode(this._generalizationNode)
|
||||
this._generalizationNode.remove()
|
||||
this._generalizationNode = null
|
||||
}
|
||||
this._generalizationList.forEach(item => {
|
||||
if (item.generalizationLine) {
|
||||
item.generalizationLine.remove()
|
||||
item.generalizationLine = null
|
||||
}
|
||||
if (item.generalizationNode) {
|
||||
// 删除概要节点时要同步从激活节点里删除
|
||||
this.renderer.removeNodeFromActiveList(item.generalizationNode)
|
||||
item.generalizationNode.remove()
|
||||
item.generalizationNode = null
|
||||
}
|
||||
})
|
||||
this._generalizationList = []
|
||||
// hack修复当激活一个节点时创建概要,然后立即激活创建的概要节点后会重复创建概要节点并且无法删除的问题
|
||||
if (this.generalizationBelongNode) {
|
||||
this.draw
|
||||
this.nodeDraw
|
||||
.find('.generalization_' + this.generalizationBelongNode.uid)
|
||||
.remove()
|
||||
}
|
||||
@@ -88,31 +154,65 @@ function removeGeneralization() {
|
||||
// 隐藏概要节点
|
||||
function hideGeneralization() {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.hide()
|
||||
}
|
||||
if (this._generalizationNode) {
|
||||
this._generalizationNode.hide()
|
||||
}
|
||||
this._generalizationList.forEach(item => {
|
||||
if (item.generalizationLine) item.generalizationLine.hide()
|
||||
if (item.generalizationNode) item.generalizationNode.hide()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示概要节点
|
||||
function showGeneralization() {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.show()
|
||||
}
|
||||
if (this._generalizationNode) {
|
||||
this._generalizationNode.show()
|
||||
this._generalizationList.forEach(item => {
|
||||
if (item.generalizationLine) item.generalizationLine.show()
|
||||
if (item.generalizationNode) item.generalizationNode.show()
|
||||
})
|
||||
}
|
||||
|
||||
// 设置概要节点的透明度
|
||||
function setGeneralizationOpacity(val) {
|
||||
this._generalizationList.forEach(item => {
|
||||
item.generalizationLine.opacity(val)
|
||||
item.generalizationNode.group.opacity(val)
|
||||
})
|
||||
}
|
||||
|
||||
// 处理概要节点鼠标移入事件
|
||||
function handleGeneralizationMouseenter() {
|
||||
const belongNode = this.generalizationBelongNode
|
||||
const list = belongNode.formatGetGeneralization()
|
||||
const index = belongNode.getGeneralizationNodeIndex(this)
|
||||
const generalizationData = list[index]
|
||||
// 区间概要,框子节点
|
||||
if (
|
||||
Array.isArray(generalizationData.range) &&
|
||||
generalizationData.range.length > 0
|
||||
) {
|
||||
this.mindMap.renderer.highlightNode(belongNode, generalizationData.range)
|
||||
} else {
|
||||
// 否则框自己
|
||||
this.mindMap.renderer.highlightNode(belongNode)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理概要节点鼠标移出事件
|
||||
function handleGeneralizationMouseleave() {
|
||||
this.mindMap.renderer.closeHighlightNode()
|
||||
}
|
||||
|
||||
export default {
|
||||
formatGetGeneralization,
|
||||
checkHasGeneralization,
|
||||
checkHasSelfGeneralization,
|
||||
getGeneralizationNodeIndex,
|
||||
createGeneralizationNode,
|
||||
updateGeneralization,
|
||||
updateGeneralizationData,
|
||||
renderGeneralization,
|
||||
removeGeneralization,
|
||||
hideGeneralization,
|
||||
showGeneralization
|
||||
showGeneralization,
|
||||
setGeneralizationOpacity,
|
||||
handleGeneralizationMouseenter,
|
||||
handleGeneralizationMouseleave
|
||||
}
|
||||
|
||||
@@ -25,24 +25,19 @@ class View {
|
||||
this.mindMap.keyCommand.addShortcut('Control+-', () => {
|
||||
this.narrow()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
|
||||
this.reset()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+i', () => {
|
||||
this.fit()
|
||||
})
|
||||
this.mindMap.svg.on('dblclick', () => {
|
||||
if (!this.mindMap.opt.enableDblclickReset) return
|
||||
this.reset()
|
||||
})
|
||||
// 拖动视图
|
||||
this.mindMap.event.on('mousedown', () => {
|
||||
if (this.mindMap.opt.isDisableDrag) return
|
||||
this.sx = this.x
|
||||
this.sy = this.y
|
||||
})
|
||||
this.mindMap.event.on('drag', (e, event) => {
|
||||
if (e.ctrlKey) {
|
||||
// 按住ctrl键拖动为多选
|
||||
// 按住ctrl键拖动为多选
|
||||
// 禁用拖拽
|
||||
if (e.ctrlKey || this.mindMap.opt.isDisableDrag) {
|
||||
return
|
||||
}
|
||||
if (this.firstDrag) {
|
||||
@@ -77,7 +72,7 @@ class View {
|
||||
return customHandleMousewheel(e)
|
||||
}
|
||||
// 鼠标滚轮事件控制缩放
|
||||
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM) {
|
||||
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM || e.ctrlKey) {
|
||||
if (disableMouseWheelZoom) return
|
||||
const { x: clientX, y: clientY } = this.mindMap.toPos(
|
||||
e.clientX,
|
||||
@@ -267,7 +262,7 @@ class View {
|
||||
let drawHeight = rect.height / origTransform.scaleY
|
||||
let drawRatio = drawWidth / drawHeight
|
||||
let { width: elWidth, height: elHeight } =
|
||||
this.mindMap.el.getBoundingClientRect()
|
||||
this.mindMap.elRect
|
||||
elWidth = elWidth - fitPadding * 2
|
||||
elHeight = elHeight - fitPadding * 2
|
||||
let elRatio = elWidth / elHeight
|
||||
|
||||
@@ -13,6 +13,7 @@ class Base {
|
||||
this.mindMap = renderer.mindMap
|
||||
// 绘图对象
|
||||
this.draw = this.mindMap.draw
|
||||
this.lineDraw = this.mindMap.lineDraw
|
||||
// 根节点
|
||||
this.root = null
|
||||
this.lru = new Lru(this.mindMap.opt.maxNodeCacheCount)
|
||||
@@ -90,7 +91,7 @@ class Base {
|
||||
// 数据上没有保存节点引用,但是通过uid找到了缓存的节点,也可以复用
|
||||
newNode = this.lru.get(data.data.uid)
|
||||
// 保存该节点上一次的数据
|
||||
let lastData = JSON.stringify(newNode.nodeData.data)
|
||||
let lastData = JSON.stringify(newNode.getData())
|
||||
let isLayerTypeChange = this.checkIsLayerTypeChange(
|
||||
newNode.layerIndex,
|
||||
layerIndex
|
||||
@@ -126,9 +127,15 @@ class Base {
|
||||
// 数据关联实际节点
|
||||
data._node = newNode
|
||||
if (data.data.isActive) {
|
||||
this.renderer.addActiveNode(newNode)
|
||||
this.renderer.addNodeToActiveList(newNode)
|
||||
}
|
||||
}
|
||||
// 如果当前节点在激活节点列表里,那么添加上激活的状态
|
||||
if (this.mindMap.renderer.findActiveNodeIndex(newNode) !== -1) {
|
||||
newNode.setData({
|
||||
isActive: true
|
||||
})
|
||||
}
|
||||
// 根节点
|
||||
if (isRoot) {
|
||||
newNode.isRoot = true
|
||||
@@ -293,12 +300,12 @@ class Base {
|
||||
let { left, right, top, bottom } = walk(child)
|
||||
// 概要内容的宽度
|
||||
let generalizationWidth =
|
||||
child.checkHasGeneralization() && child.nodeData.data.expand
|
||||
child.checkHasGeneralization() && child.getData('expand')
|
||||
? child._generalizationNodeWidth + generalizationNodeMargin
|
||||
: 0
|
||||
// 概要内容的高度
|
||||
let generalizationHeight =
|
||||
child.checkHasGeneralization() && child.nodeData.data.expand
|
||||
child.checkHasGeneralization() && child.getData('expand')
|
||||
? child._generalizationNodeHeight + generalizationNodeMargin
|
||||
: 0
|
||||
if (left - (dir === 'h' ? generalizationWidth : 0) < _left) {
|
||||
@@ -339,6 +346,50 @@ class Base {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定索引区间的子节点的边界范围
|
||||
getChildrenBoundaries(node, dir, startIndex = 0, endIndex) {
|
||||
let { generalizationLineMargin, generalizationNodeMargin } =
|
||||
this.mindMap.themeConfig
|
||||
const children = node.children.slice(startIndex, endIndex + 1)
|
||||
let left = Infinity
|
||||
let right = -Infinity
|
||||
let top = Infinity
|
||||
let bottom = -Infinity
|
||||
children.forEach(item => {
|
||||
const cur = this.getNodeBoundaries(item, dir)
|
||||
left = cur.left < left ? cur.left : left
|
||||
right = cur.right > right ? cur.right : right
|
||||
top = cur.top < top ? cur.top : top
|
||||
bottom = cur.bottom > bottom ? cur.bottom : bottom
|
||||
})
|
||||
return {
|
||||
left,
|
||||
right,
|
||||
top,
|
||||
bottom,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点概要的渲染边界
|
||||
getNodeGeneralizationRenderBoundaries(item, dir) {
|
||||
let res = null
|
||||
// 区间
|
||||
if (item.range) {
|
||||
res = this.getChildrenBoundaries(
|
||||
item.node,
|
||||
dir,
|
||||
item.range[0],
|
||||
item.range[1]
|
||||
)
|
||||
} else {
|
||||
// 整体概要
|
||||
res = this.getNodeBoundaries(item.node, dir)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 获取节点实际存在几个子节点
|
||||
getNodeActChildrenLength(node) {
|
||||
return node.nodeData.children && node.nodeData.children.length
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
|
||||
// 目录组织图
|
||||
class CatalogOrganization extends Base {
|
||||
@@ -72,11 +72,7 @@ class CatalogOrganization extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
if (node.getData('expand') && node.children && node.children.length) {
|
||||
let marginX = this.getMarginX(layerIndex + 1)
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
if (isRoot) {
|
||||
@@ -114,7 +110,7 @@ class CatalogOrganization extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
// 调整left
|
||||
@@ -159,9 +155,7 @@ class CatalogOrganization extends Base {
|
||||
updateBrothersLeft(node, addWidth) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition() || _index <= index) {
|
||||
// 适配自定义位置
|
||||
@@ -182,9 +176,7 @@ class CatalogOrganization extends Base {
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -247,14 +239,14 @@ class CatalogOrganization extends Base {
|
||||
minx = Math.min(minx, x1)
|
||||
maxx = Math.max(maxx, x1)
|
||||
// 父节点的竖线
|
||||
let line1 = this.draw.path()
|
||||
let line1 = this.lineDraw.path()
|
||||
node.style.line(line1)
|
||||
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
let lin2 = this.lineDraw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
node._lines.push(lin2)
|
||||
@@ -315,7 +307,7 @@ class CatalogOrganization extends Base {
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
let lin2 = this.lineDraw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
node.style.line(lin2)
|
||||
if (maxy < y1 + expandBtnSize) {
|
||||
@@ -343,24 +335,27 @@ class CatalogOrganization extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
renderGeneralization(list) {
|
||||
list.forEach(item => {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationNode.left = right + generalizationNodeMargin
|
||||
item.generalizationNode.top =
|
||||
top + (bottom - top - item.generalizationNode.height) / 2
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun, degToRad } from '../utils'
|
||||
import { walk, asyncRun, degToRad, getNodeIndexInNodeList } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
import utils from './fishboneUtils'
|
||||
|
||||
@@ -112,7 +112,7 @@ class Fishbone extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
let params = { node, parent, layerIndex, ctx: this }
|
||||
@@ -193,9 +193,7 @@ class Fishbone extends Base {
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -252,7 +250,7 @@ class Fishbone extends Base {
|
||||
let nodeLineX = item.left
|
||||
let offset = node.height / 2 + marginY
|
||||
let offsetX = offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
let line = this.draw.path()
|
||||
let line = this.lineDraw.path()
|
||||
if (this.checkIsTop(item)) {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
|
||||
@@ -273,7 +271,7 @@ class Fishbone extends Base {
|
||||
// 从根节点出发的水平线
|
||||
let nodeHalfTop = node.top + node.height / 2
|
||||
let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1)
|
||||
let line = this.draw.path()
|
||||
let line = this.lineDraw.path()
|
||||
line.plot(
|
||||
`M ${node.left + node.width},${nodeHalfTop} L ${
|
||||
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
@@ -308,7 +306,7 @@ class Fishbone extends Base {
|
||||
})
|
||||
// 斜线
|
||||
if (len >= 0) {
|
||||
let line = this.draw.path()
|
||||
let line = this.lineDraw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * this.indent
|
||||
lineLength = Math.max(lineLength, 0)
|
||||
@@ -359,24 +357,27 @@ class Fishbone extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
renderGeneralization(list) {
|
||||
list.forEach(item => {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationNode.left = right + generalizationNodeMargin
|
||||
item.generalizationNode.top =
|
||||
top + (bottom - top - item.generalizationNode.height) / 2
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
const degToRad = deg => {
|
||||
@@ -127,7 +127,7 @@ class Fishbone extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
// 调整top
|
||||
@@ -237,9 +237,7 @@ class Fishbone extends Base {
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -307,7 +305,7 @@ class Fishbone extends Base {
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
let line = this.lineDraw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * 0.3
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
import { CONSTANTS } from '../utils/constant'
|
||||
|
||||
const degToRad = deg => {
|
||||
@@ -110,7 +110,7 @@ class Fishbone extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
// 调整top
|
||||
@@ -206,9 +206,7 @@ class Fishbone extends Base {
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -276,7 +274,7 @@ class Fishbone extends Base {
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
let line = this.lineDraw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
let lineLength = maxx - node.left - node.width * 0.3
|
||||
if (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
|
||||
// 逻辑结构图
|
||||
class LogicalStructure extends Base {
|
||||
@@ -77,11 +77,7 @@ class LogicalStructure extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
if (node.getData('expand') && node.children && node.children.length) {
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
|
||||
let top = node.top + node.height / 2 - node.childrenAreaHeight / 2
|
||||
@@ -103,7 +99,7 @@ class LogicalStructure extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
|
||||
@@ -124,9 +120,7 @@ class LogicalStructure extends Base {
|
||||
updateBrothers(node, addHeight) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.uid === node.uid || item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -271,24 +265,27 @@ class LogicalStructure extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
renderGeneralization(list) {
|
||||
list.forEach(item => {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationNode.left = right + generalizationNodeMargin
|
||||
item.generalizationNode.top =
|
||||
top + (bottom - top - item.generalizationNode.height) / 2
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 思维导图
|
||||
@@ -117,7 +117,7 @@ class MindMap extends Base {
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.getData('expand') &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
@@ -148,7 +148,7 @@ class MindMap extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
|
||||
@@ -171,9 +171,7 @@ class MindMap extends Base {
|
||||
let childrenList = node.parent.children.filter(item => {
|
||||
return item.dir === node.dir
|
||||
})
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -360,32 +358,34 @@ class MindMap extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h', isLeft)
|
||||
let x = isLeft
|
||||
? left - generalizationLineMargin
|
||||
: right + generalizationLineMargin
|
||||
let x1 = x
|
||||
let y1 = top
|
||||
let x2 = x
|
||||
let y2 = bottom
|
||||
let cx = x1 + (isLeft ? -20 : 20)
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left =
|
||||
x +
|
||||
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
|
||||
(isLeft ? gNode.width : 0)
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
renderGeneralization(list) {
|
||||
list.forEach(item => {
|
||||
let isLeft = item.node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
|
||||
let x = isLeft
|
||||
? left - generalizationLineMargin
|
||||
: right + generalizationLineMargin
|
||||
let x1 = x
|
||||
let y1 = top
|
||||
let x2 = x
|
||||
let y2 = bottom
|
||||
let cx = x1 + (isLeft ? -20 : 20)
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationNode.left =
|
||||
x +
|
||||
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
|
||||
(isLeft ? item.generalizationNode.width : 0)
|
||||
item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
|
||||
// 组织结构图
|
||||
// 和逻辑结构图基本一样,只是方向变成向下生长,所以先计算节点的top,后计算节点的left、最后调整节点的left即可
|
||||
@@ -79,7 +79,7 @@ class OrganizationStructure extends Base {
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.getData('expand') &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
@@ -104,7 +104,7 @@ class OrganizationStructure extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
// 判断子节点所占的宽度之和是否大于该节点自身,大于则需要调整位置
|
||||
@@ -125,9 +125,7 @@ class OrganizationStructure extends Base {
|
||||
updateBrothers(node, addWidth) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -218,7 +216,7 @@ class OrganizationStructure extends Base {
|
||||
minx = Math.min(x1, minx)
|
||||
maxx = Math.max(x1, maxx)
|
||||
// 父节点的竖线
|
||||
let line1 = this.draw.path()
|
||||
let line1 = this.lineDraw.path()
|
||||
node.style.line(line1)
|
||||
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
|
||||
line1.plot(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
|
||||
@@ -226,7 +224,7 @@ class OrganizationStructure extends Base {
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
let lin2 = this.lineDraw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
node._lines.push(lin2)
|
||||
@@ -245,24 +243,27 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'v')
|
||||
let x1 = left
|
||||
let y1 = bottom + generalizationLineMargin
|
||||
let x2 = right
|
||||
let y2 = bottom + generalizationLineMargin
|
||||
let cx = x1 + (x2 - x1) / 2
|
||||
let cy = y1 + 20
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.top = bottom + generalizationNodeMargin
|
||||
gNode.left = left + (right - left - gNode.width) / 2
|
||||
renderGeneralization(list) {
|
||||
list.forEach(item => {
|
||||
let {
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeGeneralizationRenderBoundaries(item, 'v')
|
||||
let x1 = left
|
||||
let y1 = bottom + generalizationLineMargin
|
||||
let x2 = right
|
||||
let y2 = bottom + generalizationLineMargin
|
||||
let cx = x1 + (x2 - x1) / 2
|
||||
let cy = y1 + 20
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationNode.top = bottom + generalizationNodeMargin
|
||||
item.generalizationNode.left = left + (right - left - item.generalizationNode.width) / 2
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 时间轴
|
||||
@@ -80,11 +80,7 @@ class Timeline extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
if (node.getData('expand') && node.children && node.children.length) {
|
||||
let marginX = this.getMarginX(layerIndex + 1)
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
if (isRoot) {
|
||||
@@ -122,7 +118,7 @@ class Timeline extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
// 调整left
|
||||
@@ -208,9 +204,7 @@ class Timeline extends Base {
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -275,7 +269,7 @@ class Timeline extends Base {
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
let line = this.draw.path()
|
||||
let line = this.lineDraw.path()
|
||||
expandBtnSize = len > 0 ? expandBtnSize : 0
|
||||
if (
|
||||
node.parent &&
|
||||
@@ -317,24 +311,26 @@ class Timeline extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
renderGeneralization(list) {
|
||||
list.forEach(item => {
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
|
||||
let x1 = right + generalizationLineMargin
|
||||
let y1 = top
|
||||
let x2 = right + generalizationLineMargin
|
||||
let y2 = bottom
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationNode.left = right + generalizationNodeMargin
|
||||
item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 竖向时间轴
|
||||
@@ -97,11 +97,7 @@ class VerticalTimeline extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
if (node.getData('expand') && node.children && node.children.length) {
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
// 定位二级节点的top
|
||||
if (isRoot) {
|
||||
@@ -135,7 +131,7 @@ class VerticalTimeline extends Base {
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
if (!node.getData('expand')) {
|
||||
return
|
||||
}
|
||||
if (isRoot) return
|
||||
@@ -155,9 +151,7 @@ class VerticalTimeline extends Base {
|
||||
updateBrothers(node, addHeight) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
// 自定义节点位置
|
||||
if (item.hasCustomPosition()) return
|
||||
@@ -201,9 +195,7 @@ class VerticalTimeline extends Base {
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, childrenList)
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
@@ -390,32 +382,35 @@ class VerticalTimeline extends Base {
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h', isLeft)
|
||||
let x = isLeft
|
||||
? left - generalizationLineMargin
|
||||
: right + generalizationLineMargin
|
||||
let x1 = x
|
||||
let y1 = top
|
||||
let x2 = x
|
||||
let y2 = bottom
|
||||
let cx = x1 + (isLeft ? -20 : 20)
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left =
|
||||
x +
|
||||
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
|
||||
(isLeft ? gNode.width : 0)
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
renderGeneralization(list) {
|
||||
list.forEach(item => {
|
||||
let isLeft = item.node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeGeneralizationRenderBoundaries(item, 'h')
|
||||
let x = isLeft
|
||||
? left - generalizationLineMargin
|
||||
: right + generalizationLineMargin
|
||||
let x1 = x
|
||||
let y1 = top
|
||||
let x2 = x
|
||||
let y2 = bottom
|
||||
let cx = x1 + (isLeft ? -20 : 20)
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationNode.left =
|
||||
x +
|
||||
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
|
||||
(isLeft ? item.generalizationNode.width : 0)
|
||||
item.generalizationNode.top =
|
||||
top + (bottom - top - item.generalizationNode.height) / 2
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import JSZip from 'jszip'
|
||||
import xmlConvert from 'xml-js'
|
||||
import { getTextFromHtml, isUndef } from '../utils/index'
|
||||
import {
|
||||
getTextFromHtml,
|
||||
imgToDataUrl,
|
||||
parseDataUrl,
|
||||
getImageSize,
|
||||
isUndef
|
||||
} from '../utils/index'
|
||||
getSummaryText,
|
||||
getSummaryText2,
|
||||
getRoot,
|
||||
getItemByName,
|
||||
getElementsByType,
|
||||
addSummaryData,
|
||||
handleNodeImageFromXmind,
|
||||
handleNodeImageToXmind,
|
||||
getXmindContentXmlData,
|
||||
parseNodeGeneralizationToXmind
|
||||
} from '../utils/xmind'
|
||||
|
||||
// 解析.xmind文件
|
||||
const parseXmindFile = file => {
|
||||
@@ -43,18 +49,18 @@ const parseXmindFile = file => {
|
||||
|
||||
// 转换xmind数据
|
||||
const transformXmind = async (content, files) => {
|
||||
let data = JSON.parse(content)[0]
|
||||
let nodeTree = data.rootTopic
|
||||
let newTree = {}
|
||||
let waitLoadImageList = []
|
||||
let walk = async (node, newNode) => {
|
||||
const data = JSON.parse(content)[0]
|
||||
const nodeTree = data.rootTopic
|
||||
const newTree = {}
|
||||
const waitLoadImageList = []
|
||||
const walk = async (node, newNode) => {
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: isUndef(node.title) ? '' : node.title
|
||||
}
|
||||
// 节点备注
|
||||
if (node.notes) {
|
||||
let notesData = node.notes.realHTML || node.notes.plain
|
||||
const notesData = node.notes.realHTML || node.notes.plain
|
||||
newNode.data.note = notesData ? notesData.content || '' : ''
|
||||
}
|
||||
// 超链接
|
||||
@@ -66,41 +72,26 @@ const transformXmind = async (content, files) => {
|
||||
newNode.data.tag = node.labels
|
||||
}
|
||||
// 图片
|
||||
if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) {
|
||||
// 处理异步逻辑
|
||||
let resolve = null
|
||||
let promise = new Promise(_resolve => {
|
||||
resolve = _resolve
|
||||
})
|
||||
waitLoadImageList.push(promise)
|
||||
try {
|
||||
// 读取图片
|
||||
let imageType = /\.([^.]+)$/.exec(node.image.src)[1]
|
||||
let imageBase64 =
|
||||
`data:image/${imageType};base64,` +
|
||||
(await files['resources/' + node.image.src.split('/')[1]].async(
|
||||
'base64'
|
||||
))
|
||||
newNode.data.image = imageBase64
|
||||
// 如果图片尺寸不存在
|
||||
if (!node.image.width && !node.image.height) {
|
||||
let imageSize = await getImageSize(imageBase64)
|
||||
newNode.data.imageSize = {
|
||||
width: imageSize.width,
|
||||
height: imageSize.height
|
||||
}
|
||||
} else {
|
||||
newNode.data.imageSize = {
|
||||
width: node.image.width,
|
||||
height: node.image.height
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
resolve()
|
||||
}
|
||||
handleNodeImageFromXmind(node, newNode, waitLoadImageList, files)
|
||||
// 概要
|
||||
const selfSummary = []
|
||||
const childrenSummary = []
|
||||
if (newNode._summary) {
|
||||
selfSummary.push(newNode._summary)
|
||||
}
|
||||
if (Array.isArray(node.summaries) && node.summaries.length > 0) {
|
||||
node.summaries.forEach(item => {
|
||||
addSummaryData(
|
||||
selfSummary,
|
||||
childrenSummary,
|
||||
() => {
|
||||
return getSummaryText(node, item.topicId)
|
||||
},
|
||||
item.range
|
||||
)
|
||||
})
|
||||
}
|
||||
newNode.data.generalization = selfSummary
|
||||
// 子节点
|
||||
newNode.children = []
|
||||
if (
|
||||
@@ -108,9 +99,12 @@ const transformXmind = async (content, files) => {
|
||||
node.children.attached &&
|
||||
node.children.attached.length > 0
|
||||
) {
|
||||
node.children.attached.forEach(item => {
|
||||
let newChild = {}
|
||||
node.children.attached.forEach((item, index) => {
|
||||
const newChild = {}
|
||||
newNode.children.push(newChild)
|
||||
if (childrenSummary[index]) {
|
||||
newChild._summary = childrenSummary[index]
|
||||
}
|
||||
walk(item, newChild)
|
||||
})
|
||||
}
|
||||
@@ -122,39 +116,21 @@ const transformXmind = async (content, files) => {
|
||||
|
||||
// 转换旧版xmind数据,xmind8
|
||||
const transformOldXmind = content => {
|
||||
let data = JSON.parse(content)
|
||||
let elements = data.elements
|
||||
let root = null
|
||||
let getRoot = arr => {
|
||||
if (!arr) return
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (!root && arr[i].name === 'topic') {
|
||||
root = arr[i]
|
||||
return
|
||||
}
|
||||
}
|
||||
arr.forEach(item => {
|
||||
getRoot(item.elements)
|
||||
})
|
||||
}
|
||||
getRoot(elements)
|
||||
let newTree = {}
|
||||
let getItemByName = (arr, name) => {
|
||||
return arr.find(item => {
|
||||
return item.name === name
|
||||
})
|
||||
}
|
||||
let walk = (node, newNode) => {
|
||||
let nodeElements = node.elements
|
||||
const data = JSON.parse(content)
|
||||
const elements = data.elements
|
||||
const root = getRoot(elements)
|
||||
const newTree = {}
|
||||
const walk = (node, newNode) => {
|
||||
const nodeElements = node.elements
|
||||
let nodeTitle = getItemByName(nodeElements, 'title')
|
||||
nodeTitle = nodeTitle && nodeTitle.elements && nodeTitle.elements[0].text
|
||||
// 节点内容
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: isUndef(nodeTitle) ? '' : nodeTitle
|
||||
}
|
||||
// 节点备注
|
||||
try {
|
||||
// 节点备注
|
||||
let notesElement = getItemByName(nodeElements, 'notes')
|
||||
const notesElement = getItemByName(nodeElements, 'notes')
|
||||
if (notesElement) {
|
||||
newNode.data.note =
|
||||
notesElement.elements[0].elements[0].elements[0].text
|
||||
@@ -162,8 +138,8 @@ const transformOldXmind = content => {
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
// 超链接
|
||||
try {
|
||||
// 超链接
|
||||
if (
|
||||
node.attributes &&
|
||||
node.attributes['xlink:href'] &&
|
||||
@@ -174,9 +150,9 @@ const transformOldXmind = content => {
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
// 标签
|
||||
try {
|
||||
// 标签
|
||||
let labelsElement = getItemByName(nodeElements, 'labels')
|
||||
const labelsElement = getItemByName(nodeElements, 'labels')
|
||||
if (labelsElement) {
|
||||
newNode.data.tag = labelsElement.elements.map(item => {
|
||||
return item.elements[0].text
|
||||
@@ -185,22 +161,50 @@ const transformOldXmind = content => {
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
const childrenItem = getItemByName(nodeElements, 'children')
|
||||
// 概要
|
||||
const selfSummary = []
|
||||
const childrenSummary = []
|
||||
try {
|
||||
if (newNode._summary) {
|
||||
selfSummary.push(newNode._summary)
|
||||
}
|
||||
const summariesItem = getItemByName(nodeElements, 'summaries')
|
||||
if (
|
||||
summariesItem &&
|
||||
Array.isArray(summariesItem.elements) &&
|
||||
summariesItem.elements.length > 0
|
||||
) {
|
||||
summariesItem.elements.forEach(item => {
|
||||
addSummaryData(
|
||||
selfSummary,
|
||||
childrenSummary,
|
||||
() => {
|
||||
return getSummaryText2(childrenItem, item.attributes['topic-id'])
|
||||
},
|
||||
item.attributes.range
|
||||
)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
newNode.data.generalization = selfSummary
|
||||
// 子节点
|
||||
newNode.children = []
|
||||
let _children = getItemByName(nodeElements, 'children')
|
||||
if (_children && _children.elements && _children.elements.length > 0) {
|
||||
_children.elements.forEach(item => {
|
||||
if (item.name === 'topics') {
|
||||
;(item.elements || []).forEach(item2 => {
|
||||
let newChild = {}
|
||||
newNode.children.push(newChild)
|
||||
walk(item2, newChild)
|
||||
})
|
||||
} else {
|
||||
let newChild = {}
|
||||
newNode.children.push(newChild)
|
||||
walk(item, newChild)
|
||||
if (
|
||||
childrenItem &&
|
||||
childrenItem.elements &&
|
||||
childrenItem.elements.length > 0
|
||||
) {
|
||||
const children = getElementsByType(childrenItem.elements, 'attached')
|
||||
children.forEach((item, index) => {
|
||||
const newChild = {}
|
||||
newNode.children.push(newChild)
|
||||
if (childrenSummary[index]) {
|
||||
newChild._summary = childrenSummary[index]
|
||||
}
|
||||
walk(item, newChild)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -209,6 +213,7 @@ const transformOldXmind = content => {
|
||||
}
|
||||
|
||||
// 数据转换为xmind文件
|
||||
// 直接转换为最新版本的xmind文件 2023.09.11172
|
||||
const transformToXmind = async (data, name) => {
|
||||
const id = 'simpleMindMap_' + Date.now()
|
||||
const imageList = []
|
||||
@@ -244,38 +249,7 @@ const transformToXmind = async (data, name) => {
|
||||
newData.labels = node.data.tag || []
|
||||
}
|
||||
// 图片
|
||||
if (node.data.image) {
|
||||
// 处理异步逻辑
|
||||
let resolve = null
|
||||
let promise = new Promise(_resolve => {
|
||||
resolve = _resolve
|
||||
})
|
||||
waitLoadImageList.push(promise)
|
||||
try {
|
||||
let imgName = ''
|
||||
let imgData = node.data.image
|
||||
// base64之外的其他图片要先转换成data:url
|
||||
if (!/^data:/.test(node.data.image)) {
|
||||
imgData = await imgToDataUrl(node.data.image)
|
||||
}
|
||||
// 从data:url中解析出图片类型和ase64
|
||||
let dataUrlRes = parseDataUrl(imgData)
|
||||
imgName = 'image_' + imageList.length + '.' + dataUrlRes.type
|
||||
imageList.push({
|
||||
name: imgName,
|
||||
data: dataUrlRes.base64
|
||||
})
|
||||
newData.image = {
|
||||
src: 'xap:resources/' + imgName,
|
||||
width: node.data.imageSize.width,
|
||||
height: node.data.imageSize.height
|
||||
}
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
handleNodeImageToXmind(node, newNode, waitLoadImageList, imageList)
|
||||
// 样式
|
||||
// 暂时不考虑样式
|
||||
if (isRoot) {
|
||||
@@ -293,6 +267,20 @@ const transformToXmind = async (data, name) => {
|
||||
newNode[key] = newData[key]
|
||||
})
|
||||
}
|
||||
// 概要
|
||||
const { summary, summaries } = parseNodeGeneralizationToXmind(node)
|
||||
if (isRoot) {
|
||||
if (summaries.length > 0) {
|
||||
newNode.rootTopic.children.summary = summary
|
||||
newNode.rootTopic.summaries = summaries
|
||||
}
|
||||
} else {
|
||||
if (summaries.length > 0) {
|
||||
newNode.children.summary = summary
|
||||
newNode.summaries = summaries
|
||||
}
|
||||
}
|
||||
// 子节点
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach(child => {
|
||||
let newChild = {}
|
||||
@@ -309,10 +297,15 @@ const transformToXmind = async (data, name) => {
|
||||
zip.file('content.json', JSON.stringify(contentData))
|
||||
zip.file(
|
||||
'metadata.json',
|
||||
`{"modifier":"","dataStructureVersion":"1","layoutEngineVersion":"2","activeSheetId":"${id}"}`
|
||||
`{"modifier":"","dataStructureVersion":"2","creator":{"name":"mind-map"},"layoutEngineVersion":"3","activeSheetId":"${id}"}`
|
||||
)
|
||||
zip.file('content.xml', getXmindContentXmlData())
|
||||
const manifestData = {
|
||||
'file-entries': { 'content.json': {}, 'metadata.json': {} }
|
||||
'file-entries': {
|
||||
'content.json': {},
|
||||
'metadata.json': {},
|
||||
'Thumbnails/thumbnail.png': {}
|
||||
}
|
||||
}
|
||||
// 图片
|
||||
if (imageList.length > 0) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import associativeLineTextMethods from './associativeLine/associativeLineText'
|
||||
class AssociativeLine {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.draw = this.mindMap.draw
|
||||
this.associativeLineDraw = this.mindMap.associativeLineDraw
|
||||
// 当前所有连接线
|
||||
this.lineList = []
|
||||
// 当前激活的连接线
|
||||
@@ -98,7 +98,7 @@ class AssociativeLine {
|
||||
|
||||
// 创建箭头
|
||||
createMarker() {
|
||||
return this.draw.marker(20, 20, add => {
|
||||
return this.associativeLineDraw.marker(20, 20, add => {
|
||||
add.ref(12, 5)
|
||||
add.size(10, 10)
|
||||
add.attr('orient', 'auto-start-reverse')
|
||||
@@ -142,7 +142,7 @@ class AssociativeLine {
|
||||
null,
|
||||
cur => {
|
||||
if (!cur) return
|
||||
let data = cur.nodeData.data
|
||||
let data = cur.getData()
|
||||
if (
|
||||
data.associativeLineTargets &&
|
||||
data.associativeLineTargets.length > 0
|
||||
@@ -161,7 +161,7 @@ class AssociativeLine {
|
||||
ids.forEach((uid, index) => {
|
||||
let toNode = idToNode.get(uid)
|
||||
if (!node || !toNode) return
|
||||
const associativeLinePoint = (node.nodeData.data.associativeLinePoint ||
|
||||
const associativeLinePoint = (node.getData('associativeLinePoint') ||
|
||||
[])[index]
|
||||
// 切换结构和布局,都会更新坐标
|
||||
const [startPoint, endPoint] = this.updateAllLinesPos(
|
||||
@@ -194,7 +194,7 @@ class AssociativeLine {
|
||||
toNode
|
||||
)
|
||||
// 虚线
|
||||
let path = this.draw.path()
|
||||
let path = this.associativeLineDraw.path()
|
||||
path
|
||||
.stroke({
|
||||
width: associativeLineWidth,
|
||||
@@ -205,7 +205,7 @@ class AssociativeLine {
|
||||
path.plot(pathStr)
|
||||
path.marker('end', this.marker)
|
||||
// 不可见的点击线
|
||||
let clickPath = this.draw.path()
|
||||
let clickPath = this.associativeLineDraw.path()
|
||||
clickPath
|
||||
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
|
||||
.fill({ color: 'none' })
|
||||
@@ -276,6 +276,7 @@ class AssociativeLine {
|
||||
controlPoints[1]
|
||||
)
|
||||
this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
|
||||
this.front()
|
||||
}
|
||||
|
||||
// 移除所有连接线
|
||||
@@ -300,9 +301,10 @@ class AssociativeLine {
|
||||
let { associativeLineWidth, associativeLineColor } =
|
||||
this.mindMap.themeConfig
|
||||
if (this.isCreatingLine || !fromNode) return
|
||||
this.front()
|
||||
this.isCreatingLine = true
|
||||
this.creatingStartNode = fromNode
|
||||
this.creatingLine = this.draw.path()
|
||||
this.creatingLine = this.associativeLineDraw.path()
|
||||
this.creatingLine
|
||||
.stroke({
|
||||
width: associativeLineWidth,
|
||||
@@ -357,12 +359,13 @@ class AssociativeLine {
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
// 检测当前移动到的目标节点
|
||||
checkOverlapNode(x, y) {
|
||||
this.overlapNode = null
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
if (node.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.setNodeActive(node, false)
|
||||
if (node.getData('isActive')) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
|
||||
}
|
||||
if (node.uid === this.creatingStartNode.uid || this.overlapNode) {
|
||||
return
|
||||
@@ -374,8 +377,8 @@ class AssociativeLine {
|
||||
this.overlapNode = node
|
||||
}
|
||||
})
|
||||
if (this.overlapNode && !this.overlapNode.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
|
||||
if (this.overlapNode && !this.overlapNode.getData('isActive')) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,21 +386,22 @@ class AssociativeLine {
|
||||
completeCreateLine(node) {
|
||||
if (this.creatingStartNode.uid === node.uid) return
|
||||
this.addLine(this.creatingStartNode, node)
|
||||
if (this.overlapNode && this.overlapNode.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
|
||||
if (this.overlapNode && this.overlapNode.getData('isActive')) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, false)
|
||||
}
|
||||
this.isCreatingLine = false
|
||||
this.creatingStartNode = null
|
||||
this.creatingLine.remove()
|
||||
this.creatingLine = null
|
||||
this.overlapNode = null
|
||||
this.back()
|
||||
}
|
||||
|
||||
// 添加连接线
|
||||
addLine(fromNode, toNode) {
|
||||
if (!fromNode || !toNode) return
|
||||
// 目标节点如果没有id,则生成一个id
|
||||
let uid = toNode.nodeData.data.uid
|
||||
let uid = toNode.getData('uid')
|
||||
if (!uid) {
|
||||
uid = uuid()
|
||||
this.mindMap.execCommand('SET_NODE_DATA', toNode, {
|
||||
@@ -405,7 +409,7 @@ class AssociativeLine {
|
||||
})
|
||||
}
|
||||
// 将目标节点id保存起来
|
||||
let list = fromNode.nodeData.data.associativeLineTargets || []
|
||||
let list = fromNode.getData('associativeLineTargets') || []
|
||||
// 连线节点是否存在相同的id,存在则阻止添加关联线
|
||||
const sameLine = list.some(item => item === uid)
|
||||
if (sameLine) {
|
||||
@@ -421,7 +425,7 @@ class AssociativeLine {
|
||||
endPoint.y
|
||||
)
|
||||
let offsetList =
|
||||
fromNode.nodeData.data.associativeLineTargetControlOffsets || []
|
||||
fromNode.getData('associativeLineTargetControlOffsets') || []
|
||||
// 保存的实际是控制点和端点的差值,否则当节点位置改变了,控制点还是原来的位置,连线就不对了
|
||||
offsetList[list.length - 1] = [
|
||||
{
|
||||
@@ -433,7 +437,7 @@ class AssociativeLine {
|
||||
y: controlPoints[1].y - endPoint.y
|
||||
}
|
||||
]
|
||||
let associativeLinePoint = fromNode.nodeData.data.associativeLinePoint || []
|
||||
let associativeLinePoint = fromNode.getData('associativeLinePoint') || []
|
||||
// 记录关联的起始|结束坐标
|
||||
associativeLinePoint[list.length - 1] = { startPoint, endPoint }
|
||||
this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
|
||||
@@ -453,14 +457,14 @@ class AssociativeLine {
|
||||
associativeLinePoint,
|
||||
associativeLineTargetControlOffsets,
|
||||
associativeLineText
|
||||
} = node.nodeData.data
|
||||
} = node.getData()
|
||||
associativeLinePoint = associativeLinePoint || []
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
// 更新关联线文本数据
|
||||
let newAssociativeLineText = {}
|
||||
if (associativeLineText) {
|
||||
Object.keys(associativeLineText).forEach(item => {
|
||||
if (item !== toNode.nodeData.data.uid) {
|
||||
if (item !== toNode.getData('uid')) {
|
||||
newAssociativeLineText[item] = associativeLineText[item]
|
||||
}
|
||||
})
|
||||
@@ -500,6 +504,7 @@ class AssociativeLine {
|
||||
}
|
||||
this.activeLine = null
|
||||
this.removeControls()
|
||||
this.back()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,6 +531,19 @@ class AssociativeLine {
|
||||
this.showControls()
|
||||
this.isNodeDragging = false
|
||||
}
|
||||
|
||||
// 关联线顶层显示
|
||||
front() {
|
||||
if (this.mindMap.opt.associativeLineIsAlwaysAboveNode) return
|
||||
this.associativeLineDraw.front()
|
||||
}
|
||||
|
||||
// 关联线回到原有层级
|
||||
back() {
|
||||
if (this.mindMap.opt.associativeLineIsAlwaysAboveNode) return
|
||||
this.associativeLineDraw.back() // 最底层
|
||||
this.associativeLineDraw.forward() // 连线层上面
|
||||
}
|
||||
}
|
||||
|
||||
AssociativeLine.instanceName = 'associativeLine'
|
||||
|
||||
345
simple-mind-map/src/plugins/Cooperate.js
Normal file
@@ -0,0 +1,345 @@
|
||||
import * as Y from 'yjs'
|
||||
import { WebrtcProvider } from 'y-webrtc'
|
||||
import { isSameObject, simpleDeepClone, getType, isUndef } from '../utils/index'
|
||||
|
||||
// 协同插件
|
||||
class Cooperate {
|
||||
constructor(opt) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
// yjs文档
|
||||
this.ydoc = new Y.Doc()
|
||||
// 共享数据
|
||||
this.ymap = null
|
||||
// 连接提供者
|
||||
this.provider = null
|
||||
// 感知数据
|
||||
this.awareness = null
|
||||
this.currentAwarenessData = []
|
||||
this.waitNodeUidMap = {} // 该列表中的uid对应的节点还未渲染完毕
|
||||
// 当前的平级对象类型的思维导图数据
|
||||
this.currentData = null
|
||||
// 用户信息
|
||||
this.userInfo = null
|
||||
// 绑定事件
|
||||
this.bindEvent()
|
||||
// 处理实例化时传入的思维导图数据
|
||||
if (this.mindMap.opt.data) {
|
||||
this.initData(this.mindMap.opt.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
initData(data) {
|
||||
data = simpleDeepClone(data)
|
||||
// 解绑原来的数据
|
||||
if (this.ymap) {
|
||||
this.ymap.unobserve(this.onObserve)
|
||||
}
|
||||
// 创建共享数据
|
||||
this.ymap = this.ydoc.getMap()
|
||||
// 思维导图树结构转平级对象结构
|
||||
this.currentData = this.transformTreeDataToObject(data)
|
||||
// 将思维导图数据添加到共享数据中
|
||||
Object.keys(this.currentData).forEach(uid => {
|
||||
this.ymap.set(uid, this.currentData[uid])
|
||||
})
|
||||
// 监听数据同步
|
||||
this.onObserve = this.onObserve.bind(this)
|
||||
this.ymap.observe(this.onObserve)
|
||||
}
|
||||
|
||||
// 获取yjs doc实例
|
||||
getDoc() {
|
||||
return this.ydoc
|
||||
}
|
||||
|
||||
// 设置连接提供者
|
||||
setProvider(provider, webrtcProviderConfig = {}) {
|
||||
const { roomName, signalingList, ...otherConfig } = webrtcProviderConfig
|
||||
this.provider =
|
||||
provider ||
|
||||
new WebrtcProvider(roomName, this.ydoc, {
|
||||
signaling: signalingList,
|
||||
...otherConfig
|
||||
})
|
||||
this.awareness = this.provider.awareness
|
||||
|
||||
// 监听状态同步事件
|
||||
this.onAwareness = this.onAwareness.bind(this)
|
||||
this.awareness.on('change', this.onAwareness)
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
// 监听思维导图改变
|
||||
this.onDataChange = this.onDataChange.bind(this)
|
||||
this.mindMap.on('data_change', this.onDataChange)
|
||||
|
||||
// 监听思维导图节点激活事件
|
||||
this.onNodeActive = this.onNodeActive.bind(this)
|
||||
this.mindMap.on('node_active', this.onNodeActive)
|
||||
|
||||
// 监听思维导图渲染完毕事件
|
||||
this.onNodeTreeRenderEnd = this.onNodeTreeRenderEnd.bind(this)
|
||||
this.mindMap.on('node_tree_render_end', this.onNodeTreeRenderEnd)
|
||||
|
||||
// 监听设置思维导图数据事件
|
||||
this.initData = this.initData.bind(this)
|
||||
this.mindMap.on('set_data', this.initData)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
unBindEvent() {
|
||||
if (this.ymap) {
|
||||
this.ymap.unobserve(this.onObserve)
|
||||
}
|
||||
this.mindMap.off('data_change', this.onDataChange)
|
||||
this.mindMap.off('node_active', this.onNodeActive)
|
||||
this.mindMap.off('node_tree_render_end', this.onNodeTreeRenderEnd)
|
||||
this.mindMap.off('set_data', this.initData)
|
||||
this.ydoc.destroy()
|
||||
}
|
||||
|
||||
// 数据同步时的处理,更新当前思维导图
|
||||
onObserve(event) {
|
||||
const data = event.target.toJSON()
|
||||
// 如果数据没有改变直接返回
|
||||
if (isSameObject(data, this.currentData)) return
|
||||
this.currentData = data
|
||||
// 平级对象转树结构
|
||||
const res = this.transformObjectToTreeData(data)
|
||||
if (!res) return
|
||||
// 更新思维导图画布
|
||||
this.mindMap.renderer.setData(res)
|
||||
this.mindMap.render()
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
|
||||
// 当前思维导图改变后的处理,触发同步
|
||||
onDataChange(data) {
|
||||
const res = this.transformTreeDataToObject(data)
|
||||
this.updateChanges(res)
|
||||
}
|
||||
|
||||
// 找出更新点
|
||||
updateChanges(data) {
|
||||
const oldData = this.currentData
|
||||
this.currentData = data
|
||||
this.ydoc.transact(() => {
|
||||
// 找出新增的或修改的
|
||||
Object.keys(data).forEach(uid => {
|
||||
// 新增的或已经存在的,如果数据发生了改变
|
||||
if (!oldData[uid] || !isSameObject(oldData[uid], data[uid])) {
|
||||
this.ymap.set(uid, data[uid])
|
||||
}
|
||||
})
|
||||
// 找出删除的
|
||||
Object.keys(oldData).forEach(uid => {
|
||||
if (!data[uid]) {
|
||||
this.ymap.delete(uid)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 节点激活状态改变后触发感知数据同步
|
||||
onNodeActive(node, nodeList) {
|
||||
if (this.userInfo) {
|
||||
this.awareness.setLocalStateField(this.userInfo.name, {
|
||||
// 用户信息
|
||||
userInfo: {
|
||||
...this.userInfo
|
||||
},
|
||||
// 当前激活的节点id列表
|
||||
nodeIdList: nodeList.map(item => {
|
||||
return item.uid
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 节点树渲染完毕事件
|
||||
onNodeTreeRenderEnd() {
|
||||
Object.keys(this.waitNodeUidMap).forEach(uid => {
|
||||
const node = this.mindMap.renderer.findNodeByUid(uid)
|
||||
if (node) {
|
||||
node.addUser(this.waitNodeUidMap[uid])
|
||||
}
|
||||
})
|
||||
this.waitNodeUidMap = {}
|
||||
}
|
||||
|
||||
// 设置用户信息
|
||||
/**
|
||||
* {
|
||||
* id: '', // 必传,用户唯一的id
|
||||
* name: '', // 用户名称。name和avatar两个只传一个即可,如果都传了,会显示avatar
|
||||
* avatar: '', // 用户头像
|
||||
* color: '' // 如果没有传头像,那么会以一个圆形来显示名称的第一个字,文字的颜色为白色,圆的颜色可以通过该字段设置
|
||||
* }
|
||||
**/
|
||||
setUserInfo(userInfo) {
|
||||
if (
|
||||
getType(userInfo) !== 'Object' ||
|
||||
isUndef(userInfo.id) ||
|
||||
(isUndef(userInfo.name) && isUndef(userInfo.avatar))
|
||||
)
|
||||
return
|
||||
this.userInfo = userInfo || null
|
||||
}
|
||||
|
||||
// 监听感知数据同步事件
|
||||
onAwareness() {
|
||||
const walk = (list, callback) => {
|
||||
list.forEach(value => {
|
||||
const userName = Object.keys(value)[0]
|
||||
if (!userName) return
|
||||
const data = value[userName]
|
||||
const userInfo = data.userInfo
|
||||
const nodeIdList = data.nodeIdList
|
||||
nodeIdList.forEach(uid => {
|
||||
const node = this.mindMap.renderer.findNodeByUid(uid)
|
||||
callback(uid, node, userInfo)
|
||||
})
|
||||
})
|
||||
}
|
||||
// 清除之前的数据
|
||||
walk(this.currentAwarenessData, (uid, node, userInfo) => {
|
||||
if (node) {
|
||||
node.removeUser(userInfo)
|
||||
}
|
||||
})
|
||||
// 设置当前数据
|
||||
const data = Array.from(this.awareness.getStates().values())
|
||||
this.currentAwarenessData = data
|
||||
walk(data, (uid, node, userInfo) => {
|
||||
// 不显示自己
|
||||
if (userInfo.id === this.userInfo.id) return
|
||||
if (node) {
|
||||
node.addUser(userInfo)
|
||||
} else {
|
||||
this.waitNodeUidMap[uid] = userInfo
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 将树结构转平级对象
|
||||
/*
|
||||
{
|
||||
data: {
|
||||
uid: 'xxx'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
data: {
|
||||
uid: 'xxx'
|
||||
},
|
||||
children: []
|
||||
}
|
||||
]
|
||||
}
|
||||
转为:
|
||||
{
|
||||
uid: {
|
||||
children: [uid1, uid2],
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
*/
|
||||
transformTreeDataToObject(data) {
|
||||
const res = {}
|
||||
const walk = (root, parent) => {
|
||||
const uid = root.data.uid
|
||||
if (parent) {
|
||||
parent.children.push(uid)
|
||||
}
|
||||
res[uid] = {
|
||||
isRoot: !parent,
|
||||
data: {
|
||||
...root.data
|
||||
},
|
||||
children: []
|
||||
}
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach(item => {
|
||||
walk(item, res[uid])
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data, null)
|
||||
return res
|
||||
}
|
||||
|
||||
// 找到父节点的uid
|
||||
findParentUid(data, targetUid) {
|
||||
const uids = Object.keys(data)
|
||||
let res = ''
|
||||
uids.forEach(uid => {
|
||||
const children = data[uid].children
|
||||
const isParent =
|
||||
children.findIndex(childUid => {
|
||||
return childUid === targetUid
|
||||
}) !== -1
|
||||
if (isParent) {
|
||||
res = uid
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 将平级对象转树结构
|
||||
transformObjectToTreeData(data) {
|
||||
const uids = Object.keys(data)
|
||||
if (uids.length <= 0) return null
|
||||
const rootKey = uids.find(uid => {
|
||||
return data[uid].isRoot
|
||||
})
|
||||
if (!rootKey || !data[rootKey]) return null
|
||||
// 根节点
|
||||
const res = {
|
||||
data: simpleDeepClone(data[rootKey].data),
|
||||
children: []
|
||||
}
|
||||
const map = {}
|
||||
map[rootKey] = res
|
||||
uids.forEach(uid => {
|
||||
const parentUid = this.findParentUid(data, uid)
|
||||
const cur = data[uid]
|
||||
const node = map[uid] || {
|
||||
data: simpleDeepClone(cur.data),
|
||||
children: []
|
||||
}
|
||||
if (!map[uid]) {
|
||||
map[uid] = node
|
||||
}
|
||||
if (parentUid) {
|
||||
const index = data[parentUid].children.findIndex(item => {
|
||||
return item === uid
|
||||
})
|
||||
if (!map[parentUid]) {
|
||||
map[parentUid] = {
|
||||
data: simpleDeepClone(data[parentUid].data),
|
||||
children: []
|
||||
}
|
||||
}
|
||||
map[parentUid].children[index] = node
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
}
|
||||
|
||||
Cooperate.instanceName = 'cooperate'
|
||||
|
||||
export default Cooperate
|
||||
@@ -1,4 +1,9 @@
|
||||
import { bfsWalk, throttle, getTopAncestorsFomNodeList } from '../utils'
|
||||
import {
|
||||
bfsWalk,
|
||||
throttle,
|
||||
getTopAncestorsFomNodeList,
|
||||
getNodeIndexInNodeList
|
||||
} from '../utils'
|
||||
import Base from '../layouts/Base'
|
||||
|
||||
// 节点拖动插件
|
||||
@@ -108,14 +113,12 @@ class Drag extends Base {
|
||||
node.endDrag()
|
||||
})
|
||||
this.removeCloneNode()
|
||||
let overlapNodeUid = this.overlapNode
|
||||
? this.overlapNode.nodeData.data.uid
|
||||
: ''
|
||||
let prevNodeUid = this.prevNode ? this.prevNode.nodeData.data.uid : ''
|
||||
let nextNodeUid = this.nextNode ? this.nextNode.nodeData.data.uid : ''
|
||||
let overlapNodeUid = this.overlapNode ? this.overlapNode.getData('uid') : ''
|
||||
let prevNodeUid = this.prevNode ? this.prevNode.getData('uid') : ''
|
||||
let nextNodeUid = this.nextNode ? this.nextNode.getData('uid') : ''
|
||||
// 存在重叠子节点,则移动作为其子节点
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, false)
|
||||
this.mindMap.execCommand(
|
||||
'MOVE_NODE_TO',
|
||||
this.beingDragNodeList,
|
||||
@@ -123,7 +126,7 @@ class Drag extends Base {
|
||||
)
|
||||
} else if (this.prevNode) {
|
||||
// 存在前一个相邻节点,作为其下一个兄弟节点
|
||||
this.mindMap.renderer.setNodeActive(this.prevNode, false)
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.prevNode, false)
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_AFTER',
|
||||
this.beingDragNodeList,
|
||||
@@ -131,7 +134,7 @@ class Drag extends Base {
|
||||
)
|
||||
} else if (this.nextNode) {
|
||||
// 存在下一个相邻节点,作为其前一个兄弟节点
|
||||
this.mindMap.renderer.setNodeActive(this.nextNode, false)
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.nextNode, false)
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_BEFORE',
|
||||
this.beingDragNodeList,
|
||||
@@ -162,12 +165,14 @@ class Drag extends Base {
|
||||
)
|
||||
this.mindMap.render()
|
||||
}
|
||||
if (this.isDragging) {
|
||||
this.mindMap.emit('node_dragend', {
|
||||
overlapNodeUid,
|
||||
prevNodeUid,
|
||||
nextNodeUid
|
||||
})
|
||||
}
|
||||
this.reset()
|
||||
this.mindMap.emit('node_dragend', {
|
||||
overlapNodeUid,
|
||||
prevNodeUid,
|
||||
nextNodeUid
|
||||
})
|
||||
}
|
||||
|
||||
// 拖动中
|
||||
@@ -205,7 +210,7 @@ class Drag extends Base {
|
||||
this.offsetX = this.mouseDownX - (node.left * scaleX + translateX)
|
||||
this.offsetY = this.mouseDownY - (node.top * scaleY + translateY)
|
||||
// 如果鼠标按下的节点是激活节点,那么保存当前所有激活的节点
|
||||
if (node.nodeData.data.isActive) {
|
||||
if (node.getData('isActive')) {
|
||||
// 找出这些激活节点中的最顶层节点
|
||||
this.beingDragNodeList = getTopAncestorsFomNodeList(
|
||||
// 过滤掉根节点和概要节点
|
||||
@@ -222,7 +227,7 @@ class Drag extends Base {
|
||||
// 创建克隆节点
|
||||
this.createCloneNode()
|
||||
// 清除当前所有激活的节点
|
||||
this.mindMap.renderer.clearAllActive()
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +266,7 @@ class Drag extends Base {
|
||||
const lineColor = node.style.merge('lineColor', true)
|
||||
// 如果当前被拖拽的节点数量大于1,那么创建一个矩形示意
|
||||
if (this.beingDragNodeList.length > 1) {
|
||||
this.clone = this.draw
|
||||
this.clone = this.mindMap.otherDraw
|
||||
.rect()
|
||||
.size(rectWidth, rectHeight)
|
||||
.radius(rectHeight / 2)
|
||||
@@ -278,12 +283,12 @@ class Drag extends Base {
|
||||
if (expandEl) {
|
||||
expandEl.remove()
|
||||
}
|
||||
this.mindMap.draw.add(this.clone)
|
||||
this.mindMap.otherDraw.add(this.clone)
|
||||
}
|
||||
this.clone.opacity(dragOpacityConfig.cloneNodeOpacity)
|
||||
this.clone.css('z-index', 99999)
|
||||
// 同级位置提示元素
|
||||
this.placeholder = this.draw.rect().fill({
|
||||
this.placeholder = this.mindMap.otherDraw.rect().fill({
|
||||
color: dragPlaceholderRectFill || lineColor
|
||||
})
|
||||
// 当前被拖拽的节点的临时设置
|
||||
@@ -317,8 +322,8 @@ class Drag extends Base {
|
||||
this.nextNode = null
|
||||
this.placeholder.size(0, 0)
|
||||
this.nodeList.forEach(node => {
|
||||
if (node.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.setNodeActive(node, false)
|
||||
if (node.getData('isActive')) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
|
||||
}
|
||||
if (this.overlapNode || (this.prevNode && this.nextNode)) {
|
||||
return
|
||||
@@ -353,7 +358,7 @@ class Drag extends Base {
|
||||
}
|
||||
})
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,9 +492,7 @@ class Drag extends Base {
|
||||
getNodeDistanceToSiblingNode(checkList, node, nodeRect, dir) {
|
||||
let dir1 = dir === 'v' ? 'top' : 'left'
|
||||
let dir2 = dir === 'v' ? 'bottom' : 'right'
|
||||
let index = checkList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
let index = getNodeIndexInNodeList(node, checkList)
|
||||
let prevBrother = null
|
||||
let nextBrother = null
|
||||
if (index !== -1) {
|
||||
@@ -679,7 +682,7 @@ class Drag extends Base {
|
||||
// 检查某个节点是否在被拖拽节点内
|
||||
checkIsInBeingDragNodeList(node) {
|
||||
return !!this.beingDragNodeList.find(item => {
|
||||
return item.uid === node.uid || item.isParent(node)
|
||||
return item.uid === node.uid || item.isAncestor(node)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ import {
|
||||
imgToDataUrl,
|
||||
downloadFile,
|
||||
readBlob,
|
||||
removeHTMLEntities
|
||||
removeHTMLEntities,
|
||||
resizeImgSize,
|
||||
handleSelfCloseTags
|
||||
} from '../utils'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
|
||||
@@ -19,7 +21,7 @@ class Export {
|
||||
// 导出
|
||||
async export(type, isDownload = true, name = '思维导图', ...args) {
|
||||
if (this[type]) {
|
||||
let result = await this[type](name, ...args)
|
||||
const result = await this[type](name, ...args)
|
||||
if (isDownload && type !== 'pdf') {
|
||||
downloadFile(result, name + '.' + type)
|
||||
}
|
||||
@@ -29,6 +31,20 @@ class Export {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建图片url转换任务
|
||||
createTransformImgTaskList(svg, tagName, propName, getUrlFn) {
|
||||
const imageList = svg.find(tagName)
|
||||
return imageList.map(async item => {
|
||||
const imgUlr = getUrlFn(item)
|
||||
// 已经是data:URL形式不用转换
|
||||
if (/^data:/.test(imgUlr) || imgUlr === 'none') {
|
||||
return
|
||||
}
|
||||
const imgData = await imgToDataUrl(imgUlr)
|
||||
item.attr(propName, imgData)
|
||||
})
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
async getSvgData() {
|
||||
let { exportPaddingX, exportPaddingY } = this.mindMap.opt
|
||||
@@ -36,19 +52,34 @@ class Export {
|
||||
paddingX: exportPaddingX,
|
||||
paddingY: exportPaddingY
|
||||
})
|
||||
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
let imageList = svg.find('image')
|
||||
let task = imageList.map(async item => {
|
||||
let imgUlr = item.attr('href') || item.attr('xlink:href')
|
||||
// 已经是data:URL形式不用转换
|
||||
if (/^data:/.test(imgUlr) || imgUlr === 'none') {
|
||||
return
|
||||
// svg的image标签,把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
const task1 = this.createTransformImgTaskList(
|
||||
svg,
|
||||
'image',
|
||||
'href',
|
||||
item => {
|
||||
return item.attr('href') || item.attr('xlink:href')
|
||||
}
|
||||
let imgData = await imgToDataUrl(imgUlr)
|
||||
item.attr('href', imgData)
|
||||
)
|
||||
// html的img标签
|
||||
const task2 = this.createTransformImgTaskList(svg, 'img', 'src', item => {
|
||||
return item.attr('src')
|
||||
})
|
||||
await Promise.all(task)
|
||||
if (imageList.length > 0) {
|
||||
const taskList = [...task1, ...task2]
|
||||
await Promise.all(taskList)
|
||||
// 开启了节点富文本编辑,需要增加一些样式
|
||||
let isAddResetCss
|
||||
if (this.mindMap.richText) {
|
||||
const foreignObjectList = svg.find('foreignObject')
|
||||
if (foreignObjectList.length > 0) {
|
||||
foreignObjectList[0].add(
|
||||
SVG(`<style>${this.mindMap.opt.resetCss}</style>`)
|
||||
)
|
||||
isAddResetCss = true
|
||||
}
|
||||
}
|
||||
// svg节点内容有变,需要重新获取html字符串
|
||||
if (taskList.length > 0 || isAddResetCss) {
|
||||
svgHTML = svg.svg()
|
||||
}
|
||||
return {
|
||||
@@ -63,7 +94,8 @@ class Export {
|
||||
transparent,
|
||||
checkRotate = () => {
|
||||
return false
|
||||
}
|
||||
},
|
||||
compress
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
@@ -76,8 +108,19 @@ class Export {
|
||||
window.devicePixelRatio,
|
||||
this.mindMap.opt.minExportImgCanvasScale
|
||||
)
|
||||
const imgWidth = img.width
|
||||
const imgHeight = img.height
|
||||
let imgWidth = img.width
|
||||
let imgHeight = img.height
|
||||
// 压缩图片
|
||||
if (compress) {
|
||||
const compressedSize = resizeImgSize(
|
||||
imgWidth,
|
||||
imgHeight,
|
||||
compress.width,
|
||||
compress.height
|
||||
)
|
||||
imgWidth = compressedSize[0]
|
||||
imgHeight = compressedSize[1]
|
||||
}
|
||||
// 如果宽比高长,那么旋转90度
|
||||
const needRotate = checkRotate(imgWidth, imgHeight)
|
||||
if (needRotate) {
|
||||
@@ -118,7 +161,7 @@ class Export {
|
||||
// 在canvas上绘制思维导图背景
|
||||
drawBackgroundToCanvas(ctx, width, height) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let {
|
||||
const {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'no-repeat',
|
||||
@@ -162,7 +205,7 @@ class Export {
|
||||
// 在svg上绘制思维导图背景
|
||||
drawBackgroundToSvg(svg) {
|
||||
return new Promise(async resolve => {
|
||||
let {
|
||||
const {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
@@ -171,7 +214,7 @@ class Export {
|
||||
svg.css('background-color', backgroundColor)
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
let imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
const imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
svg.css('background-image', `url(${imgDataUrl})`)
|
||||
svg.css('background-repeat', backgroundRepeat)
|
||||
resolve()
|
||||
@@ -186,49 +229,31 @@ class Export {
|
||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
||||
*/
|
||||
async png(name, transparent = false, checkRotate) {
|
||||
let { node, str } = await this.getSvgData()
|
||||
str = removeHTMLEntities(str)
|
||||
// 如果开启了富文本,则使用htmltocanvas转换为图片
|
||||
if (this.mindMap.richText) {
|
||||
// 覆盖html默认的样式
|
||||
let foreignObjectList = node.find('foreignObject')
|
||||
if (foreignObjectList.length > 0) {
|
||||
foreignObjectList[0].add(
|
||||
SVG(`<style>${this.mindMap.opt.resetCss}</style>`)
|
||||
)
|
||||
}
|
||||
str = node.svg()
|
||||
// 使用其他库(html2canvas、dom-to-image-more等)来完成导出
|
||||
// let res = await this.mindMap.richText.handleExportPng(node.node)
|
||||
// let imgDataUrl = await this.svgToPng(
|
||||
// res,
|
||||
// transparent,
|
||||
// checkRotate
|
||||
// )
|
||||
// return imgDataUrl
|
||||
}
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
// 转换成data:url数据
|
||||
let svgUrl = await readBlob(blob)
|
||||
async png(name, transparent = false, checkRotate, compress) {
|
||||
const { str } = await this.getSvgData()
|
||||
const svgUrl = await this.fixSvgStrAndToBlob(str)
|
||||
// 绘制到canvas上
|
||||
let res = await this.svgToPng(svgUrl, transparent, checkRotate)
|
||||
const res = await this.svgToPng(svgUrl, transparent, checkRotate, compress)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为pdf
|
||||
async pdf(name, useMultiPageExport) {
|
||||
async pdf(name, useMultiPageExport, maxImageWidth) {
|
||||
if (!this.mindMap.doExportPDF) {
|
||||
throw new Error('请注册ExportPDF插件')
|
||||
}
|
||||
let img = await this.png('', false, (width, height) => {
|
||||
if (width <= a4Size.width && height && a4Size.height) return false
|
||||
return width / height > 1
|
||||
})
|
||||
this.mindMap.doExportPDF.pdf(name, img, useMultiPageExport)
|
||||
const img = await this.png(
|
||||
'',
|
||||
false,
|
||||
(width, height) => {
|
||||
if (width <= a4Size.width && height && a4Size.height) return false
|
||||
return width / height > 1
|
||||
},
|
||||
{
|
||||
width: maxImageWidth || a4Size.width * 2
|
||||
}
|
||||
)
|
||||
await this.mindMap.doExportPDF.pdf(name, img, useMultiPageExport)
|
||||
}
|
||||
|
||||
// 导出为xmind
|
||||
@@ -243,51 +268,50 @@ class Export {
|
||||
}
|
||||
|
||||
// 导出为svg
|
||||
// plusCssText:附加的css样式,如果svg中存在dom节点,想要设置一些针对节点的样式可以通过这个参数传入
|
||||
async svg(name) {
|
||||
let { node } = await this.getSvgData()
|
||||
// 开启了节点富文本编辑
|
||||
if (this.mindMap.richText) {
|
||||
let foreignObjectList = node.find('foreignObject')
|
||||
if (foreignObjectList.length > 0) {
|
||||
foreignObjectList[0].add(
|
||||
SVG(`<style>${this.mindMap.opt.resetCss}</style>`)
|
||||
)
|
||||
}
|
||||
}
|
||||
const { node } = await this.getSvgData()
|
||||
node.first().before(SVG(`<title>${name}</title>`))
|
||||
await this.drawBackgroundToSvg(node)
|
||||
let str = node.svg()
|
||||
const str = node.svg()
|
||||
const res = await this.fixSvgStrAndToBlob(str)
|
||||
return res
|
||||
}
|
||||
|
||||
// 修复svg字符串,并且转换为blob数据
|
||||
async fixSvgStrAndToBlob(str) {
|
||||
// 移除字符串中的html实体
|
||||
str = removeHTMLEntities(str)
|
||||
// 给html自闭合标签添加闭合状态
|
||||
str = handleSelfCloseTags(str)
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
const blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
let res = await readBlob(blob)
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为json
|
||||
async json(name, withConfig = true) {
|
||||
let data = this.mindMap.getData(withConfig)
|
||||
let str = JSON.stringify(data)
|
||||
let blob = new Blob([str])
|
||||
let res = await readBlob(blob)
|
||||
const data = this.mindMap.getData(withConfig)
|
||||
const str = JSON.stringify(data)
|
||||
const blob = new Blob([str])
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// 专有文件,其实就是json文件
|
||||
async smm(name, withConfig) {
|
||||
let res = await this.json(name, withConfig)
|
||||
const res = await this.json(name, withConfig)
|
||||
return res
|
||||
}
|
||||
|
||||
// markdown文件
|
||||
async md() {
|
||||
let data = this.mindMap.getData()
|
||||
let content = transformToMarkdown(data)
|
||||
let blob = new Blob([content])
|
||||
let res = await readBlob(blob)
|
||||
const data = this.mindMap.getData()
|
||||
const content = transformToMarkdown(data)
|
||||
const blob = new Blob([content])
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import JsPDF from 'jspdf'
|
||||
import JsPDF from '../utils/jspdf'
|
||||
import { a4Size } from '../constants/constant'
|
||||
|
||||
// 导出PDF插件,需要通过Export插件使用
|
||||
@@ -9,92 +9,104 @@ class ExportPDF {
|
||||
}
|
||||
|
||||
// 导出为pdf
|
||||
pdf(name, img, useMultiPageExport = false) {
|
||||
async pdf(name, img, useMultiPageExport = false) {
|
||||
if (useMultiPageExport) {
|
||||
this.multiPageExport(name, img)
|
||||
await this.multiPageExport(name, img)
|
||||
} else {
|
||||
this.onePageExport(name, img)
|
||||
await this.onePageExport(name, img)
|
||||
}
|
||||
}
|
||||
|
||||
// 单页导出
|
||||
onePageExport(name, img) {
|
||||
let pdf = new JsPDF('', 'pt', 'a4')
|
||||
let a4Ratio = a4Size.width / a4Size.height
|
||||
let image = new Image()
|
||||
image.onload = () => {
|
||||
let imageWidth = image.width
|
||||
let imageHeight = image.height
|
||||
let imageRatio = imageWidth / imageHeight
|
||||
let w, h
|
||||
if (imageWidth <= a4Size.width && imageHeight <= a4Size.height) {
|
||||
// 使用图片原始宽高
|
||||
w = imageWidth
|
||||
h = imageHeight
|
||||
} else if (a4Ratio > imageRatio) {
|
||||
// 以a4Height为高度,缩放图片宽度
|
||||
w = imageRatio * a4Size.height
|
||||
h = a4Size.height
|
||||
} else {
|
||||
// 以a4Width为宽度,缩放图片高度
|
||||
w = a4Size.width
|
||||
h = a4Size.width / imageRatio
|
||||
return new Promise((resolve, reject) => {
|
||||
let pdf = new JsPDF('', 'pt', 'a4')
|
||||
let a4Ratio = a4Size.width / a4Size.height
|
||||
let image = new Image()
|
||||
image.onload = () => {
|
||||
let imageWidth = image.width
|
||||
let imageHeight = image.height
|
||||
let imageRatio = imageWidth / imageHeight
|
||||
let w, h
|
||||
if (imageWidth <= a4Size.width && imageHeight <= a4Size.height) {
|
||||
// 使用图片原始宽高
|
||||
w = imageWidth
|
||||
h = imageHeight
|
||||
} else if (a4Ratio > imageRatio) {
|
||||
// 以a4Height为高度,缩放图片宽度
|
||||
w = imageRatio * a4Size.height
|
||||
h = a4Size.height
|
||||
} else {
|
||||
// 以a4Width为宽度,缩放图片高度
|
||||
w = a4Size.width
|
||||
h = a4Size.width / imageRatio
|
||||
}
|
||||
pdf.addImage(
|
||||
img,
|
||||
'PNG',
|
||||
(a4Size.width - w) / 2,
|
||||
(a4Size.height - h) / 2,
|
||||
w,
|
||||
h
|
||||
)
|
||||
pdf.save(name)
|
||||
resolve()
|
||||
}
|
||||
pdf.addImage(
|
||||
img,
|
||||
'PNG',
|
||||
(a4Size.width - w) / 2,
|
||||
(a4Size.height - h) / 2,
|
||||
w,
|
||||
h
|
||||
)
|
||||
pdf.save(name)
|
||||
}
|
||||
image.src = img
|
||||
image.onerror = e => {
|
||||
reject(e)
|
||||
}
|
||||
image.src = img
|
||||
})
|
||||
}
|
||||
|
||||
// 多页导出
|
||||
multiPageExport(name, img) {
|
||||
let image = new Image()
|
||||
image.onload = () => {
|
||||
let imageWidth = image.width
|
||||
let imageHeight = image.height
|
||||
// 一页pdf显示高度
|
||||
let pageHeight = (imageWidth / a4Size.width) * a4Size.height
|
||||
// 未生成pdf的高度
|
||||
let leftHeight = imageHeight
|
||||
// 偏移
|
||||
let position = 0
|
||||
// a4纸的尺寸[595.28,841.89],图片在pdf中图片的宽高
|
||||
let imgWidth = a4Size.width
|
||||
let imgHeight = (a4Size.width / imageWidth) * imageHeight
|
||||
let pdf = new JsPDF('', 'pt', 'a4')
|
||||
// 有两个高度需要区分,一个是图片的实际高度,和生成pdf的页面高度(841.89)
|
||||
// 当内容未超过pdf一页显示的范围,无需分页
|
||||
if (leftHeight < pageHeight) {
|
||||
pdf.addImage(
|
||||
img,
|
||||
'PNG',
|
||||
(a4Size.width - imgWidth) / 2,
|
||||
(a4Size.height - imgHeight) / 2,
|
||||
imgWidth,
|
||||
imgHeight
|
||||
)
|
||||
} else {
|
||||
// 分页
|
||||
while (leftHeight > 0) {
|
||||
pdf.addImage(img, 'PNG', 0, position, imgWidth, imgHeight)
|
||||
leftHeight -= pageHeight
|
||||
position -= a4Size.height
|
||||
// 避免添加空白页
|
||||
if (leftHeight > 0) {
|
||||
pdf.addPage()
|
||||
return new Promise((resolve, reject) => {
|
||||
let image = new Image()
|
||||
image.onload = () => {
|
||||
let imageWidth = image.width
|
||||
let imageHeight = image.height
|
||||
// 一页pdf显示高度
|
||||
let pageHeight = (imageWidth / a4Size.width) * a4Size.height
|
||||
// 未生成pdf的高度
|
||||
let leftHeight = imageHeight
|
||||
// 偏移
|
||||
let position = 0
|
||||
// a4纸的尺寸[595.28,841.89],图片在pdf中图片的宽高
|
||||
let imgWidth = a4Size.width
|
||||
let imgHeight = (a4Size.width / imageWidth) * imageHeight
|
||||
let pdf = new JsPDF('', 'pt', 'a4')
|
||||
// 有两个高度需要区分,一个是图片的实际高度,和生成pdf的页面高度(841.89)
|
||||
// 当内容未超过pdf一页显示的范围,无需分页
|
||||
if (leftHeight < pageHeight) {
|
||||
pdf.addImage(
|
||||
img,
|
||||
'PNG',
|
||||
(a4Size.width - imgWidth) / 2,
|
||||
(a4Size.height - imgHeight) / 2,
|
||||
imgWidth,
|
||||
imgHeight
|
||||
)
|
||||
} else {
|
||||
// 分页
|
||||
while (leftHeight > 0) {
|
||||
pdf.addImage(img, 'PNG', 0, position, imgWidth, imgHeight)
|
||||
leftHeight -= pageHeight
|
||||
position -= a4Size.height
|
||||
// 避免添加空白页
|
||||
if (leftHeight > 0) {
|
||||
pdf.addPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
pdf.save(name)
|
||||
resolve()
|
||||
}
|
||||
pdf.save(name)
|
||||
}
|
||||
image.src = img
|
||||
image.onerror = (e) => {
|
||||
reject(e)
|
||||
}
|
||||
image.src = img
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {
|
||||
isWhite,
|
||||
isTransparent,
|
||||
getVisibleColorFromTheme
|
||||
getVisibleColorFromTheme,
|
||||
readBlob
|
||||
} from '../utils/index'
|
||||
|
||||
// 小地图插件
|
||||
@@ -27,7 +28,9 @@ class MiniMap {
|
||||
*/
|
||||
calculationMiniMap(boxWidth, boxHeight) {
|
||||
let { svg, rect, origWidth, origHeight, scaleX, scaleY } =
|
||||
this.mindMap.getSvgData()
|
||||
this.mindMap.getSvgData({
|
||||
ignoreWatermark: true
|
||||
})
|
||||
// 计算数据
|
||||
const elRect = this.mindMap.elRect
|
||||
rect.x -= elRect.left
|
||||
@@ -85,10 +88,18 @@ class MiniMap {
|
||||
Object.keys(viewBoxStyle).forEach(key => {
|
||||
viewBoxStyle[key] = viewBoxStyle[key] + 'px'
|
||||
})
|
||||
|
||||
this.removeNodeContent(svg)
|
||||
const svgStr = svg.svg()
|
||||
|
||||
return {
|
||||
svgHTML: svg.svg(), // 小地图html
|
||||
getImgUrl: async callback => {
|
||||
const blob = new Blob([svgStr], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
const res = await readBlob(blob)
|
||||
callback(res)
|
||||
},
|
||||
svgHTML: svgStr, // 小地图html
|
||||
viewBoxStyle, // 视图框的位置信息
|
||||
miniMapBoxScale, // 视图框的缩放值
|
||||
miniMapBoxLeft, // 视图框的left值
|
||||
|
||||
@@ -205,7 +205,7 @@ class NodeImgAdjust {
|
||||
// 隐藏节点实际图片
|
||||
this.hideNodeImage()
|
||||
// 将节点图片渲染到自定义元素上
|
||||
this.handleEl.style.backgroundImage = `url(${this.node.nodeData.data.image})`
|
||||
this.handleEl.style.backgroundImage = `url(${this.node.getData('image')})`
|
||||
}
|
||||
|
||||
// 鼠标移动
|
||||
@@ -214,7 +214,7 @@ class NodeImgAdjust {
|
||||
e.preventDefault()
|
||||
// 计算当前拖拽位置对应的图片的实时大小
|
||||
let { width: imageOriginWidth, height: imageOriginHeight } =
|
||||
this.node.nodeData.data.imageSize
|
||||
this.node.getData('imageSize')
|
||||
let newWidth = e.clientX - this.rect.x
|
||||
let newHeight = e.clientY - this.rect.y
|
||||
if (newWidth <= 0 || newHeight <= 0) return
|
||||
@@ -237,7 +237,7 @@ class NodeImgAdjust {
|
||||
// 隐藏自定义元素
|
||||
this.hideHandleEl()
|
||||
// 更新节点图片为新的大小
|
||||
let { image, imageTitle } = this.node.nodeData.data
|
||||
let { image, imageTitle } = this.node.getData()
|
||||
let { scaleX, scaleY } = this.mindMap.draw.transform()
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, {
|
||||
url: image,
|
||||
|
||||
@@ -38,6 +38,7 @@ class Painter {
|
||||
}
|
||||
|
||||
onEndPainter() {
|
||||
if (!this.isInPainter) return
|
||||
this.endPainter()
|
||||
this.mindMap.emit('painter_end')
|
||||
}
|
||||
@@ -53,7 +54,7 @@ class Painter {
|
||||
)
|
||||
return
|
||||
const style = {}
|
||||
const painterNodeData = this.painterNode.nodeData.data
|
||||
const painterNodeData = this.painterNode.getData()
|
||||
Object.keys(painterNodeData).forEach(key => {
|
||||
if (checkIsNodeStyleDataKey(key)) {
|
||||
style[key] = painterNodeData[key]
|
||||
|
||||
@@ -153,7 +153,7 @@ class RichText {
|
||||
}
|
||||
|
||||
// 显示文本编辑控件
|
||||
showEditText(node, rect, isInserting, isFromKeyDown) {
|
||||
showEditText({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
|
||||
if (this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
@@ -167,7 +167,9 @@ class RichText {
|
||||
this.node = node
|
||||
this.isInserting = isInserting
|
||||
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
if (!isFromScale) {
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
}
|
||||
this.mindMap.renderer.textEdit.registerTmpShortcut()
|
||||
// 原始宽高
|
||||
let g = node._textData.node
|
||||
@@ -236,17 +238,17 @@ class RichText {
|
||||
this.textEditNode.style.borderRadius = (node.height || 50) + 'px'
|
||||
}
|
||||
}
|
||||
if (!node.nodeData.data.richText) {
|
||||
if (!node.getData('richText')) {
|
||||
// 还不是富文本的情况
|
||||
let text = ''
|
||||
if (!isUndef(node.nodeData.data.text)) {
|
||||
text = String(node.nodeData.data.text).split(/\n/gim).join('<br>')
|
||||
if (!isUndef(node.getData('text'))) {
|
||||
text = String(node.getData('text')).split(/\n/gim).join('<br>')
|
||||
}
|
||||
let html = `<p>${text}</p>`
|
||||
this.textEditNode.innerHTML = this.cacheEditingText || html
|
||||
} else {
|
||||
this.textEditNode.innerHTML =
|
||||
this.cacheEditingText || node.nodeData.data.text
|
||||
this.cacheEditingText || node.getData('text')
|
||||
}
|
||||
this.initQuillEditor()
|
||||
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
|
||||
@@ -256,7 +258,7 @@ class RichText {
|
||||
this.focus(
|
||||
isInserting || (selectTextOnEnterEditText && !isFromKeyDown) ? 0 : null
|
||||
)
|
||||
if (!node.nodeData.data.richText) {
|
||||
if (!node.getData('richText')) {
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
this.setTextStyleIfNotRichText(node)
|
||||
}
|
||||
@@ -422,10 +424,13 @@ class RichText {
|
||||
|
||||
// 中文输入结束
|
||||
onCompositionEnd() {
|
||||
if (!this.showTextEdit || !this.lostStyle) {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.isCompositing = false
|
||||
if (!this.lostStyle) {
|
||||
return
|
||||
}
|
||||
this.setTextStyleIfNotRichText(this.node)
|
||||
}
|
||||
|
||||
@@ -494,7 +499,7 @@ class RichText {
|
||||
})
|
||||
} else {
|
||||
let data = this.richTextStyleToNormalStyle(config)
|
||||
this.mindMap.renderer.setNodeData(this.node, data)
|
||||
this.mindMap.execCommand('SET_NODE_DATA', this.node, data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,6 +569,16 @@ 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])
|
||||
}
|
||||
}
|
||||
|
||||
// 处理导出为图片
|
||||
async handleExportPng(node) {
|
||||
let el = document.createElement('div')
|
||||
@@ -638,11 +653,13 @@ class RichText {
|
||||
beforePluginRemove() {
|
||||
this.transformAllNodesToNormalNode()
|
||||
document.head.removeChild(this.styleEl)
|
||||
this.unbindEvent()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
document.head.removeChild(this.styleEl)
|
||||
this.unbindEvent()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ class Search {
|
||||
this.currentIndex = -1
|
||||
this.notResetSearchText = false
|
||||
this.isSearching = false
|
||||
if (this.mindMap.opt.readonly) {
|
||||
this.mindMap.renderer.closeHighlightNode()
|
||||
}
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
@@ -73,7 +76,7 @@ class Search {
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
let { richText, text } = node.nodeData.data
|
||||
let { richText, text } = node.getData()
|
||||
if (richText) {
|
||||
text = getTextFromHtml(text)
|
||||
}
|
||||
@@ -96,6 +99,10 @@ class Search {
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
|
||||
this.notResetSearchText = false
|
||||
callback()
|
||||
// 只读模式下节点无法激活,所以通过高亮的方式
|
||||
if (this.mindMap.opt.readonly) {
|
||||
this.mindMap.renderer.highlightNode(currentNode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -115,7 +122,7 @@ class Search {
|
||||
if (!currentNode) return
|
||||
let text = this.getReplacedText(currentNode, this.searchText, replaceText)
|
||||
this.notResetSearchText = true
|
||||
currentNode.setText(text, currentNode.nodeData.data.richText, true)
|
||||
currentNode.setText(text, currentNode.getData('richText'), true)
|
||||
this.matchNodeList = this.matchNodeList.filter(node => {
|
||||
return currentNode !== node
|
||||
})
|
||||
@@ -143,7 +150,7 @@ class Search {
|
||||
node,
|
||||
{
|
||||
text,
|
||||
resetRichText: !!node.nodeData.data.richText
|
||||
resetRichText: !!node.getData('richText')
|
||||
},
|
||||
true
|
||||
)
|
||||
@@ -155,7 +162,7 @@ class Search {
|
||||
|
||||
// 获取某个节点替换后的文本
|
||||
getReplacedText(node, searchText, replaceText) {
|
||||
let { richText, text } = node.nodeData.data
|
||||
let { richText, text } = node.getData()
|
||||
if (richText) {
|
||||
return replaceHtmlText(text, searchText, replaceText)
|
||||
} else {
|
||||
|
||||
@@ -89,23 +89,28 @@ class Select {
|
||||
}
|
||||
)
|
||||
})
|
||||
this.mindMap.on('mouseup', () => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.checkTriggerNodeActiveEvent()
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
this.isMousedown = false
|
||||
this.cacheActiveList = []
|
||||
if (this.rect) this.rect.remove()
|
||||
this.rect = null
|
||||
setTimeout(() => {
|
||||
this.isSelecting = false
|
||||
}, 0)
|
||||
})
|
||||
this.onMouseup = this.onMouseup.bind(this)
|
||||
this.mindMap.on('mouseup', this.onMouseup)
|
||||
this.mindMap.on('node_mouseup', this.onMouseup)
|
||||
}
|
||||
|
||||
// 结束框选
|
||||
onMouseup() {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.checkTriggerNodeActiveEvent()
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
this.isMousedown = false
|
||||
this.cacheActiveList = []
|
||||
if (this.rect) this.rect.remove()
|
||||
this.rect = null
|
||||
setTimeout(() => {
|
||||
this.isSelecting = false
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 如果激活节点改变了,那么触发事件
|
||||
@@ -119,7 +124,7 @@ class Select {
|
||||
let cur = this.cacheActiveList[i]
|
||||
if (
|
||||
!this.mindMap.renderer.activeNodeList.find(item => {
|
||||
return item.nodeData.data.uid === cur.nodeData.data.uid
|
||||
return item.getData('uid') === cur.getData('uid')
|
||||
})
|
||||
) {
|
||||
isNodeChange = true
|
||||
@@ -128,9 +133,7 @@ class Select {
|
||||
}
|
||||
}
|
||||
if (isNumChange || isNodeChange) {
|
||||
this.mindMap.emit('node_active', null, [
|
||||
...this.mindMap.renderer.activeNodeList
|
||||
])
|
||||
this.mindMap.renderer.emitNodeActiveEvent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +187,7 @@ class Select {
|
||||
|
||||
// 创建矩形
|
||||
createRect(x, y) {
|
||||
if (this.rect) this.rect.remove()
|
||||
this.rect = this.mindMap.svg
|
||||
.polygon()
|
||||
.stroke({
|
||||
@@ -212,17 +216,15 @@ class Select {
|
||||
if (
|
||||
checkTwoRectIsOverlap(minx, maxx, miny, maxy, left, right, top, bottom)
|
||||
) {
|
||||
if (node.nodeData.data.isActive) {
|
||||
if (node.getData('isActive')) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.setNodeActive(node, true)
|
||||
this.mindMap.renderer.addActiveNode(node)
|
||||
} else if (node.nodeData.data.isActive) {
|
||||
if (!node.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.addNodeToActiveList(node)
|
||||
} else if (node.getData('isActive')) {
|
||||
if (!node.getData('isActive')) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.setNodeActive(node, false)
|
||||
this.mindMap.renderer.removeActiveNode(node)
|
||||
this.mindMap.renderer.removeNodeFromActiveList(node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class TouchEvent {
|
||||
let touch = e.touches[0]
|
||||
this.dispatchMouseEvent('mousemove', touch.target, touch)
|
||||
} else if (len === 2) {
|
||||
if (this.mindMap.opt.disableTouchZoom) return
|
||||
let touch1 = e.touches[0]
|
||||
let touch2 = e.touches[1]
|
||||
let ox = touch1.clientX - touch2.clientX
|
||||
|
||||
@@ -11,13 +11,48 @@ class Watermark {
|
||||
this.angle = 0 // 旋转角度
|
||||
this.text = '' // 水印文字
|
||||
this.textStyle = {} // 水印文字样式
|
||||
this.watermarkDraw = null // 容器
|
||||
this.maxLong = this.getMaxLong()
|
||||
this.updateWatermark(this.mindMap.opt.watermarkConfig || {})
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
getMaxLong() {
|
||||
return Math.sqrt(
|
||||
Math.pow(this.mindMap.width, 2) + Math.pow(this.mindMap.height, 2)
|
||||
)
|
||||
}
|
||||
|
||||
bindEvent() {
|
||||
this.onResize = this.onResize.bind(this)
|
||||
this.mindMap.on('resize', this.onResize)
|
||||
}
|
||||
|
||||
unBindEvent() {
|
||||
this.mindMap.off('resize', this.onResize)
|
||||
}
|
||||
|
||||
onResize() {
|
||||
this.maxLong = this.getMaxLong()
|
||||
this.draw()
|
||||
}
|
||||
|
||||
// 创建水印容器
|
||||
createContainer() {
|
||||
if (this.watermarkDraw) return
|
||||
this.watermarkDraw = this.mindMap.svg
|
||||
.group()
|
||||
.css({ 'pointer-events': 'none', 'user-select': 'none' })
|
||||
this.maxLong = Math.sqrt(
|
||||
Math.pow(this.mindMap.width, 2) + Math.pow(this.mindMap.height, 2)
|
||||
)
|
||||
this.updateWatermark(this.mindMap.opt.watermarkConfig || {})
|
||||
.addClass('smm-water-mark-container')
|
||||
}
|
||||
|
||||
// 删除水印容器
|
||||
removeContainer() {
|
||||
if (!this.watermarkDraw) {
|
||||
return
|
||||
}
|
||||
this.watermarkDraw.remove()
|
||||
this.watermarkDraw = null
|
||||
}
|
||||
|
||||
// 获取是否存在水印
|
||||
@@ -40,10 +75,14 @@ class Watermark {
|
||||
// 绘制水印
|
||||
// 非精确绘制,会绘制一些超出可视区域的水印
|
||||
draw() {
|
||||
this.watermarkDraw.clear()
|
||||
// 清空之前的水印
|
||||
if (this.watermarkDraw) this.watermarkDraw.clear()
|
||||
// 如果没有水印数据,那么水印容器也删除掉
|
||||
if (!this.hasWatermark()) {
|
||||
this.removeContainer()
|
||||
return
|
||||
}
|
||||
this.createContainer()
|
||||
let x = 0
|
||||
while (x < this.mindMap.width) {
|
||||
this.drawText(x)
|
||||
@@ -116,6 +155,16 @@ class Watermark {
|
||||
this.handleConfig(config)
|
||||
this.draw()
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
}
|
||||
|
||||
Watermark.instanceName = 'watermark'
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
function createControlNodes() {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
// 连线
|
||||
this.controlLine1 = this.draw
|
||||
this.controlLine1 = this.associativeLineDraw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
this.controlLine2 = this.draw
|
||||
this.controlLine2 = this.associativeLineDraw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
// 控制点
|
||||
@@ -23,7 +23,7 @@ function createControlNodes() {
|
||||
// 创建控制点
|
||||
function createOneControlNode(pointKey) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
return this.draw
|
||||
return this.associativeLineDraw
|
||||
.circle(this.controlPointDiameter)
|
||||
.stroke({ color: associativeLineActiveColor })
|
||||
.fill({ color: '#fff' })
|
||||
@@ -64,7 +64,7 @@ function onControlPointMousemove(e) {
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
let { associativeLinePoint, associativeLineTargetControlOffsets } =
|
||||
node.nodeData.data
|
||||
node.getData()
|
||||
associativeLinePoint = associativeLinePoint || []
|
||||
const nodePos = this.getNodePos(node)
|
||||
const toNodePos = this.getNodePos(toNode)
|
||||
@@ -160,7 +160,7 @@ function onControlPointMouseup(e) {
|
||||
let [, , , node] = this.activeLine
|
||||
let offsetList = []
|
||||
let { associativeLinePoint, associativeLineTargetControlOffsets } =
|
||||
node.nodeData.data
|
||||
node.getData()
|
||||
if (!associativeLinePoint) {
|
||||
associativeLinePoint = []
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
|
||||
// 创建文字节点
|
||||
function createText(data) {
|
||||
let g = this.draw.group()
|
||||
let g = this.associativeLineDraw.group()
|
||||
const setActive = () => {
|
||||
if (
|
||||
!this.activeLine ||
|
||||
@@ -110,8 +110,8 @@ function hideEditTextBox() {
|
||||
str = isDefaultText ? '' : str
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineText: {
|
||||
...(node.nodeData.data.associativeLineText || {}),
|
||||
[toNode.nodeData.data.uid]: str
|
||||
...(node.getData('associativeLineText') || {}),
|
||||
[toNode.getData('uid')]: str
|
||||
}
|
||||
})
|
||||
this.textEditNode.style.display = 'none'
|
||||
@@ -123,11 +123,11 @@ function hideEditTextBox() {
|
||||
|
||||
// 获取某根关联线的文字
|
||||
function getText(node, toNode) {
|
||||
let obj = node.nodeData.data.associativeLineText
|
||||
let obj = node.getData('associativeLineText')
|
||||
if (!obj) {
|
||||
return ''
|
||||
}
|
||||
return obj[toNode.nodeData.data.uid] || ''
|
||||
return obj[toNode.getData('uid')] || ''
|
||||
}
|
||||
|
||||
// 渲染关联线文字
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 获取目标节点在起始节点的目标数组中的索引
|
||||
export const getAssociativeLineTargetIndex = (node, toNode) => {
|
||||
return node.nodeData.data.associativeLineTargets.findIndex(item => {
|
||||
return item === toNode.nodeData.data.uid
|
||||
return node.getData('associativeLineTargets').findIndex(item => {
|
||||
return item === toNode.getData('uid')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ export const computeNodePoints = (fromNode, toNode) => {
|
||||
// 中心点坐标的差值
|
||||
let offsetX = toCx - fromCx
|
||||
let offsetY = toCy - fromCy
|
||||
if (offsetX === 0 && offsetY === 0) return
|
||||
if (offsetX === 0 && offsetY === 0) return []
|
||||
let fromDir = ''
|
||||
let toDir = ''
|
||||
if (offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY) {
|
||||
@@ -231,7 +231,7 @@ export const getNodeLinePath = (startPoint, endPoint, node, toNode) => {
|
||||
// 控制点
|
||||
let controlPoints = []
|
||||
let associativeLineTargetControlOffsets =
|
||||
node.nodeData.data.associativeLineTargetControlOffsets
|
||||
node.getData('associativeLineTargetControlOffsets')
|
||||
if (
|
||||
associativeLineTargetControlOffsets &&
|
||||
associativeLineTargetControlOffsets[targetIndex]
|
||||
|
||||
@@ -12,6 +12,7 @@ class BatchExecution {
|
||||
// 添加任务
|
||||
push(name, fn) {
|
||||
if (this.has[name]) {
|
||||
this.replaceTask(name, fn)
|
||||
return
|
||||
}
|
||||
this.has[name] = true
|
||||
@@ -22,6 +23,19 @@ class BatchExecution {
|
||||
this.nextTick()
|
||||
}
|
||||
|
||||
// 替换任务
|
||||
replaceTask(name, fn) {
|
||||
const index = this.queue.findIndex(item => {
|
||||
return item.name === name
|
||||
})
|
||||
if (index !== -1) {
|
||||
this.queue[index] = {
|
||||
name,
|
||||
fn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行队列
|
||||
flush() {
|
||||
let fns = this.queue.slice(0)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { nodeDataNoStylePropList } from '../constants/constant'
|
||||
import {
|
||||
nodeDataNoStylePropList,
|
||||
selfCloseTagList
|
||||
} from '../constants/constant'
|
||||
import MersenneTwister from './mersenneTwister'
|
||||
// 深度优先遍历树
|
||||
export const walk = (
|
||||
@@ -152,6 +155,9 @@ export const copyRenderTree = (tree, root, removeActiveState = false) => {
|
||||
tree.data = simpleDeepClone(root.data)
|
||||
if (removeActiveState) {
|
||||
tree.data.isActive = false
|
||||
if (tree.data.generalization) {
|
||||
tree.data.generalization.isActive = false
|
||||
}
|
||||
}
|
||||
tree.children = []
|
||||
if (root.children && root.children.length > 0) {
|
||||
@@ -167,11 +173,14 @@ export const copyNodeTree = (
|
||||
tree,
|
||||
root,
|
||||
removeActiveState = false,
|
||||
keepId = false
|
||||
removeId = true
|
||||
) => {
|
||||
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
|
||||
// 重新创建节点uid,因为节点uid不能重复
|
||||
if (!keepId) {
|
||||
// 移除节点uid
|
||||
if (removeId) {
|
||||
delete tree.data.uid
|
||||
} else if (!tree.data.uid) {
|
||||
// 否则保留或生成
|
||||
tree.data.uid = createUid()
|
||||
}
|
||||
if (removeActiveState) {
|
||||
@@ -180,7 +189,7 @@ export const copyNodeTree = (
|
||||
tree.children = []
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((item, index) => {
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState, keepId)
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState, removeId)
|
||||
})
|
||||
} else if (
|
||||
root.nodeData &&
|
||||
@@ -188,7 +197,7 @@ export const copyNodeTree = (
|
||||
root.nodeData.children.length > 0
|
||||
) {
|
||||
root.nodeData.children.forEach((item, index) => {
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState, keepId)
|
||||
tree.children[index] = copyNodeTree({}, item, removeActiveState, removeId)
|
||||
})
|
||||
}
|
||||
return tree
|
||||
@@ -623,7 +632,7 @@ export const textToNodeRichTextWithWrap = html => {
|
||||
}
|
||||
return list
|
||||
.map(item => {
|
||||
return `<p><span>${item}</span></p>`
|
||||
return `<p><span>${htmlEscape(item)}</span></p>`
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
@@ -707,7 +716,7 @@ export const getTopAncestorsFomNodeList = list => {
|
||||
list.forEach(node => {
|
||||
if (
|
||||
!list.find(item => {
|
||||
return item.uid !== node.uid && item.isParent(node)
|
||||
return item.uid !== node.uid && item.isAncestor(node)
|
||||
})
|
||||
) {
|
||||
res.push(node)
|
||||
@@ -716,6 +725,71 @@ export const getTopAncestorsFomNodeList = list => {
|
||||
return res
|
||||
}
|
||||
|
||||
// 从给定的节点实例列表里判断是否存在上下级关系
|
||||
export const checkHasSupSubRelation = list => {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const cur = list[i]
|
||||
if (
|
||||
list.find(item => {
|
||||
return item.uid !== cur.uid && cur.isAncestor(item)
|
||||
})
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 解析要添加概要的节点实例列表
|
||||
export const parseAddGeneralizationNodeList = list => {
|
||||
const cache = {}
|
||||
const uidToParent = {}
|
||||
list.forEach(node => {
|
||||
const parent = node.parent
|
||||
if (parent) {
|
||||
const pUid = parent.uid
|
||||
uidToParent[pUid] = parent
|
||||
const index = node.getIndexInBrothers()
|
||||
const data = {
|
||||
node,
|
||||
index
|
||||
}
|
||||
if (cache[pUid]) {
|
||||
if (
|
||||
!cache[pUid].find(item => {
|
||||
return item.index === data.index
|
||||
})
|
||||
) {
|
||||
cache[pUid].push(data)
|
||||
}
|
||||
} else {
|
||||
cache[pUid] = [data]
|
||||
}
|
||||
}
|
||||
})
|
||||
const res = []
|
||||
Object.keys(cache).forEach(uid => {
|
||||
if (cache[uid].length > 1) {
|
||||
const rangeList = cache[uid]
|
||||
.map(item => {
|
||||
return item.index
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a - b
|
||||
})
|
||||
res.push({
|
||||
node: uidToParent[uid],
|
||||
range: [rangeList[0], rangeList[rangeList.length - 1]]
|
||||
})
|
||||
} else {
|
||||
res.push({
|
||||
node: cache[uid][0].node
|
||||
})
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 判断两个矩形是否重叠
|
||||
export const checkTwoRectIsOverlap = (
|
||||
minx1,
|
||||
@@ -766,14 +840,15 @@ export const addDataToAppointNodes = (appointNodes, data = {}) => {
|
||||
return appointNodes
|
||||
}
|
||||
|
||||
// 给指定的节点列表树数据添加uid,如果不存在的话,会修改原数据
|
||||
export const createUidForAppointNodes = appointNodes => {
|
||||
// 给指定的节点列表树数据添加uid,会修改原数据
|
||||
// createNewId默认为false,即如果节点不存在uid的话,会创建新的uid。如果传true,那么无论节点数据原来是否存在uid,都会创建新的uid
|
||||
export const createUidForAppointNodes = (appointNodes, createNewId = false) => {
|
||||
const walk = list => {
|
||||
list.forEach(node => {
|
||||
if (!node.data) {
|
||||
node.data = {}
|
||||
}
|
||||
if (isUndef(node.data.uid)) {
|
||||
if (createNewId || isUndef(node.data.uid)) {
|
||||
node.data.uid = createUid()
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
@@ -792,14 +867,21 @@ export const formatDataToArray = data => {
|
||||
}
|
||||
|
||||
// 获取节点在同级里的位置索引
|
||||
export const getNodeIndex = node => {
|
||||
export const getNodeDataIndex = node => {
|
||||
return node.parent
|
||||
? node.parent.children.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
? node.parent.nodeData.children.findIndex(item => {
|
||||
return item.data.uid === node.uid
|
||||
})
|
||||
: 0
|
||||
}
|
||||
|
||||
// 从一个节点列表里找出某个节点的索引
|
||||
export const getNodeIndexInNodeList = (node, nodeList) => {
|
||||
return nodeList.findIndex(item => {
|
||||
return item.uid === node.uid
|
||||
})
|
||||
}
|
||||
|
||||
// 根据内容生成颜色
|
||||
export const generateColorByContent = str => {
|
||||
let hash = 0
|
||||
@@ -871,3 +953,68 @@ export const isSameObject = (a, b) => {
|
||||
return a === b
|
||||
}
|
||||
}
|
||||
|
||||
// 将数据设置到用户剪切板中
|
||||
export const setDataToClipboard = data => {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(JSON.stringify(data))
|
||||
}
|
||||
}
|
||||
|
||||
// 从用户剪贴板中读取文字和图片
|
||||
export const getDataFromClipboard = async () => {
|
||||
let text = null
|
||||
let img = null
|
||||
if (navigator.clipboard) {
|
||||
text = await navigator.clipboard.readText()
|
||||
const items = await navigator.clipboard.read()
|
||||
if (items && items.length > 0) {
|
||||
for (const clipboardItem of items) {
|
||||
for (const type of clipboardItem.types) {
|
||||
if (/^image\//.test(type)) {
|
||||
img = await clipboardItem.getType(type)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
text,
|
||||
img
|
||||
}
|
||||
}
|
||||
|
||||
// 从节点的父节点的nodeData.children列表中移除该节点的数据
|
||||
export const removeFromParentNodeData = node => {
|
||||
if (!node || !node.parent) return
|
||||
const index = getNodeDataIndex(node)
|
||||
if (index === -1) return
|
||||
node.parent.nodeData.children.splice(index, 1)
|
||||
}
|
||||
|
||||
// 给html自闭合标签添加闭合状态
|
||||
export const handleSelfCloseTags = str => {
|
||||
selfCloseTagList.forEach(tagName => {
|
||||
str = str.replaceAll(
|
||||
new RegExp(`<${tagName}([^>]*)>`, 'g'),
|
||||
`<${tagName} $1 />`
|
||||
)
|
||||
})
|
||||
return str
|
||||
}
|
||||
|
||||
// 检查两个节点列表是否包含的节点是一样的
|
||||
export const checkNodeListIsEqual = (list1, list2) => {
|
||||
if (list1.length !== list2.length) return false
|
||||
for (let i = 0; i < list1.length; i++) {
|
||||
if (
|
||||
!list2.find(item => {
|
||||
return item.uid === list1[i].uid
|
||||
})
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
27908
simple-mind-map/src/utils/jspdf.js
Normal file
253
simple-mind-map/src/utils/xmind.js
Normal file
57
simple-mind-map/types/index.d.ts
vendored
@@ -74,9 +74,10 @@ declare class MindMap {
|
||||
richTextEditFakeInPlace: boolean;
|
||||
customHandleClipboardText: any;
|
||||
disableMouseWheelZoom: boolean;
|
||||
disableTouchZoom: boolean;
|
||||
errorHandler: (code: any, error: any) => void;
|
||||
resetCss: string;
|
||||
enableDblclickReset: boolean;
|
||||
enableDblclickBackToRootNode: boolean;
|
||||
minExportImgCanvasScale: number;
|
||||
hoverRectColor: string;
|
||||
hoverRectPadding: number;
|
||||
@@ -95,15 +96,24 @@ declare class MindMap {
|
||||
beingDragNodeOpacity: number;
|
||||
};
|
||||
tagsColorMap: {};
|
||||
cooperateStyle: {
|
||||
avatarSize: number;
|
||||
fontSize: number;
|
||||
};
|
||||
associativeLineIsAlwaysAboveNode: boolean;
|
||||
defaultGeneralizationText: string;
|
||||
handleIsSplitByWrapOnPasteCreateNewNode: any;
|
||||
addHistoryTime: number;
|
||||
isDisableDrag: boolean;
|
||||
highlightNodeBoxStyle: {
|
||||
stroke: string;
|
||||
fill: string;
|
||||
};
|
||||
createNewNodeBehavior: string;
|
||||
});
|
||||
opt: any;
|
||||
el: any;
|
||||
elRect: any;
|
||||
width: any;
|
||||
height: any;
|
||||
cssEl: HTMLStyleElement;
|
||||
svg: any;
|
||||
draw: any;
|
||||
event: Event;
|
||||
keyCommand: KeyCommand;
|
||||
command: Command;
|
||||
@@ -111,10 +121,22 @@ declare class MindMap {
|
||||
view: View;
|
||||
batchExecution: BatchExecution;
|
||||
handleOpt(opt: any): any;
|
||||
initContainer(): void;
|
||||
associativeLineDraw: any;
|
||||
svg: any;
|
||||
draw: any;
|
||||
lineDraw: any;
|
||||
nodeDraw: any;
|
||||
otherDraw: any;
|
||||
clearDraw(): void;
|
||||
addCss(): void;
|
||||
removeCss(): void;
|
||||
render(callback: any, source?: string): void;
|
||||
reRender(callback: any, source?: string): void;
|
||||
getElRectInfo(): void;
|
||||
elRect: any;
|
||||
width: any;
|
||||
height: any;
|
||||
resize(): void;
|
||||
on(event: any, fn: any): void;
|
||||
emit(event: any, ...args: any[]): void;
|
||||
@@ -122,15 +144,15 @@ declare class MindMap {
|
||||
initCache(): void;
|
||||
initTheme(): void;
|
||||
themeConfig: any;
|
||||
setTheme(theme: any): void;
|
||||
setTheme(theme: any, notRender?: boolean): void;
|
||||
getTheme(): any;
|
||||
setThemeConfig(config: any): void;
|
||||
setThemeConfig(config: any, notRender?: boolean): void;
|
||||
getCustomThemeConfig(): any;
|
||||
getThemeConfig(prop: any): any;
|
||||
getConfig(prop: any): any;
|
||||
updateConfig(opt?: {}): void;
|
||||
getLayout(): any;
|
||||
setLayout(layout: any): void;
|
||||
setLayout(layout: any, notRender?: boolean): void;
|
||||
execCommand(...args: any[]): void;
|
||||
setData(data: any): void;
|
||||
setFullData(data: any): void;
|
||||
@@ -141,9 +163,10 @@ declare class MindMap {
|
||||
y: number;
|
||||
};
|
||||
setMode(mode: any): void;
|
||||
getSvgData({ paddingX, paddingY }?: {
|
||||
getSvgData({ paddingX, paddingY, ignoreWatermark }?: {
|
||||
paddingX?: number;
|
||||
paddingY?: number;
|
||||
ignoreWatermark?: boolean;
|
||||
}): {
|
||||
svg: any;
|
||||
svgHTML: any;
|
||||
@@ -159,14 +182,14 @@ declare class MindMap {
|
||||
destroy(): void;
|
||||
}
|
||||
declare namespace MindMap {
|
||||
let pluginList: any[];
|
||||
const pluginList: any[];
|
||||
function usePlugin(plugin: any, opt?: {}): typeof MindMap;
|
||||
function hasPlugin(plugin: any): number;
|
||||
function defineTheme(name: any, config?: {}): Error;
|
||||
}
|
||||
import Event from './src/core/event/Event';
|
||||
import KeyCommand from './src/core/command/KeyCommand';
|
||||
import Command from './src/core/command/Command';
|
||||
import Render from './src/core/render/Render';
|
||||
import View from './src/core/view/View';
|
||||
import BatchExecution from './src/utils/BatchExecution';
|
||||
import Event from "./src/core/event/Event";
|
||||
import KeyCommand from "./src/core/command/KeyCommand";
|
||||
import Command from "./src/core/command/Command";
|
||||
import Render from "./src/core/render/Render";
|
||||
import View from "./src/core/view/View";
|
||||
import BatchExecution from "./src/utils/BatchExecution";
|
||||
|
||||
118
simple-mind-map/types/src/constants/constant.d.ts
vendored
@@ -4,81 +4,86 @@ export const themeList: {
|
||||
dark: boolean;
|
||||
}[];
|
||||
export namespace CONSTANTS {
|
||||
let CHANGE_THEME: string;
|
||||
let CHANGE_LAYOUT: string;
|
||||
let SET_DATA: string;
|
||||
let TRANSFORM_TO_NORMAL_NODE: string;
|
||||
const CHANGE_THEME: string;
|
||||
const CHANGE_LAYOUT: string;
|
||||
const SET_DATA: string;
|
||||
const TRANSFORM_TO_NORMAL_NODE: string;
|
||||
namespace MODE {
|
||||
let READONLY: string;
|
||||
let EDIT: string;
|
||||
const READONLY: string;
|
||||
const EDIT: string;
|
||||
}
|
||||
namespace LAYOUT {
|
||||
let LOGICAL_STRUCTURE: string;
|
||||
let MIND_MAP: string;
|
||||
let ORGANIZATION_STRUCTURE: string;
|
||||
let CATALOG_ORGANIZATION: string;
|
||||
let TIMELINE: string;
|
||||
let TIMELINE2: string;
|
||||
let FISHBONE: string;
|
||||
let VERTICAL_TIMELINE: string;
|
||||
const LOGICAL_STRUCTURE: string;
|
||||
const MIND_MAP: string;
|
||||
const ORGANIZATION_STRUCTURE: string;
|
||||
const CATALOG_ORGANIZATION: string;
|
||||
const TIMELINE: string;
|
||||
const TIMELINE2: string;
|
||||
const FISHBONE: string;
|
||||
const VERTICAL_TIMELINE: string;
|
||||
}
|
||||
namespace DIR {
|
||||
let UP: string;
|
||||
let LEFT: string;
|
||||
let DOWN: string;
|
||||
let RIGHT: string;
|
||||
const UP: string;
|
||||
const LEFT: string;
|
||||
const DOWN: string;
|
||||
const RIGHT: string;
|
||||
}
|
||||
namespace KEY_DIR {
|
||||
let LEFT_1: string;
|
||||
const LEFT_1: string;
|
||||
export { LEFT_1 as LEFT };
|
||||
let UP_1: string;
|
||||
const UP_1: string;
|
||||
export { UP_1 as UP };
|
||||
let RIGHT_1: string;
|
||||
const RIGHT_1: string;
|
||||
export { RIGHT_1 as RIGHT };
|
||||
let DOWN_1: string;
|
||||
const DOWN_1: string;
|
||||
export { DOWN_1 as DOWN };
|
||||
}
|
||||
namespace SHAPE {
|
||||
let RECTANGLE: string;
|
||||
let DIAMOND: string;
|
||||
let PARALLELOGRAM: string;
|
||||
let ROUNDED_RECTANGLE: string;
|
||||
let OCTAGONAL_RECTANGLE: string;
|
||||
let OUTER_TRIANGULAR_RECTANGLE: string;
|
||||
let INNER_TRIANGULAR_RECTANGLE: string;
|
||||
let ELLIPSE: string;
|
||||
let CIRCLE: string;
|
||||
const RECTANGLE: string;
|
||||
const DIAMOND: string;
|
||||
const PARALLELOGRAM: string;
|
||||
const ROUNDED_RECTANGLE: string;
|
||||
const OCTAGONAL_RECTANGLE: string;
|
||||
const OUTER_TRIANGULAR_RECTANGLE: string;
|
||||
const INNER_TRIANGULAR_RECTANGLE: string;
|
||||
const ELLIPSE: string;
|
||||
const CIRCLE: string;
|
||||
}
|
||||
namespace MOUSE_WHEEL_ACTION {
|
||||
let ZOOM: string;
|
||||
let MOVE: string;
|
||||
const ZOOM: string;
|
||||
const MOVE: string;
|
||||
}
|
||||
namespace INIT_ROOT_NODE_POSITION {
|
||||
let LEFT_2: string;
|
||||
const LEFT_2: string;
|
||||
export { LEFT_2 as LEFT };
|
||||
export let TOP: string;
|
||||
let RIGHT_2: string;
|
||||
export const TOP: string;
|
||||
const RIGHT_2: string;
|
||||
export { RIGHT_2 as RIGHT };
|
||||
export let BOTTOM: string;
|
||||
export let CENTER: string;
|
||||
export const BOTTOM: string;
|
||||
export const CENTER: string;
|
||||
}
|
||||
namespace LAYOUT_GROW_DIR {
|
||||
let LEFT_3: string;
|
||||
const LEFT_3: string;
|
||||
export { LEFT_3 as LEFT };
|
||||
let TOP_1: string;
|
||||
const TOP_1: string;
|
||||
export { TOP_1 as TOP };
|
||||
let RIGHT_3: string;
|
||||
const RIGHT_3: string;
|
||||
export { RIGHT_3 as RIGHT };
|
||||
let BOTTOM_1: string;
|
||||
const BOTTOM_1: string;
|
||||
export { BOTTOM_1 as BOTTOM };
|
||||
}
|
||||
namespace PASTE_TYPE {
|
||||
let CLIP_BOARD: string;
|
||||
let CANVAS: string;
|
||||
const CLIP_BOARD: string;
|
||||
const CANVAS: string;
|
||||
}
|
||||
namespace SCROLL_BAR_DIR {
|
||||
let VERTICAL: string;
|
||||
let HORIZONTAL: string;
|
||||
const VERTICAL: string;
|
||||
const HORIZONTAL: string;
|
||||
}
|
||||
namespace CREATE_NEW_NODE_BEHAVIOR {
|
||||
const DEFAULT: string;
|
||||
const NOT_ACTIVE: string;
|
||||
const ACTIVE_ONLY: string;
|
||||
}
|
||||
}
|
||||
export const initRootNodePositionMap: {
|
||||
@@ -91,19 +96,20 @@ export const layoutList: {
|
||||
export const layoutValueList: string[];
|
||||
export const nodeDataNoStylePropList: string[];
|
||||
export namespace commonCaches {
|
||||
let measureCustomNodeContentSizeEl: any;
|
||||
let measureRichtextNodeTextSizeEl: any;
|
||||
const measureCustomNodeContentSizeEl: any;
|
||||
const measureRichtextNodeTextSizeEl: any;
|
||||
}
|
||||
export namespace ERROR_TYPES {
|
||||
let READ_CLIPBOARD_ERROR: string;
|
||||
let PARSE_PASTE_DATA_ERROR: string;
|
||||
let CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR: string;
|
||||
let LOAD_CLIPBOARD_IMAGE_ERROR: string;
|
||||
let BEFORE_TEXT_EDIT_ERROR: string;
|
||||
let EXPORT_ERROR: string;
|
||||
const READ_CLIPBOARD_ERROR: string;
|
||||
const PARSE_PASTE_DATA_ERROR: string;
|
||||
const CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR: string;
|
||||
const LOAD_CLIPBOARD_IMAGE_ERROR: string;
|
||||
const BEFORE_TEXT_EDIT_ERROR: string;
|
||||
const EXPORT_ERROR: string;
|
||||
}
|
||||
export namespace a4Size {
|
||||
let width: number;
|
||||
let height: number;
|
||||
const width: number;
|
||||
const height: number;
|
||||
}
|
||||
export const cssContent: "\n /* 鼠标hover和激活时渲染的矩形 */\n .smm-hover-node{\n display: none;\n opacity: 0.6;\n stroke-width: 1;\n }\n\n .smm-node:not(.smm-node-dragging):hover .smm-hover-node{\n display: block;\n }\n\n .smm-node.active .smm-hover-node{\n display: block;\n opacity: 1;\n stroke-width: 2;\n }\n";
|
||||
export const selfCloseTagList: string[];
|
||||
|
||||
@@ -1,95 +1,112 @@
|
||||
export namespace defaultOpt {
|
||||
let readonly: boolean;
|
||||
let layout: string;
|
||||
let fishboneDeg: number;
|
||||
let theme: string;
|
||||
let themeConfig: {};
|
||||
let scaleRatio: number;
|
||||
let mouseScaleCenterUseMousePosition: boolean;
|
||||
let maxTag: number;
|
||||
let expandBtnSize: number;
|
||||
let imgTextMargin: number;
|
||||
let textContentMargin: number;
|
||||
let selectTranslateStep: number;
|
||||
let selectTranslateLimit: number;
|
||||
let customNoteContentShow: any;
|
||||
let enableFreeDrag: boolean;
|
||||
const readonly: boolean;
|
||||
const layout: string;
|
||||
const fishboneDeg: number;
|
||||
const theme: string;
|
||||
const themeConfig: {};
|
||||
const scaleRatio: number;
|
||||
const mouseScaleCenterUseMousePosition: boolean;
|
||||
const maxTag: number;
|
||||
const expandBtnSize: number;
|
||||
const imgTextMargin: number;
|
||||
const textContentMargin: number;
|
||||
const selectTranslateStep: number;
|
||||
const selectTranslateLimit: number;
|
||||
const customNoteContentShow: any;
|
||||
const enableFreeDrag: boolean;
|
||||
namespace watermarkConfig {
|
||||
let text: string;
|
||||
let lineSpacing: number;
|
||||
let textSpacing: number;
|
||||
let angle: number;
|
||||
const text: string;
|
||||
const lineSpacing: number;
|
||||
const textSpacing: number;
|
||||
const angle: number;
|
||||
namespace textStyle {
|
||||
let color: string;
|
||||
let opacity: number;
|
||||
let fontSize: number;
|
||||
const color: string;
|
||||
const opacity: number;
|
||||
const fontSize: number;
|
||||
}
|
||||
}
|
||||
let textAutoWrapWidth: number;
|
||||
let customHandleMousewheel: any;
|
||||
let mousewheelAction: string;
|
||||
let mousewheelMoveStep: number;
|
||||
let mousewheelZoomActionReverse: boolean;
|
||||
let defaultInsertSecondLevelNodeText: string;
|
||||
let defaultInsertBelowSecondLevelNodeText: string;
|
||||
const textAutoWrapWidth: number;
|
||||
const customHandleMousewheel: any;
|
||||
const mousewheelAction: string;
|
||||
const mousewheelMoveStep: number;
|
||||
const mousewheelZoomActionReverse: boolean;
|
||||
const defaultInsertSecondLevelNodeText: string;
|
||||
const defaultInsertBelowSecondLevelNodeText: string;
|
||||
namespace expandBtnStyle {
|
||||
let color_1: string;
|
||||
const color_1: string;
|
||||
export { color_1 as color };
|
||||
export let fill: string;
|
||||
let fontSize_1: number;
|
||||
export const fill: string;
|
||||
const fontSize_1: number;
|
||||
export { fontSize_1 as fontSize };
|
||||
export let strokeColor: string;
|
||||
export const strokeColor: string;
|
||||
}
|
||||
namespace expandBtnIcon {
|
||||
let open: string;
|
||||
let close: string;
|
||||
const open: string;
|
||||
const close: string;
|
||||
}
|
||||
function expandBtnNumHandler(num: any): any;
|
||||
let isShowExpandNum: boolean;
|
||||
let enableShortcutOnlyWhenMouseInSvg: boolean;
|
||||
let initRootNodePosition: any;
|
||||
let exportPaddingX: number;
|
||||
let exportPaddingY: number;
|
||||
let nodeTextEditZIndex: number;
|
||||
let nodeNoteTooltipZIndex: number;
|
||||
let isEndNodeTextEditOnClickOuter: boolean;
|
||||
let maxHistoryCount: number;
|
||||
let alwaysShowExpandBtn: boolean;
|
||||
let iconList: any[];
|
||||
let maxNodeCacheCount: number;
|
||||
let defaultAssociativeLineText: string;
|
||||
let fitPadding: number;
|
||||
let enableCtrlKeyNodeSelection: boolean;
|
||||
let useLeftKeySelectionRightKeyDrag: boolean;
|
||||
let beforeTextEdit: any;
|
||||
let isUseCustomNodeContent: boolean;
|
||||
let customCreateNodeContent: any;
|
||||
let customInnerElsAppendTo: any;
|
||||
let nodeDragPlaceholderMaxSize: number;
|
||||
let enableAutoEnterTextEditWhenKeydown: boolean;
|
||||
let richTextEditFakeInPlace: boolean;
|
||||
let customHandleClipboardText: any;
|
||||
let disableMouseWheelZoom: boolean;
|
||||
const isShowExpandNum: boolean;
|
||||
const enableShortcutOnlyWhenMouseInSvg: boolean;
|
||||
const initRootNodePosition: any;
|
||||
const exportPaddingX: number;
|
||||
const exportPaddingY: number;
|
||||
const nodeTextEditZIndex: number;
|
||||
const nodeNoteTooltipZIndex: number;
|
||||
const isEndNodeTextEditOnClickOuter: boolean;
|
||||
const maxHistoryCount: number;
|
||||
const alwaysShowExpandBtn: boolean;
|
||||
const iconList: any[];
|
||||
const maxNodeCacheCount: number;
|
||||
const defaultAssociativeLineText: string;
|
||||
const fitPadding: number;
|
||||
const enableCtrlKeyNodeSelection: boolean;
|
||||
const useLeftKeySelectionRightKeyDrag: boolean;
|
||||
const beforeTextEdit: any;
|
||||
const isUseCustomNodeContent: boolean;
|
||||
const customCreateNodeContent: any;
|
||||
const customInnerElsAppendTo: any;
|
||||
const nodeDragPlaceholderMaxSize: number;
|
||||
const enableAutoEnterTextEditWhenKeydown: boolean;
|
||||
const richTextEditFakeInPlace: boolean;
|
||||
const customHandleClipboardText: any;
|
||||
const disableMouseWheelZoom: boolean;
|
||||
const disableTouchZoom: boolean;
|
||||
function errorHandler(code: any, error: any): void;
|
||||
let resetCss: string;
|
||||
let enableDblclickReset: boolean;
|
||||
let minExportImgCanvasScale: number;
|
||||
let hoverRectColor: string;
|
||||
let hoverRectPadding: number;
|
||||
let selectTextOnEnterEditText: boolean;
|
||||
let deleteNodeActive: boolean;
|
||||
let autoMoveWhenMouseInEdgeOnDrag: boolean;
|
||||
let fit: boolean;
|
||||
const resetCss: string;
|
||||
const enableDblclickBackToRootNode: boolean;
|
||||
const minExportImgCanvasScale: number;
|
||||
const hoverRectColor: string;
|
||||
const hoverRectPadding: number;
|
||||
const selectTextOnEnterEditText: boolean;
|
||||
const deleteNodeActive: boolean;
|
||||
const autoMoveWhenMouseInEdgeOnDrag: boolean;
|
||||
const fit: boolean;
|
||||
namespace dragMultiNodeRectConfig {
|
||||
export let width: number;
|
||||
export let height: number;
|
||||
let fill_1: string;
|
||||
export const width: number;
|
||||
export const height: number;
|
||||
const fill_1: string;
|
||||
export { fill_1 as fill };
|
||||
}
|
||||
let dragPlaceholderRectFill: string;
|
||||
const dragPlaceholderRectFill: string;
|
||||
namespace dragOpacityConfig {
|
||||
let cloneNodeOpacity: number;
|
||||
let beingDragNodeOpacity: number;
|
||||
const cloneNodeOpacity: number;
|
||||
const beingDragNodeOpacity: number;
|
||||
}
|
||||
let tagsColorMap: {};
|
||||
const tagsColorMap: {};
|
||||
namespace cooperateStyle {
|
||||
export const avatarSize: number;
|
||||
const fontSize_2: number;
|
||||
export { fontSize_2 as fontSize };
|
||||
}
|
||||
const associativeLineIsAlwaysAboveNode: boolean;
|
||||
const defaultGeneralizationText: string;
|
||||
const handleIsSplitByWrapOnPasteCreateNewNode: any;
|
||||
const addHistoryTime: number;
|
||||
const isDisableDrag: boolean;
|
||||
namespace highlightNodeBoxStyle {
|
||||
export const stroke: string;
|
||||
const fill_2: string;
|
||||
export { fill_2 as fill };
|
||||
}
|
||||
const createNewNodeBehavior: string;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ export default class KeyCommand {
|
||||
save(): void;
|
||||
restore(): void;
|
||||
bindEvent(): void;
|
||||
onKeydown(e: any): void;
|
||||
unBindEvent(): void;
|
||||
checkKey(e: any, key: any): boolean;
|
||||
getOriginEventCodeArr(e: any): any[];
|
||||
hasCombinationKey(e: any): any;
|
||||
|
||||
@@ -4,11 +4,11 @@ declare class Render {
|
||||
opt: {};
|
||||
mindMap: any;
|
||||
themeConfig: any;
|
||||
draw: any;
|
||||
renderTree: any;
|
||||
reRender: boolean;
|
||||
isRendering: boolean;
|
||||
hasWaitRendering: boolean;
|
||||
waitRenderingParams: any[];
|
||||
nodeCache: {};
|
||||
lastNodeCache: {};
|
||||
renderSource: string;
|
||||
@@ -20,8 +20,12 @@ declare class Render {
|
||||
beingPasteText: string;
|
||||
beingPasteImgSize: number;
|
||||
currentBeingPasteType: string;
|
||||
highlightBoxNode: any;
|
||||
lastActiveNode: any;
|
||||
lastActiveNodeList: any[];
|
||||
setLayout(): void;
|
||||
layout: MindMap | CatalogOrganization | OrganizationStructure | Timeline | VerticalTimeline;
|
||||
setData(data: any): void;
|
||||
bindEvent(): void;
|
||||
registerCommands(): void;
|
||||
selectAll(): void;
|
||||
@@ -31,18 +35,20 @@ declare class Render {
|
||||
insertMultiNode(appointNodes: any, nodeList: any): void;
|
||||
insertChildNode(openEdit?: boolean, appointNodes?: any[], appointData?: any, appointChildren?: any[]): void;
|
||||
insertMultiChildNode(appointNodes: any, childList: any): void;
|
||||
insertParentNode(openEdit: boolean, appointNodes: any, appointData: any): void;
|
||||
upNode(): void;
|
||||
downNode(): void;
|
||||
insertAfter(node: any, exist: any): void;
|
||||
insertBefore(node: any, exist: any): void;
|
||||
moveNodeTo(node: any, toNode: any): void;
|
||||
removeNode(appointNodes?: any[]): void;
|
||||
removeCurrentNode(appointNodes?: any[]): void;
|
||||
pasteNode(data: any): void;
|
||||
cutNode(callback: any): void;
|
||||
setNodeStyle(node: any, prop: any, value: any): void;
|
||||
setNodeStyles(node: any, style: any): void;
|
||||
setNodeActive(node: any, active: any): void;
|
||||
clearAllActive(): void;
|
||||
clearActiveNode(): void;
|
||||
setNodeExpand(node: any, expand: any): void;
|
||||
expandAllNode(): void;
|
||||
unexpandAllNode(): void;
|
||||
@@ -62,34 +68,42 @@ declare class Render {
|
||||
setNodeShape(node: any, shape: any): void;
|
||||
goTargetNode(node: any, callback?: () => void): void;
|
||||
registerShortcutKeys(): void;
|
||||
insertNodeWrap: () => void;
|
||||
toggleActiveExpand(): void;
|
||||
removeNodeWrap: () => void;
|
||||
copy(): void;
|
||||
cut(): void;
|
||||
emitNodeActiveEvent(node?: any, activeNodeList?: any[]): void;
|
||||
clearActiveNodeListOnDrawClick(e: any, eventType: any): void;
|
||||
startTextEdit(): void;
|
||||
endTextEdit(): void;
|
||||
render(callback: () => void, source: any): void;
|
||||
clearActive(): void;
|
||||
addActiveNode(node: any): void;
|
||||
removeActiveNode(node: any): void;
|
||||
findActiveNodeIndex(node: any): number;
|
||||
setCopyDataToClipboard(data: any): void;
|
||||
clearActiveNodeList(): void;
|
||||
addNodeToActiveList(node: any): void;
|
||||
removeNodeFromActiveList(node: any): void;
|
||||
findActiveNodeIndex(node: any): any;
|
||||
backForward(type: any, step: any): void;
|
||||
getNewNodeBehavior(openEdit?: boolean, handleMultiNodes?: boolean): {
|
||||
focusNewNode: boolean;
|
||||
inserting: boolean;
|
||||
};
|
||||
copy(): void;
|
||||
cut(): void;
|
||||
paste(): void;
|
||||
onPaste(): Promise<void>;
|
||||
insertTo(node: any, exist: any, dir?: string): void;
|
||||
checkNodeLayerChange(node: any, toNode: any): void;
|
||||
removeOneNode(node: any): void;
|
||||
deleteNodeGeneralization(node: any): void;
|
||||
getNextActiveNode(): any;
|
||||
copyNode(): any;
|
||||
toggleNodeExpand(node: any): void;
|
||||
setNodeDataRender(node: any, data: any, notRender?: boolean): void;
|
||||
moveNodeToCenter(node: any): void;
|
||||
setRootNodeCenter(): void;
|
||||
expandToNodeUid(uid: any, callback?: () => void): void;
|
||||
findNodeByUid(uid: any): any;
|
||||
highlightNode(node: any, range: any): void;
|
||||
closeHighlightNode(): void;
|
||||
}
|
||||
import TextEdit from './TextEdit';
|
||||
import MindMap from '../../layouts/MindMap';
|
||||
import CatalogOrganization from '../../layouts/CatalogOrganization';
|
||||
import OrganizationStructure from '../../layouts/OrganizationStructure';
|
||||
import Timeline from '../../layouts/Timeline';
|
||||
import VerticalTimeline from '../../layouts/VerticalTimeline';
|
||||
import TextEdit from "./TextEdit";
|
||||
import MindMap from "../../layouts/MindMap";
|
||||
import CatalogOrganization from "../../layouts/CatalogOrganization";
|
||||
import OrganizationStructure from "../../layouts/OrganizationStructure";
|
||||
import Timeline from "../../layouts/Timeline";
|
||||
import VerticalTimeline from "../../layouts/VerticalTimeline";
|
||||
|
||||
@@ -7,11 +7,24 @@ export default class TextEdit {
|
||||
showTextEdit: boolean;
|
||||
cacheEditingText: string;
|
||||
bindEvent(): void;
|
||||
show(node: any, e: any, isInserting?: boolean, isFromKeyDown?: boolean): Promise<void>;
|
||||
show({ node, isInserting, isFromKeyDown, isFromScale }: {
|
||||
node: any;
|
||||
isInserting?: boolean;
|
||||
isFromKeyDown?: boolean;
|
||||
isFromScale?: boolean;
|
||||
}): Promise<void>;
|
||||
onScale(): void;
|
||||
onKeydown(e: any): void;
|
||||
unBindEvent(): void;
|
||||
checkIsAutoEnterTextEditKey(e: any): boolean;
|
||||
registerTmpShortcut(): void;
|
||||
showEditTextBox(node: any, rect: any, isInserting: any, isFromKeyDown: any): void;
|
||||
showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }: {
|
||||
node: any;
|
||||
rect: any;
|
||||
isInserting: any;
|
||||
isFromKeyDown: any;
|
||||
isFromScale: any;
|
||||
}): void;
|
||||
getEditText(): any;
|
||||
hideEditTextBox(): any;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ declare class Node {
|
||||
mindMap: any;
|
||||
renderer: any;
|
||||
draw: any;
|
||||
nodeDraw: any;
|
||||
lineDraw: any;
|
||||
style: Style;
|
||||
shapeInstance: Shape;
|
||||
shapePadding: {
|
||||
@@ -25,6 +27,7 @@ declare class Node {
|
||||
isDrag: boolean;
|
||||
parent: any;
|
||||
children: any;
|
||||
userList: any[];
|
||||
group: any;
|
||||
shapeNode: any;
|
||||
hoverNode: any;
|
||||
@@ -36,15 +39,16 @@ declare class Node {
|
||||
_tagData: any;
|
||||
_noteData: any;
|
||||
noteEl: any;
|
||||
noteContentIsShow: boolean;
|
||||
_expandBtn: any;
|
||||
_lastExpandBtnType: any;
|
||||
_showExpandBtn: boolean;
|
||||
_openExpandNode: any;
|
||||
_closeExpandNode: any;
|
||||
_fillExpandNode: any;
|
||||
_userListGroup: any;
|
||||
_lines: any[];
|
||||
_generalizationLine: any;
|
||||
_generalizationNode: any;
|
||||
_generalizationList: any[];
|
||||
_unVisibleRectRegionNode: any;
|
||||
_isMouseenter: boolean;
|
||||
_rectInfo: {
|
||||
@@ -82,7 +86,8 @@ declare class Node {
|
||||
top: any;
|
||||
};
|
||||
reRender(): boolean;
|
||||
updateNodeActive(): void;
|
||||
updateNodeActiveClass(): void;
|
||||
updateNodeByActive(active: any): void;
|
||||
render(callback?: () => void): void;
|
||||
remove(): void;
|
||||
destroy(): void;
|
||||
@@ -97,11 +102,14 @@ declare class Node {
|
||||
getShape(): any;
|
||||
hasCustomPosition(): boolean;
|
||||
ancestorHasCustomPosition(): boolean;
|
||||
ancestorHasGeneralization(): boolean;
|
||||
addChildren(node: any): void;
|
||||
styleLine(line: any, node: any): void;
|
||||
removeLine(): void;
|
||||
isAncestor(node: any): boolean;
|
||||
isParent(node: any): boolean;
|
||||
isBrother(node: any): any;
|
||||
getIndexInBrothers(): any;
|
||||
getPaddingVale(): {
|
||||
paddingX: any;
|
||||
paddingY: any;
|
||||
@@ -112,7 +120,17 @@ declare class Node {
|
||||
getSelfInhertStyle(prop: any): any;
|
||||
getBorderWidth(): any;
|
||||
getData(key: any): any;
|
||||
getPureData(removeActiveState?: boolean, removeId?: boolean): any;
|
||||
hasCustomStyle(): boolean;
|
||||
getRect(): any;
|
||||
getRectInSvg(): {
|
||||
left: any;
|
||||
right: any;
|
||||
top: any;
|
||||
bottom: any;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
import Style from './Style';
|
||||
import Shape from './Shape';
|
||||
import Style from "./Style";
|
||||
import Shape from "./Shape";
|
||||
|
||||
@@ -32,5 +32,5 @@ declare class Style {
|
||||
hoverNode(node: any): void;
|
||||
}
|
||||
declare namespace Style {
|
||||
let cacheStyle: any;
|
||||
const cacheStyle: any;
|
||||
}
|
||||
|
||||
18
simple-mind-map/types/src/core/render/node/nodeCooperate.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
declare namespace _default {
|
||||
export { createUserListNode };
|
||||
export { updateUserListNode };
|
||||
export { createTextAvatar };
|
||||
export { createImageAvatar };
|
||||
export { addUser };
|
||||
export { removeUser };
|
||||
}
|
||||
export default _default;
|
||||
declare function createUserListNode(): void;
|
||||
declare class createUserListNode {
|
||||
_userListGroup: any;
|
||||
}
|
||||
declare function updateUserListNode(): void;
|
||||
declare function createTextAvatar(item: any): any;
|
||||
declare function createImageAvatar(item: any): any;
|
||||
declare function addUser(userInfo: any): void;
|
||||
declare function removeUser(userInfo: any): void;
|
||||
@@ -7,6 +7,7 @@ declare namespace _default {
|
||||
export { createHyperlinkNode };
|
||||
export { createTagNode };
|
||||
export { createNoteNode };
|
||||
export { getNoteContentPosition };
|
||||
export { measureCustomNodeContentSize };
|
||||
export { isUseCustomNodeContent };
|
||||
}
|
||||
@@ -38,6 +39,10 @@ declare function createNoteNode(): {
|
||||
declare class createNoteNode {
|
||||
noteEl: HTMLDivElement;
|
||||
}
|
||||
declare function getNoteContentPosition(): {
|
||||
left: any;
|
||||
top: any;
|
||||
};
|
||||
declare function measureCustomNodeContentSize(content: any): {
|
||||
width: any;
|
||||
height: any;
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
declare namespace _default {
|
||||
export { formatGetGeneralization };
|
||||
export { checkHasGeneralization };
|
||||
export { checkHasSelfGeneralization };
|
||||
export { getGeneralizationNodeIndex };
|
||||
export { createGeneralizationNode };
|
||||
export { updateGeneralization };
|
||||
export { updateGeneralizationData };
|
||||
export { renderGeneralization };
|
||||
export { removeGeneralization };
|
||||
export { hideGeneralization };
|
||||
export { showGeneralization };
|
||||
export { setGeneralizationOpacity };
|
||||
export { handleGeneralizationMouseenter };
|
||||
export { handleGeneralizationMouseleave };
|
||||
}
|
||||
export default _default;
|
||||
declare function formatGetGeneralization(): any[];
|
||||
declare function checkHasGeneralization(): boolean;
|
||||
declare function checkHasSelfGeneralization(): boolean;
|
||||
declare function getGeneralizationNodeIndex(node: any): any;
|
||||
declare function createGeneralizationNode(): void;
|
||||
declare class createGeneralizationNode {
|
||||
_generalizationLine: any;
|
||||
_generalizationNode: Node;
|
||||
_generalizationNodeWidth: any;
|
||||
_generalizationNodeHeight: any;
|
||||
}
|
||||
declare function updateGeneralization(): void;
|
||||
declare function renderGeneralization(): void;
|
||||
declare class renderGeneralization {
|
||||
_generalizationNodeWidth: number;
|
||||
_generalizationNodeHeight: number;
|
||||
}
|
||||
declare function updateGeneralization(): void;
|
||||
declare function updateGeneralizationData(): void;
|
||||
declare function renderGeneralization(): void;
|
||||
declare function removeGeneralization(): void;
|
||||
declare class removeGeneralization {
|
||||
_generalizationLine: any;
|
||||
_generalizationNode: any;
|
||||
_generalizationList: any[];
|
||||
}
|
||||
declare function hideGeneralization(): void;
|
||||
declare function showGeneralization(): void;
|
||||
import Node from './Node';
|
||||
declare function setGeneralizationOpacity(val: any): void;
|
||||
declare function handleGeneralizationMouseenter(): void;
|
||||
declare function handleGeneralizationMouseleave(): void;
|
||||
|
||||
19
simple-mind-map/types/src/layouts/Base.d.ts
vendored
@@ -4,6 +4,7 @@ declare class Base {
|
||||
renderer: any;
|
||||
mindMap: any;
|
||||
draw: any;
|
||||
lineDraw: any;
|
||||
root: any;
|
||||
lru: Lru;
|
||||
doLayout(): void;
|
||||
@@ -38,6 +39,22 @@ declare class Base {
|
||||
generalizationLineMargin: any;
|
||||
generalizationNodeMargin: any;
|
||||
};
|
||||
getChildrenBoundaries(node: any, dir: any, startIndex: number, endIndex: any): {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
generalizationLineMargin: any;
|
||||
generalizationNodeMargin: any;
|
||||
};
|
||||
getNodeGeneralizationRenderBoundaries(item: any, dir: any): {
|
||||
left: any;
|
||||
right: any;
|
||||
top: any;
|
||||
bottom: any;
|
||||
generalizationLineMargin: any;
|
||||
generalizationNodeMargin: any;
|
||||
};
|
||||
getNodeActChildrenLength(node: any): any;
|
||||
}
|
||||
import Lru from '../utils/Lru';
|
||||
import Lru from "../utils/Lru";
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
export default CatalogOrganization;
|
||||
declare class CatalogOrganization extends Base {
|
||||
constructor(opt?: {});
|
||||
doLayout(callback: any): void;
|
||||
computedBaseValue(): void;
|
||||
computedLeftTopValue(): void;
|
||||
adjustLeftTopValue(): void;
|
||||
updateBrothersLeft(node: any, addWidth: any): void;
|
||||
updateBrothersTop(node: any, addHeight: any): void;
|
||||
renderLine(node: any, lines: any, style: any): any[];
|
||||
renderExpandBtn(node: any, btn: any): void;
|
||||
renderGeneralization(node: any, gLine: any, gNode: any): void;
|
||||
renderExpandBtnRect(rect: any, expandBtnSize: any, width: any, height: any, node: any): void;
|
||||
}
|
||||
import Base from './Base';
|
||||
import Base from "./Base";
|
||||
|
||||
@@ -3,7 +3,6 @@ declare class Fishbone extends Base {
|
||||
constructor(opt?: {});
|
||||
indent: number;
|
||||
childIndent: number;
|
||||
doLayout(callback: any): void;
|
||||
computedBaseValue(): void;
|
||||
computedLeftTopValue(): void;
|
||||
adjustLeftTopValue(): void;
|
||||
@@ -11,9 +10,6 @@ declare class Fishbone extends Base {
|
||||
updateBrothersLeft(node: any): void;
|
||||
updateBrothersTop(node: any, addHeight: any): void;
|
||||
checkIsTop(node: any): boolean;
|
||||
renderLine(node: any, lines: any, style: any): any[];
|
||||
renderExpandBtn(node: any, btn: any): void;
|
||||
renderGeneralization(node: any, gLine: any, gNode: any): void;
|
||||
renderExpandBtnRect(rect: any, expandBtnSize: any, width: any, height: any, node: any): void;
|
||||
}
|
||||
import Base from './Base';
|
||||
import Base from "./Base";
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
export default LogicalStructure;
|
||||
declare class LogicalStructure extends Base {
|
||||
constructor(opt?: {});
|
||||
doLayout(callback: any): void;
|
||||
computedBaseValue(): void;
|
||||
computedTopValue(): void;
|
||||
adjustTopValue(): void;
|
||||
updateBrothers(node: any, addHeight: any): void;
|
||||
renderLine(node: any, lines: any, style: any, lineStyle: any): void;
|
||||
renderLineStraight(node: any, lines: any, style: any): any[];
|
||||
renderLineDirect(node: any, lines: any, style: any): any[];
|
||||
renderLineCurve(node: any, lines: any, style: any): any[];
|
||||
renderExpandBtn(node: any, btn: any): void;
|
||||
renderGeneralization(node: any, gLine: any, gNode: any): void;
|
||||
renderExpandBtnRect(rect: any, expandBtnSize: any, width: any, height: any, node: any): void;
|
||||
}
|
||||
import Base from './Base';
|
||||
import Base from "./Base";
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
export default MindMap;
|
||||
declare class MindMap extends Base {
|
||||
constructor(opt?: {});
|
||||
doLayout(callback: any): void;
|
||||
computedBaseValue(): void;
|
||||
computedTopValue(): void;
|
||||
adjustTopValue(): void;
|
||||
updateBrothers(node: any, leftAddHeight: any, rightAddHeight: any): void;
|
||||
renderLine(node: any, lines: any, style: any, lineStyle: any): void;
|
||||
renderLineStraight(node: any, lines: any, style: any): any[];
|
||||
renderLineDirect(node: any, lines: any, style: any): any[];
|
||||
renderLineCurve(node: any, lines: any, style: any): any[];
|
||||
renderExpandBtn(node: any, btn: any): void;
|
||||
renderGeneralization(node: any, gLine: any, gNode: any): void;
|
||||
renderExpandBtnRect(rect: any, expandBtnSize: any, width: any, height: any, node: any): void;
|
||||
}
|
||||
import Base from './Base';
|
||||
import Base from "./Base";
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
export default OrganizationStructure;
|
||||
declare class OrganizationStructure extends Base {
|
||||
constructor(opt?: {});
|
||||
doLayout(callback: any): void;
|
||||
computedBaseValue(): void;
|
||||
computedLeftValue(): void;
|
||||
adjustLeftValue(): void;
|
||||
updateBrothers(node: any, addWidth: any): void;
|
||||
renderLine(node: any, lines: any, style: any, lineStyle: any): void;
|
||||
renderLineDirect(node: any, lines: any, style: any): any[];
|
||||
renderLineStraight(node: any, lines: any, style: any): any[];
|
||||
renderExpandBtn(node: any, btn: any): void;
|
||||
renderGeneralization(node: any, gLine: any, gNode: any): void;
|
||||
renderExpandBtnRect(rect: any, expandBtnSize: any, width: any, height: any, node: any): void;
|
||||
}
|
||||
import Base from './Base';
|
||||
import Base from "./Base";
|
||||
|
||||
@@ -2,16 +2,12 @@ export default Timeline;
|
||||
declare class Timeline extends Base {
|
||||
constructor(opt: {}, layout: any);
|
||||
layout: any;
|
||||
doLayout(callback: any): void;
|
||||
computedBaseValue(): void;
|
||||
computedLeftTopValue(): void;
|
||||
adjustLeftTopValue(): void;
|
||||
getNodeAreaHeight(node: any): number;
|
||||
updateBrothersLeft(node: any): void;
|
||||
updateBrothersTop(node: any, addHeight: any): void;
|
||||
renderLine(node: any, lines: any, style: any): any[];
|
||||
renderExpandBtn(node: any, btn: any): void;
|
||||
renderGeneralization(node: any, gLine: any, gNode: any): void;
|
||||
renderExpandBtnRect(rect: any, expandBtnSize: any, width: any, height: any, node: any): void;
|
||||
}
|
||||
import Base from './Base';
|
||||
import Base from "./Base";
|
||||
|
||||
@@ -2,18 +2,14 @@ export default VerticalTimeline;
|
||||
declare class VerticalTimeline extends Base {
|
||||
constructor(opt: {}, layout: any);
|
||||
layout: any;
|
||||
doLayout(callback: any): void;
|
||||
computedBaseValue(): void;
|
||||
computedTopValue(): void;
|
||||
adjustLeftTopValue(): void;
|
||||
updateBrothers(node: any, addHeight: any): void;
|
||||
updateBrothersTop(node: any, addHeight: any): void;
|
||||
renderLine(node: any, lines: any, style: any, lineStyle: any): void;
|
||||
renderLineStraight(node: any, lines: any, style: any): any[];
|
||||
renderLineDirect(node: any, lines: any, style: any): any[];
|
||||
renderLineCurve(node: any, lines: any, style: any): any[];
|
||||
renderExpandBtn(node: any, btn: any): void;
|
||||
renderGeneralization(node: any, gLine: any, gNode: any): void;
|
||||
renderExpandBtnRect(rect: any, expandBtnSize: any, width: any, height: any, node: any): void;
|
||||
}
|
||||
import Base from './Base';
|
||||
import Base from "./Base";
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
declare namespace _default {
|
||||
namespace top {
|
||||
function renderExpandBtn({ node, btn, expandBtnSize, translateX, translateY, width, height }: {
|
||||
node: any;
|
||||
btn: any;
|
||||
expandBtnSize: any;
|
||||
translateX: any;
|
||||
translateY: any;
|
||||
width: any;
|
||||
height: any;
|
||||
}): void;
|
||||
function renderExpandBtn({ node, btn, expandBtnSize, translateX, translateY, width, height }: {
|
||||
node: any;
|
||||
btn: any;
|
||||
@@ -20,11 +29,33 @@ declare namespace _default {
|
||||
maxy: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function renderLine({ node, line, top, x, lineLength, height, expandBtnSize, maxy, ctx }: {
|
||||
node: any;
|
||||
line: any;
|
||||
top: any;
|
||||
x: any;
|
||||
lineLength: any;
|
||||
height: any;
|
||||
expandBtnSize: any;
|
||||
maxy: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function computedLeftTopValue({ layerIndex, node, ctx }: {
|
||||
layerIndex: any;
|
||||
node: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function computedLeftTopValue({ layerIndex, node, ctx }: {
|
||||
layerIndex: any;
|
||||
node: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function adjustLeftTopValueBefore({ node, parent, ctx, layerIndex }: {
|
||||
node: any;
|
||||
parent: any;
|
||||
ctx: any;
|
||||
layerIndex: any;
|
||||
}): void;
|
||||
function adjustLeftTopValueBefore({ node, parent, ctx, layerIndex }: {
|
||||
node: any;
|
||||
parent: any;
|
||||
@@ -36,8 +67,22 @@ declare namespace _default {
|
||||
node: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function adjustLeftTopValueAfter({ parent, node, ctx }: {
|
||||
parent: any;
|
||||
node: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
}
|
||||
namespace bottom {
|
||||
function renderExpandBtn({ node, btn, expandBtnSize, translateX, translateY, width, height }: {
|
||||
node: any;
|
||||
btn: any;
|
||||
expandBtnSize: any;
|
||||
translateX: any;
|
||||
translateY: any;
|
||||
width: any;
|
||||
height: any;
|
||||
}): void;
|
||||
function renderExpandBtn({ node, btn, expandBtnSize, translateX, translateY, width, height }: {
|
||||
node: any;
|
||||
btn: any;
|
||||
@@ -57,11 +102,31 @@ declare namespace _default {
|
||||
miny: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function renderLine({ node, line, top, x, lineLength, height, miny, ctx }: {
|
||||
node: any;
|
||||
line: any;
|
||||
top: any;
|
||||
x: any;
|
||||
lineLength: any;
|
||||
height: any;
|
||||
miny: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function computedLeftTopValue({ layerIndex, node, ctx }: {
|
||||
layerIndex: any;
|
||||
node: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function computedLeftTopValue({ layerIndex, node, ctx }: {
|
||||
layerIndex: any;
|
||||
node: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function adjustLeftTopValueBefore({ node, ctx, layerIndex }: {
|
||||
node: any;
|
||||
ctx: any;
|
||||
layerIndex: any;
|
||||
}): void;
|
||||
function adjustLeftTopValueBefore({ node, ctx, layerIndex }: {
|
||||
node: any;
|
||||
ctx: any;
|
||||
@@ -72,6 +137,11 @@ declare namespace _default {
|
||||
node: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
function adjustLeftTopValueAfter({ parent, node, ctx }: {
|
||||
parent: any;
|
||||
node: any;
|
||||
ctx: any;
|
||||
}): void;
|
||||
}
|
||||
}
|
||||
export default _default;
|
||||
|
||||
172
simple-mind-map/types/src/themes/default.d.ts
vendored
@@ -1,139 +1,139 @@
|
||||
declare namespace _default {
|
||||
let paddingX: number;
|
||||
let paddingY: number;
|
||||
let imgMaxWidth: number;
|
||||
let imgMaxHeight: number;
|
||||
let iconSize: number;
|
||||
let lineWidth: number;
|
||||
let lineColor: string;
|
||||
let lineDasharray: string;
|
||||
let lineStyle: string;
|
||||
let rootLineKeepSameInCurve: boolean;
|
||||
let generalizationLineWidth: number;
|
||||
let generalizationLineColor: string;
|
||||
let generalizationLineMargin: number;
|
||||
let generalizationNodeMargin: number;
|
||||
let associativeLineWidth: number;
|
||||
let associativeLineColor: string;
|
||||
let associativeLineActiveWidth: number;
|
||||
let associativeLineActiveColor: string;
|
||||
let associativeLineTextColor: string;
|
||||
let associativeLineTextFontSize: number;
|
||||
let associativeLineTextLineHeight: number;
|
||||
let associativeLineTextFontFamily: string;
|
||||
let backgroundColor: string;
|
||||
let backgroundImage: string;
|
||||
let backgroundRepeat: string;
|
||||
let backgroundPosition: string;
|
||||
let backgroundSize: string;
|
||||
let nodeUseLineStyle: boolean;
|
||||
const paddingX: number;
|
||||
const paddingY: number;
|
||||
const imgMaxWidth: number;
|
||||
const imgMaxHeight: number;
|
||||
const iconSize: number;
|
||||
const lineWidth: number;
|
||||
const lineColor: string;
|
||||
const lineDasharray: string;
|
||||
const lineStyle: string;
|
||||
const rootLineKeepSameInCurve: boolean;
|
||||
const generalizationLineWidth: number;
|
||||
const generalizationLineColor: string;
|
||||
const generalizationLineMargin: number;
|
||||
const generalizationNodeMargin: number;
|
||||
const associativeLineWidth: number;
|
||||
const associativeLineColor: string;
|
||||
const associativeLineActiveWidth: number;
|
||||
const associativeLineActiveColor: string;
|
||||
const associativeLineTextColor: string;
|
||||
const associativeLineTextFontSize: number;
|
||||
const associativeLineTextLineHeight: number;
|
||||
const associativeLineTextFontFamily: string;
|
||||
const backgroundColor: string;
|
||||
const backgroundImage: string;
|
||||
const backgroundRepeat: string;
|
||||
const backgroundPosition: string;
|
||||
const backgroundSize: string;
|
||||
const nodeUseLineStyle: boolean;
|
||||
namespace root {
|
||||
let shape: string;
|
||||
let fillColor: string;
|
||||
let fontFamily: string;
|
||||
let color: string;
|
||||
let fontSize: number;
|
||||
let fontWeight: string;
|
||||
let fontStyle: string;
|
||||
let lineHeight: number;
|
||||
let borderColor: string;
|
||||
let borderWidth: number;
|
||||
let borderDasharray: string;
|
||||
let borderRadius: number;
|
||||
let textDecoration: string;
|
||||
const shape: string;
|
||||
const fillColor: string;
|
||||
const fontFamily: string;
|
||||
const color: string;
|
||||
const fontSize: number;
|
||||
const fontWeight: string;
|
||||
const fontStyle: string;
|
||||
const lineHeight: number;
|
||||
const borderColor: string;
|
||||
const borderWidth: number;
|
||||
const borderDasharray: string;
|
||||
const borderRadius: number;
|
||||
const textDecoration: string;
|
||||
}
|
||||
namespace second {
|
||||
let shape_1: string;
|
||||
const shape_1: string;
|
||||
export { shape_1 as shape };
|
||||
export let marginX: number;
|
||||
export let marginY: number;
|
||||
let fillColor_1: string;
|
||||
export const marginX: number;
|
||||
export const marginY: number;
|
||||
const fillColor_1: string;
|
||||
export { fillColor_1 as fillColor };
|
||||
let fontFamily_1: string;
|
||||
const fontFamily_1: string;
|
||||
export { fontFamily_1 as fontFamily };
|
||||
let color_1: string;
|
||||
const color_1: string;
|
||||
export { color_1 as color };
|
||||
let fontSize_1: number;
|
||||
const fontSize_1: number;
|
||||
export { fontSize_1 as fontSize };
|
||||
let fontWeight_1: string;
|
||||
const fontWeight_1: string;
|
||||
export { fontWeight_1 as fontWeight };
|
||||
let fontStyle_1: string;
|
||||
const fontStyle_1: string;
|
||||
export { fontStyle_1 as fontStyle };
|
||||
let lineHeight_1: number;
|
||||
const lineHeight_1: number;
|
||||
export { lineHeight_1 as lineHeight };
|
||||
let borderColor_1: string;
|
||||
const borderColor_1: string;
|
||||
export { borderColor_1 as borderColor };
|
||||
let borderWidth_1: number;
|
||||
const borderWidth_1: number;
|
||||
export { borderWidth_1 as borderWidth };
|
||||
let borderDasharray_1: string;
|
||||
const borderDasharray_1: string;
|
||||
export { borderDasharray_1 as borderDasharray };
|
||||
let borderRadius_1: number;
|
||||
const borderRadius_1: number;
|
||||
export { borderRadius_1 as borderRadius };
|
||||
let textDecoration_1: string;
|
||||
const textDecoration_1: string;
|
||||
export { textDecoration_1 as textDecoration };
|
||||
}
|
||||
namespace node {
|
||||
let shape_2: string;
|
||||
const shape_2: string;
|
||||
export { shape_2 as shape };
|
||||
let marginX_1: number;
|
||||
const marginX_1: number;
|
||||
export { marginX_1 as marginX };
|
||||
let marginY_1: number;
|
||||
const marginY_1: number;
|
||||
export { marginY_1 as marginY };
|
||||
let fillColor_2: string;
|
||||
const fillColor_2: string;
|
||||
export { fillColor_2 as fillColor };
|
||||
let fontFamily_2: string;
|
||||
const fontFamily_2: string;
|
||||
export { fontFamily_2 as fontFamily };
|
||||
let color_2: string;
|
||||
const color_2: string;
|
||||
export { color_2 as color };
|
||||
let fontSize_2: number;
|
||||
const fontSize_2: number;
|
||||
export { fontSize_2 as fontSize };
|
||||
let fontWeight_2: string;
|
||||
const fontWeight_2: string;
|
||||
export { fontWeight_2 as fontWeight };
|
||||
let fontStyle_2: string;
|
||||
const fontStyle_2: string;
|
||||
export { fontStyle_2 as fontStyle };
|
||||
let lineHeight_2: number;
|
||||
const lineHeight_2: number;
|
||||
export { lineHeight_2 as lineHeight };
|
||||
let borderColor_2: string;
|
||||
const borderColor_2: string;
|
||||
export { borderColor_2 as borderColor };
|
||||
let borderWidth_2: number;
|
||||
const borderWidth_2: number;
|
||||
export { borderWidth_2 as borderWidth };
|
||||
let borderRadius_2: number;
|
||||
const borderRadius_2: number;
|
||||
export { borderRadius_2 as borderRadius };
|
||||
let borderDasharray_2: string;
|
||||
const borderDasharray_2: string;
|
||||
export { borderDasharray_2 as borderDasharray };
|
||||
let textDecoration_2: string;
|
||||
const textDecoration_2: string;
|
||||
export { textDecoration_2 as textDecoration };
|
||||
}
|
||||
namespace generalization {
|
||||
let shape_3: string;
|
||||
const shape_3: string;
|
||||
export { shape_3 as shape };
|
||||
let marginX_2: number;
|
||||
const marginX_2: number;
|
||||
export { marginX_2 as marginX };
|
||||
let marginY_2: number;
|
||||
const marginY_2: number;
|
||||
export { marginY_2 as marginY };
|
||||
let fillColor_3: string;
|
||||
const fillColor_3: string;
|
||||
export { fillColor_3 as fillColor };
|
||||
let fontFamily_3: string;
|
||||
const fontFamily_3: string;
|
||||
export { fontFamily_3 as fontFamily };
|
||||
let color_3: string;
|
||||
const color_3: string;
|
||||
export { color_3 as color };
|
||||
let fontSize_3: number;
|
||||
const fontSize_3: number;
|
||||
export { fontSize_3 as fontSize };
|
||||
let fontWeight_3: string;
|
||||
const fontWeight_3: string;
|
||||
export { fontWeight_3 as fontWeight };
|
||||
let fontStyle_3: string;
|
||||
const fontStyle_3: string;
|
||||
export { fontStyle_3 as fontStyle };
|
||||
let lineHeight_3: number;
|
||||
const lineHeight_3: number;
|
||||
export { lineHeight_3 as lineHeight };
|
||||
let borderColor_3: string;
|
||||
const borderColor_3: string;
|
||||
export { borderColor_3 as borderColor };
|
||||
let borderWidth_3: number;
|
||||
const borderWidth_3: number;
|
||||
export { borderWidth_3 as borderWidth };
|
||||
let borderDasharray_3: string;
|
||||
const borderDasharray_3: string;
|
||||
export { borderDasharray_3 as borderDasharray };
|
||||
let borderRadius_3: number;
|
||||
const borderRadius_3: number;
|
||||
export { borderRadius_3 as borderRadius };
|
||||
let textDecoration_3: string;
|
||||
const textDecoration_3: string;
|
||||
export { textDecoration_3 as textDecoration };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ declare class BatchExecution {
|
||||
queue: any[];
|
||||
nextTick: any;
|
||||
push(name: any, fn: any): void;
|
||||
replaceTask(name: any, fn: any): void;
|
||||
flush(): void;
|
||||
}
|
||||
|
||||
18
simple-mind-map/types/src/utils/index.d.ts
vendored
@@ -6,7 +6,7 @@ export function resizeImg(imgUrl: any, maxWidth: any, maxHeight: any): Promise<a
|
||||
export function getStrWithBrFromHtml(str: any): any;
|
||||
export function simpleDeepClone(data: any): any;
|
||||
export function copyRenderTree(tree: any, root: any, removeActiveState?: boolean): any;
|
||||
export function copyNodeTree(tree: any, root: any, removeActiveState?: boolean, keepId?: boolean): any;
|
||||
export function copyNodeTree(tree: any, root: any, removeActiveState?: boolean, removeId?: boolean): any;
|
||||
export function imgToDataUrl(src: any): Promise<any>;
|
||||
export function parseDataUrl(data: any): any;
|
||||
export function downloadFile(file: any, fileName: any): void;
|
||||
@@ -58,12 +58,24 @@ export function getObjectChangedProps(oldObject: any, newObject: any): {};
|
||||
export function checkIsNodeStyleDataKey(key: any): boolean;
|
||||
export function mergerIconList(list: any): any;
|
||||
export function getTopAncestorsFomNodeList(list: any): any[];
|
||||
export function checkHasSupSubRelation(list: any): boolean;
|
||||
export function parseAddGeneralizationNodeList(list: any): any[];
|
||||
export function checkTwoRectIsOverlap(minx1: any, maxx1: any, miny1: any, maxy1: any, minx2: any, maxx2: any, miny2: any, maxy2: any): boolean;
|
||||
export function focusInput(el: any): void;
|
||||
export function selectAllInput(el: any): void;
|
||||
export function addDataToAppointNodes(appointNodes: any, data?: {}): any;
|
||||
export function createUidForAppointNodes(appointNodes: any): any;
|
||||
export function createUidForAppointNodes(appointNodes: any, createNewId?: boolean): any;
|
||||
export function formatDataToArray(data: any): any[];
|
||||
export function getNodeIndex(node: any): any;
|
||||
export function getNodeDataIndex(node: any): any;
|
||||
export function getNodeIndexInNodeList(node: any, nodeList: any): any;
|
||||
export function generateColorByContent(str: any): string;
|
||||
export function htmlEscape(str: any): any;
|
||||
export function isSameObject(a: any, b: any): boolean;
|
||||
export function setDataToClipboard(data: any): void;
|
||||
export function getDataFromClipboard(): Promise<{
|
||||
text: string;
|
||||
img: any;
|
||||
}>;
|
||||
export function removeFromParentNodeData(node: any): void;
|
||||
export function handleSelfCloseTags(str: any): any;
|
||||
export function checkNodeListIsEqual(list1: any, list2: any): boolean;
|
||||
|
||||
2
web/package-lock.json
generated
@@ -18488,6 +18488,7 @@
|
||||
"integrity": "sha512-VCNRiAt2P/bLo09rYt3DLe6xXUMlhJwrvU18Ddd/lYJgC7s8+wvhgYs+MTx4OiAXdu58drGwSBO9SPx7C6J82Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/core": "^7.11.0",
|
||||
"@babel/helper-compilation-targets": "^7.9.6",
|
||||
"@babel/helper-module-imports": "^7.8.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
@@ -18500,6 +18501,7 @@
|
||||
"@vue/babel-plugin-jsx": "^1.0.3",
|
||||
"@vue/babel-preset-jsx": "^1.2.4",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"core-js": "^3.6.5",
|
||||
"core-js-compat": "^3.6.5",
|
||||
"semver": "^6.1.0"
|
||||
}
|
||||
|
||||
BIN
web/src/assets/avatar/moom.jpg
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
web/src/assets/avatar/天清如愿.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
web/src/assets/avatar/小逗比.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
web/src/assets/avatar/张扬.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
web/src/assets/avatar/戚永峰.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
web/src/assets/avatar/敬明朗.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
web/src/assets/avatar/有希.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
web/src/assets/avatar/樊笼.jpg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
web/src/assets/avatar/达仁科技.jpg
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1695365666344') format('woff2'),
|
||||
url('iconfont.woff?t=1695365666344') format('woff'),
|
||||
url('iconfont.ttf?t=1695365666344') format('truetype');
|
||||
src: url('iconfont.woff2?t=1697073602349') format('woff2'),
|
||||
url('iconfont.woff?t=1697073602349') format('woff'),
|
||||
url('iconfont.ttf?t=1697073602349') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,10 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icondodeparent:before {
|
||||
content: "\e70f";
|
||||
}
|
||||
|
||||
.icongongshi:before {
|
||||
content: "\e617";
|
||||
}
|
||||
|
||||
@@ -209,6 +209,11 @@ export const shortcutKeyList = [
|
||||
name: 'Insert sibling node',
|
||||
value: 'Enter'
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: 'Insert parent node',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconshangyi',
|
||||
name: 'Move up node',
|
||||
@@ -234,6 +239,11 @@ export const shortcutKeyList = [
|
||||
name: 'Delete node',
|
||||
value: 'Delete | Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: 'Delete current node',
|
||||
value: 'Shift + Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconfuzhi',
|
||||
name: 'Copy node',
|
||||
@@ -304,9 +314,14 @@ export const shortcutKeyList = [
|
||||
name: 'Zoom out',
|
||||
value: 'Ctrl + -'
|
||||
},
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: 'Zoom in/Zoom out',
|
||||
value: 'Ctrl + Mouse wheel'
|
||||
},
|
||||
{
|
||||
icon: 'icondingwei',
|
||||
name: 'Reset',
|
||||
name: 'Back root node',
|
||||
value: 'Ctrl + Enter'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -276,6 +276,11 @@ export const shortcutKeyList = [
|
||||
name: '插入同级节点',
|
||||
value: 'Enter'
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: '插入父节点',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconshangyi',
|
||||
name: '上移节点',
|
||||
@@ -301,6 +306,11 @@ export const shortcutKeyList = [
|
||||
name: '删除节点',
|
||||
value: 'Delete | Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: '仅删除当前节点',
|
||||
value: 'Shift + Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconfuzhi',
|
||||
name: '复制节点',
|
||||
@@ -371,9 +381,14 @@ export const shortcutKeyList = [
|
||||
name: '缩小',
|
||||
value: 'Ctrl + -'
|
||||
},
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: '放大/缩小',
|
||||
value: 'Ctrl + 鼠标滚动'
|
||||
},
|
||||
{
|
||||
icon: 'icondingwei',
|
||||
name: '恢复默认',
|
||||
name: '回到根节点',
|
||||
value: 'Ctrl + Enter'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -47,6 +47,10 @@ export default {
|
||||
mousewheelZoomActionReverse: 'Mouse Wheel Zoom',
|
||||
mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back',
|
||||
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back',
|
||||
createNewNodeBehavior: 'Behavior of creating new node',
|
||||
default: 'Active new node and editing',
|
||||
notActive: 'Not active new node',
|
||||
activeOnly: 'Only active new node but not editing',
|
||||
rootStyle: 'Root Node',
|
||||
associativeLineText: 'Associative line text',
|
||||
fontFamily: 'Font family',
|
||||
@@ -59,14 +63,16 @@ export default {
|
||||
contextmenu: {
|
||||
insertSiblingNode: 'Insert sibling node',
|
||||
insertChildNode: 'Insert child node',
|
||||
insertParentNode: 'Insert parent node',
|
||||
insertSummary: 'Insert summary',
|
||||
moveUpNode: 'Move up node',
|
||||
moveDownNode: 'Move down node',
|
||||
deleteNode: 'Delete node',
|
||||
deleteCurrentNode: 'Only del cur node',
|
||||
copyNode: 'Copy node',
|
||||
cutNode: 'Cut node',
|
||||
pasteNode: 'Paste node',
|
||||
backCenter: 'Back center',
|
||||
backCenter: 'Back root node',
|
||||
expandAll: 'Expand all',
|
||||
unExpandAll: 'Un expand all',
|
||||
expandTo: 'Expand to',
|
||||
@@ -112,7 +118,8 @@ export default {
|
||||
'If the download is not triggered, check whether it is blocked by the browser',
|
||||
paddingX: 'Padding x',
|
||||
paddingY: 'Padding y',
|
||||
useMultiPageExport: 'Export multi page'
|
||||
useMultiPageExport: 'Export multi page',
|
||||
defaultFileName: 'Mind map'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: 'Full screen show',
|
||||
@@ -121,7 +128,13 @@ export default {
|
||||
import: {
|
||||
title: 'Import',
|
||||
selectFile: 'Select file',
|
||||
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file'
|
||||
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file',
|
||||
enableFileTip: 'Please select .smm、.json、.xmind、.xlsx、.md file',
|
||||
maxFileNum: 'At most one file can be selected',
|
||||
notSelectTip: 'Please select the file to import',
|
||||
fileContentError: 'The file content is incorrect',
|
||||
importSuccess: 'Import success',
|
||||
fileParsingFailed: 'File parsing failed'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: 'Open mini map',
|
||||
@@ -190,13 +203,21 @@ export default {
|
||||
vertical: 'Vertical'
|
||||
},
|
||||
theme: {
|
||||
title: 'Theme'
|
||||
title: 'Theme',
|
||||
classics: 'Classics',
|
||||
dark: 'Darkness',
|
||||
simple: 'Simple',
|
||||
coverTip:
|
||||
'You have currently customized the basic style, do you want to overwrite it?',
|
||||
tip: 'Tip',
|
||||
cover: 'Cover',
|
||||
reserve: 'Reserve'
|
||||
},
|
||||
toolbar: {
|
||||
undo: 'Undo',
|
||||
redo: 'Redo',
|
||||
insertSiblingNode: 'Insert sibling node',
|
||||
insertChildNode: 'Insert child node',
|
||||
insertSiblingNode: 'Sibling node',
|
||||
insertChildNode: 'Child node',
|
||||
deleteNode: 'Delete node',
|
||||
image: 'Image',
|
||||
icon: 'Icon',
|
||||
@@ -216,12 +237,28 @@ export default {
|
||||
shortcutKey: 'Shortcut key',
|
||||
associativeLine: 'Associative line',
|
||||
painter: 'Painter',
|
||||
formula: 'Formula'
|
||||
formula: 'Formula',
|
||||
more: 'More',
|
||||
selectFileTip: 'Please select a file',
|
||||
notSupportTip:
|
||||
'Your browser does not support this feature, or the current page is not using the HTTPS protocol',
|
||||
tip: 'Tip',
|
||||
editingLocalFileTipFront: 'Currently editing your local【',
|
||||
editingLocalFileTipEnd: '】file',
|
||||
fileContentError: 'File content error',
|
||||
fileOpenFailed: 'File open failed',
|
||||
defaultFileName: 'Mind map',
|
||||
creatingTip: 'Creating file'
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: 'New feature reminder',
|
||||
newFeatureNoticeMessage:
|
||||
'This update supports node rich text editing, But there are some defects, The most important impact is that the time to export the image is proportional to the number of nodes, Therefore, if you are more dependent on export requirements, you can use【Base style】-【Other config】-【Enable node rich text editing】Set to turn off rich text editing mode.'
|
||||
'This update supports node rich text editing, But there are some defects, The most important impact is that the time to export the image is proportional to the number of nodes, Therefore, if you are more dependent on export requirements, you can use【Base style】-【Other config】-【Enable node rich text editing】Set to turn off rich text editing mode.',
|
||||
root: 'Root node',
|
||||
splitByWrap: 'Is automatically split nodes based on line breaks?',
|
||||
tip: 'Tip',
|
||||
yes: 'Yes',
|
||||
no: 'No'
|
||||
},
|
||||
mouseAction: {
|
||||
tip1:
|
||||
@@ -243,8 +280,23 @@ export default {
|
||||
},
|
||||
formulaSidebar: {
|
||||
title: 'Formula',
|
||||
placeholder: 'Please enter LaText syntax',
|
||||
placeholder: 'Please enter LaTeX syntax',
|
||||
confirm: 'Confirm',
|
||||
common: 'Common formulas'
|
||||
common: 'Common formulas',
|
||||
tip: 'Inserting formulas is not supported in non rich text mode'
|
||||
},
|
||||
richTextToolbar: {
|
||||
bold: 'Bold',
|
||||
italic: 'Italic',
|
||||
underline: 'Underline',
|
||||
strike: 'Strike',
|
||||
fontFamily: 'Font family',
|
||||
fontSize: 'Font size',
|
||||
color: 'Color',
|
||||
backgroundColor: 'Background color',
|
||||
removeFormat: 'Clear Style'
|
||||
},
|
||||
other: {
|
||||
loading: 'Loading, please wait...'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ export default {
|
||||
mousewheelZoomActionReverse: '鼠标滚轮缩放',
|
||||
mousewheelZoomActionReverse1: '向前缩小向后放大',
|
||||
mousewheelZoomActionReverse2: '向前放大向后缩小',
|
||||
createNewNodeBehavior: '创建新节点的行为',
|
||||
default: '激活新节点及进入编辑',
|
||||
notActive: '不激活新节点',
|
||||
activeOnly: '只激活新节点,不进入编辑',
|
||||
rootStyle: '根节点',
|
||||
associativeLineText: '关联线文字',
|
||||
fontFamily: '字体',
|
||||
@@ -59,14 +63,16 @@ export default {
|
||||
contextmenu: {
|
||||
insertSiblingNode: '插入同级节点',
|
||||
insertChildNode: '插入子级节点',
|
||||
insertParentNode: '插入父节点',
|
||||
insertSummary: '插入概要',
|
||||
moveUpNode: '上移节点',
|
||||
moveDownNode: '下移节点',
|
||||
deleteNode: '删除节点',
|
||||
deleteCurrentNode: '仅删除当前节点',
|
||||
copyNode: '复制节点',
|
||||
cutNode: '剪切节点',
|
||||
pasteNode: '粘贴节点',
|
||||
backCenter: '回到中心',
|
||||
backCenter: '回到根节点',
|
||||
expandAll: '展开所有',
|
||||
unExpandAll: '收起所有',
|
||||
expandTo: '展开到',
|
||||
@@ -110,7 +116,8 @@ export default {
|
||||
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了',
|
||||
paddingX: '水平内边距',
|
||||
paddingY: '垂直内边距',
|
||||
useMultiPageExport: '是否多页导出'
|
||||
useMultiPageExport: '是否多页导出',
|
||||
defaultFileName: '思维导图'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: '全屏查看',
|
||||
@@ -119,7 +126,13 @@ export default {
|
||||
import: {
|
||||
title: '导入',
|
||||
selectFile: '选取文件',
|
||||
supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件'
|
||||
supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件',
|
||||
enableFileTip: '请选择.smm、.json、.xmind、.xlsx、.md文件',
|
||||
maxFileNum: '最多只能选择一个文件',
|
||||
notSelectTip: '请选择要导入的文件',
|
||||
fileContentError: '文件内容有误',
|
||||
importSuccess: '导入成功',
|
||||
fileParsingFailed: '文件解析失败'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: '开启小地图',
|
||||
@@ -188,13 +201,20 @@ export default {
|
||||
vertical: '垂直'
|
||||
},
|
||||
theme: {
|
||||
title: '主题'
|
||||
title: '主题',
|
||||
classics: '经典',
|
||||
dark: '深色',
|
||||
simple: '朴素',
|
||||
coverTip: '你当前自定义过基础样式,是否覆盖?',
|
||||
tip: '提示',
|
||||
cover: '覆盖',
|
||||
reserve: '保留'
|
||||
},
|
||||
toolbar: {
|
||||
undo: '回退',
|
||||
redo: '前进',
|
||||
insertSiblingNode: '插入同级节点',
|
||||
insertChildNode: '插入子节点',
|
||||
insertSiblingNode: '同级节点',
|
||||
insertChildNode: '子节点',
|
||||
deleteNode: '删除节点',
|
||||
image: '图片',
|
||||
icon: '图标',
|
||||
@@ -214,12 +234,27 @@ export default {
|
||||
shortcutKey: '快捷键',
|
||||
associativeLine: '关联线',
|
||||
painter: '格式刷',
|
||||
formula: '公式'
|
||||
formula: '公式',
|
||||
more: '更多',
|
||||
selectFileTip: '请选择文件',
|
||||
notSupportTip: '你的浏览器不支持该功能,或者当前页面非https协议',
|
||||
tip: '提示',
|
||||
editingLocalFileTipFront: '当前正在编辑你本机的【',
|
||||
editingLocalFileTipEnd: '】文件',
|
||||
fileContentError: '文件内容有误',
|
||||
fileOpenFailed: '文件打开失败',
|
||||
defaultFileName: '思维导图',
|
||||
creatingTip: '正在创建文件'
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: '新特性提醒',
|
||||
newFeatureNoticeMessage:
|
||||
'本次更新支持了节点富文本编辑,但是存在一定缺陷,最主要的影响是导出为图片的时间和节点数量成正比,所以对导出需求比较依赖的话可以通过【基础样式】-【其他配置】-【是否开启节点富文本编辑】设置关掉富文本编辑模式。'
|
||||
'本次更新支持了节点富文本编辑,但是存在一定缺陷,最主要的影响是导出为图片的时间和节点数量成正比,所以对导出需求比较依赖的话可以通过【基础样式】-【其他配置】-【是否开启节点富文本编辑】设置关掉富文本编辑模式。',
|
||||
root: '根节点',
|
||||
splitByWrap: '是否按换行自动分割节点?',
|
||||
tip: '提示',
|
||||
yes: '是',
|
||||
no: '否'
|
||||
},
|
||||
mouseAction: {
|
||||
tip1: '当前:左键拖动画布,右键框选节点',
|
||||
@@ -239,8 +274,23 @@ export default {
|
||||
},
|
||||
formulaSidebar: {
|
||||
title: '公式',
|
||||
placeholder: '请输入 LaText 语法',
|
||||
placeholder: '请输入 LaTeX 语法',
|
||||
confirm: '完成',
|
||||
common: '常用公式'
|
||||
common: '常用公式',
|
||||
tip: '非富文本模式下不支持插入公式'
|
||||
},
|
||||
richTextToolbar: {
|
||||
bold: '加粗',
|
||||
italic: '斜体',
|
||||
underline: '下划线',
|
||||
strike: '删除线',
|
||||
fontFamily: '字体',
|
||||
fontSize: '字号',
|
||||
color: '字体颜色',
|
||||
backgroundColor: '背景颜色',
|
||||
removeFormat: '清除样式'
|
||||
},
|
||||
other: {
|
||||
loading: '正在加载,请稍后...'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ let langList = [
|
||||
}
|
||||
]
|
||||
let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog']
|
||||
let CourseList = new Array(24).fill(0).map((_, index) => {
|
||||
let CourseList = new Array(25).fill(0).map((_, index) => {
|
||||
return 'course' + (index + 1)
|
||||
})
|
||||
let APIList = [
|
||||
@@ -36,6 +36,7 @@ let APIList = [
|
||||
'painter',
|
||||
'scrollbar',
|
||||
'formula',
|
||||
'cooperate',
|
||||
'xmind',
|
||||
'markdown',
|
||||
'utils'
|
||||
|
||||
@@ -88,3 +88,15 @@ Deletes the currently active associative line. Clicking on an associated line is
|
||||
### clearActiveLine()
|
||||
|
||||
Clears the active state of the currently active association line.
|
||||
|
||||
### front()
|
||||
|
||||
> v0.8.0+
|
||||
|
||||
The top-level display of the associated line.
|
||||
|
||||
### back()
|
||||
|
||||
> v0.8.0+
|
||||
|
||||
The associated line returns to its original level.
|
||||
@@ -66,6 +66,16 @@ MindMap.usePlugin(AssociativeLine)
|
||||
<p>Deletes the currently active associative line. Clicking on an associated line is considered active.</p>
|
||||
<h3>clearActiveLine()</h3>
|
||||
<p>Clears the active state of the currently active association line.</p>
|
||||
<h3>front()</h3>
|
||||
<blockquote>
|
||||
<p>v0.8.0+</p>
|
||||
</blockquote>
|
||||
<p>The top-level display of the associated line.</p>
|
||||
<h3>back()</h3>
|
||||
<blockquote>
|
||||
<p>v0.8.0+</p>
|
||||
</blockquote>
|
||||
<p>The associated line returns to its original level.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,180 @@
|
||||
# Changelog
|
||||
|
||||
## 0.9.1
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Fix the issue of exporting images, SVGs, and PDFs with errors when customizing node content.
|
||||
>
|
||||
> 2.Optimize the distribution of node activation events, do not distribute events when the activation node has not changed, and skip intermediate events when distributing multiple events in a short period of time.
|
||||
>
|
||||
> 3.Fix the issue where the edit box and node detach when scrolling the canvas with the mouse while the node is in editing mode.
|
||||
>
|
||||
> 4.Fix the issue of shortcut keys becoming invalid when zooming the canvas with the mouse wheel and then exiting node editing while in node editing mode.
|
||||
>
|
||||
> 5.Fix the issue where clicking on a node can also trigger node_dragend event.
|
||||
>
|
||||
> 6.Fix that clicking on the canvas and nodes while not in the format brush will also trigger the painter_end event.
|
||||
>
|
||||
> 7.Fixed the issue where the mind map text editing box was not destroyed during node text editing and associated line text editing.
|
||||
|
||||
New:
|
||||
|
||||
> 1.When holding down the Ctrl key, disable the node double-click event.
|
||||
>
|
||||
> 2.Support configuring the behavior when creating new nodes: focusing and entering editing, not focusing, only focusing.
|
||||
>
|
||||
> 3.When searching in read-only mode, add a highlight effect to the currently matched node.
|
||||
>
|
||||
> 4.The default behavior of the mouse scroll wheel is to move the canvas up and down; The default is to scroll forward to enlarge the canvas and zoom back.
|
||||
>
|
||||
> 5.When the mouse scroll wheel behavior is to move the canvas up and down, it supports holding down the Ctrl key to zoom in and out of the canvas.
|
||||
|
||||
Demo:支持配置创建新节点时的行为。
|
||||
|
||||
## 0.9.0
|
||||
|
||||
New:
|
||||
|
||||
1.Support adding summaries to some child nodes of the same node.
|
||||
|
||||
2.Moving the mouse into the summary will highlight its node.
|
||||
|
||||
3.Importing and exporting xmind files supports processing profiles.
|
||||
|
||||
## 0.8.1
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Fix the issue where the activation status of the summary node in the history data has not been deleted, which can cause data to be triggered when clicking on the summary node_ Change event.
|
||||
>
|
||||
> 2.Fix the issue of blank pages and exceptions thrown by the console when running in Safari browser.
|
||||
>
|
||||
> 3.Fixed the issue of icon floating layer and note floating layer detached from nodes when scaling the canvas.
|
||||
>
|
||||
> 4.Fixed the issue of selecting all nodes in read-only mode.
|
||||
>
|
||||
> 5.Fix the presence of node content has in rich text mode; Error exporting as image.
|
||||
>
|
||||
> 6.Fixed the issue of overlapping profiles when adding profiles to oneself first and then to subordinates; Fix the issue of overlapping profiles when adding profiles to nodes with hierarchical relationships at the same time.
|
||||
>
|
||||
> 7.Fix the issue of exporting PDF errors when there are many nodes.
|
||||
|
||||
|
||||
New:
|
||||
|
||||
> 1.Add a configuration option that prohibits dragging the canvas.
|
||||
>
|
||||
> 2.Add a configuration option to prohibit double finger scaling of the canvas.
|
||||
>
|
||||
> 3.Add compression parameters to the method of exporting PNG; Optimize the problem of excessive volume when exporting PDF from nodes with large amounts of data.
|
||||
>
|
||||
> 4.Rename the isParent method of the node instance to isAncestor and add the isParent method at the same time.
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.Fixed the issue of being able to search for replacement and edit outlines in read-only mode.
|
||||
>
|
||||
> 2.Fix the issue where the outline cannot be displayed and edited when the node content is an HTML tag.
|
||||
>
|
||||
> 3.Fix the issue where when multiple nodes are selected and icons are added at the same time, all node icons will be unified as the icon of the first node.
|
||||
>
|
||||
> 4.Adding loading to the export operation.
|
||||
|
||||
## 0.8.0-fix.1
|
||||
|
||||
Fix: Fixed the issue of creating a new node using direct paste if the pasted content contains HTML label symbols such as <> and the newly created node content is empty.
|
||||
|
||||
## 0.8.0
|
||||
|
||||
Breaking change: Greatly optimize some of the code and slightly improve performance, mainly by using the 'render' class to remove useless logic, adjust unreasonable implementations, and extract duplicate code; Modify function names, functions, etc.
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Fix the issue of the arrow of the associated line disappearing when exporting images and SVGs.
|
||||
>
|
||||
> 2.Fix the issue of abnormal operation returning to the root node after resizing the container.
|
||||
>
|
||||
> 3.Fix that the shortcut key operations for inserting summary, moving up, down, and organizing layout with one click did not trigger data_ The issue with the change event.
|
||||
>
|
||||
> 4.Fix the issue of each node displaying a border when exporting images, SVGs, and PDFs with watermarks.
|
||||
>
|
||||
> 5.Fixed the issue of no watermarks and no redrawing after the container size was changed.
|
||||
>
|
||||
> 6.Fix the issue of slow rendering of mini maps with watermarks.
|
||||
>
|
||||
> 7.Fixed the issue where the collaboration plugin did not display the creator's avatar when creating a new node.
|
||||
|
||||
New:
|
||||
|
||||
> 1.Optimize the canvas DOM structure and render nodes, lines, and associated lines in layers.
|
||||
>
|
||||
> 2.Optimize the watermark plugin.
|
||||
>
|
||||
> 3.The setTheme, setThemeConfig, and setLayout functions add parameters that do not trigger re rendering.
|
||||
>
|
||||
> 4.Add a command to insert a parent node.
|
||||
>
|
||||
> 5.Add a command to only delete the current node.
|
||||
>
|
||||
> 6.Automatically expand child nodes when inserting a summary.
|
||||
>
|
||||
> 7.Clear the current active node when right-clicking on the canvas.
|
||||
>
|
||||
> 8.The folded active nodes are synchronously deleted from the list of active nodes.
|
||||
>
|
||||
> 9.Pasting text with line breaks supports controlling whether nodes are split by line breaks.
|
||||
>
|
||||
> 10.The mini map plugin supports returning mini maps of image types.
|
||||
>
|
||||
> 11.Only one historical record can be added within a specified time period to avoid adding unnecessary intermediate states.
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.Modify the method and copy to return to the root node.
|
||||
>
|
||||
> 2.Fix the issue of ineffective first switching when switching themes in overlay mode.
|
||||
>
|
||||
> 3.The right-click menu adds the function of inserting parent nodes and deleting only the current node.
|
||||
>
|
||||
> 4.The top toolbar supports automatic folding into more according to the window width.
|
||||
>
|
||||
> 5.Support manual input of zoom factor.
|
||||
>
|
||||
> 6.Improve the English translation of the interface.
|
||||
>
|
||||
> 7.Change the mini map to render through images.
|
||||
|
||||
## 0.7.3-fix.2
|
||||
|
||||
Fix some issues with collaborative editing:
|
||||
|
||||
1.The position of the new node is incorrect when inserting peer nodes;
|
||||
|
||||
2.Moving a position within a peer node did not trigger an update;
|
||||
|
||||
3.The position of the mobile node inserted as a sibling node is incorrect;
|
||||
|
||||
## 0.7.3-fix.1
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Fixed some issues where the box selection area did not disappear when multiple nodes were selected.
|
||||
>
|
||||
> 2.Fixed an issue where the box selection area does not disappear when releasing the mouse over multiple selected nodes.
|
||||
>
|
||||
> 3.Fixed rendering anomalies caused by duplicate node uids when pasting nodes multiple times.
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.Add protocol selection function to the hyperlink input box.
|
||||
|
||||
## 0.7.3
|
||||
|
||||
New: 1.Add a Cooperate editing plugin.
|
||||
|
||||
Demo: 1.Fix the automatic closing of the sidebar caused by the formula sidebar component.
|
||||
|
||||
## 0.7.2
|
||||
|
||||
Fix:
|
||||
|
||||
@@ -1,6 +1,113 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.9.1</h2>
|
||||
<p>Fix:</p>
|
||||
<blockquote>
|
||||
<p>1.Fix the issue of exporting images, SVGs, and PDFs with errors when customizing node content.</p>
|
||||
<p>2.Optimize the distribution of node activation events, do not distribute events when the activation node has not changed, and skip intermediate events when distributing multiple events in a short period of time.</p>
|
||||
<p>3.Fix the issue where the edit box and node detach when scrolling the canvas with the mouse while the node is in editing mode.</p>
|
||||
<p>4.Fix the issue of shortcut keys becoming invalid when zooming the canvas with the mouse wheel and then exiting node editing while in node editing mode.</p>
|
||||
<p>5.Fix the issue where clicking on a node can also trigger node_dragend event.</p>
|
||||
<p>6.Fix that clicking on the canvas and nodes while not in the format brush will also trigger the painter_end event.</p>
|
||||
<p>7.Fixed the issue where the mind map text editing box was not destroyed during node text editing and associated line text editing.</p>
|
||||
</blockquote>
|
||||
<p>New:</p>
|
||||
<blockquote>
|
||||
<p>1.When holding down the Ctrl key, disable the node double-click event.</p>
|
||||
<p>2.Support configuring the behavior when creating new nodes: focusing and entering editing, not focusing, only focusing.</p>
|
||||
<p>3.When searching in read-only mode, add a highlight effect to the currently matched node.</p>
|
||||
<p>4.The default behavior of the mouse scroll wheel is to move the canvas up and down; The default is to scroll forward to enlarge the canvas and zoom back.</p>
|
||||
<p>5.When the mouse scroll wheel behavior is to move the canvas up and down, it supports holding down the Ctrl key to zoom in and out of the canvas.</p>
|
||||
</blockquote>
|
||||
<p>Demo:支持配置创建新节点时的行为。</p>
|
||||
<h2>0.9.0</h2>
|
||||
<p>New:</p>
|
||||
<p>1.Support adding summaries to some child nodes of the same node.</p>
|
||||
<p>2.Moving the mouse into the summary will highlight its node.</p>
|
||||
<p>3.Importing and exporting xmind files supports processing profiles.</p>
|
||||
<h2>0.8.1</h2>
|
||||
<p>Fix:</p>
|
||||
<blockquote>
|
||||
<p>1.Fix the issue where the activation status of the summary node in the history data has not been deleted, which can cause data to be triggered when clicking on the summary node_ Change event.</p>
|
||||
<p>2.Fix the issue of blank pages and exceptions thrown by the console when running in Safari browser.</p>
|
||||
<p>3.Fixed the issue of icon floating layer and note floating layer detached from nodes when scaling the canvas.</p>
|
||||
<p>4.Fixed the issue of selecting all nodes in read-only mode.</p>
|
||||
<p>5.Fix the presence of node content has in rich text mode; Error exporting as image.</p>
|
||||
<p>6.Fixed the issue of overlapping profiles when adding profiles to oneself first and then to subordinates; Fix the issue of overlapping profiles when adding profiles to nodes with hierarchical relationships at the same time.</p>
|
||||
<p>7.Fix the issue of exporting PDF errors when there are many nodes.</p>
|
||||
</blockquote>
|
||||
<p>New:</p>
|
||||
<blockquote>
|
||||
<p>1.Add a configuration option that prohibits dragging the canvas.</p>
|
||||
<p>2.Add a configuration option to prohibit double finger scaling of the canvas.</p>
|
||||
<p>3.Add compression parameters to the method of exporting PNG; Optimize the problem of excessive volume when exporting PDF from nodes with large amounts of data.</p>
|
||||
<p>4.Rename the isParent method of the node instance to isAncestor and add the isParent method at the same time.</p>
|
||||
</blockquote>
|
||||
<p>Demo:</p>
|
||||
<blockquote>
|
||||
<p>1.Fixed the issue of being able to search for replacement and edit outlines in read-only mode.</p>
|
||||
<p>2.Fix the issue where the outline cannot be displayed and edited when the node content is an HTML tag.</p>
|
||||
<p>3.Fix the issue where when multiple nodes are selected and icons are added at the same time, all node icons will be unified as the icon of the first node.</p>
|
||||
<p>4.Adding loading to the export operation.</p>
|
||||
</blockquote>
|
||||
<h2>0.8.0-fix.1</h2>
|
||||
<p>Fix: Fixed the issue of creating a new node using direct paste if the pasted content contains HTML label symbols such as <> and the newly created node content is empty.</p>
|
||||
<h2>0.8.0</h2>
|
||||
<p>Breaking change: Greatly optimize some of the code and slightly improve performance, mainly by using the 'render' class to remove useless logic, adjust unreasonable implementations, and extract duplicate code; Modify function names, functions, etc.</p>
|
||||
<p>Fix:</p>
|
||||
<blockquote>
|
||||
<p>1.Fix the issue of the arrow of the associated line disappearing when exporting images and SVGs.</p>
|
||||
<p>2.Fix the issue of abnormal operation returning to the root node after resizing the container.</p>
|
||||
<p>3.Fix that the shortcut key operations for inserting summary, moving up, down, and organizing layout with one click did not trigger data_ The issue with the change event.</p>
|
||||
<p>4.Fix the issue of each node displaying a border when exporting images, SVGs, and PDFs with watermarks.</p>
|
||||
<p>5.Fixed the issue of no watermarks and no redrawing after the container size was changed.</p>
|
||||
<p>6.Fix the issue of slow rendering of mini maps with watermarks.</p>
|
||||
<p>7.Fixed the issue where the collaboration plugin did not display the creator's avatar when creating a new node.</p>
|
||||
</blockquote>
|
||||
<p>New:</p>
|
||||
<blockquote>
|
||||
<p>1.Optimize the canvas DOM structure and render nodes, lines, and associated lines in layers.</p>
|
||||
<p>2.Optimize the watermark plugin.</p>
|
||||
<p>3.The setTheme, setThemeConfig, and setLayout functions add parameters that do not trigger re rendering.</p>
|
||||
<p>4.Add a command to insert a parent node.</p>
|
||||
<p>5.Add a command to only delete the current node.</p>
|
||||
<p>6.Automatically expand child nodes when inserting a summary.</p>
|
||||
<p>7.Clear the current active node when right-clicking on the canvas.</p>
|
||||
<p>8.The folded active nodes are synchronously deleted from the list of active nodes.</p>
|
||||
<p>9.Pasting text with line breaks supports controlling whether nodes are split by line breaks.</p>
|
||||
<p>10.The mini map plugin supports returning mini maps of image types.</p>
|
||||
<p>11.Only one historical record can be added within a specified time period to avoid adding unnecessary intermediate states.</p>
|
||||
</blockquote>
|
||||
<p>Demo:</p>
|
||||
<blockquote>
|
||||
<p>1.Modify the method and copy to return to the root node.</p>
|
||||
<p>2.Fix the issue of ineffective first switching when switching themes in overlay mode.</p>
|
||||
<p>3.The right-click menu adds the function of inserting parent nodes and deleting only the current node.</p>
|
||||
<p>4.The top toolbar supports automatic folding into more according to the window width.</p>
|
||||
<p>5.Support manual input of zoom factor.</p>
|
||||
<p>6.Improve the English translation of the interface.</p>
|
||||
<p>7.Change the mini map to render through images.</p>
|
||||
</blockquote>
|
||||
<h2>0.7.3-fix.2</h2>
|
||||
<p>Fix some issues with collaborative editing:</p>
|
||||
<p>1.The position of the new node is incorrect when inserting peer nodes;</p>
|
||||
<p>2.Moving a position within a peer node did not trigger an update;</p>
|
||||
<p>3.The position of the mobile node inserted as a sibling node is incorrect;</p>
|
||||
<h2>0.7.3-fix.1</h2>
|
||||
<p>Fix:</p>
|
||||
<blockquote>
|
||||
<p>1.Fixed some issues where the box selection area did not disappear when multiple nodes were selected.</p>
|
||||
<p>2.Fixed an issue where the box selection area does not disappear when releasing the mouse over multiple selected nodes.</p>
|
||||
<p>3.Fixed rendering anomalies caused by duplicate node uids when pasting nodes multiple times.</p>
|
||||
</blockquote>
|
||||
<p>Demo:</p>
|
||||
<blockquote>
|
||||
<p>1.Add protocol selection function to the hyperlink input box.</p>
|
||||
</blockquote>
|
||||
<h2>0.7.3</h2>
|
||||
<p>New: 1.Add a Cooperate editing plugin.</p>
|
||||
<p>Demo: 1.Fix the automatic closing of the sidebar caused by the formula sidebar component.</p>
|
||||
<h2>0.7.2</h2>
|
||||
<p>Fix:</p>
|
||||
<blockquote>
|
||||
|
||||
@@ -37,15 +37,15 @@ const mindMap = new MindMap({
|
||||
| textContentMargin | Number | 2 | The spacing between various text information in the node, such as the spacing between the icon and text | |
|
||||
| selectTranslateStep | Number | 3 | The canvas offset when mouse moves to the edge during multi-select node | |
|
||||
| selectTranslateLimit | Number | 20 | The distance from the edge when the canvas begins to offset during multi-select node | |
|
||||
| customNoteContentShow(v0.1.6+) | Object | null | Custom node note content display, object type, structure: {show: (noteContent, left, top) => {// your display node note logic }, hide: () => {// your hide node note logic }} | |
|
||||
| customNoteContentShow(v0.1.6+) | Object | null | Custom node note content display, object type, structure: {show: (noteContent, left, top, node) => {// your display node note logic. node is a new parameter added in v0.8.1+ version, representing node instances }, hide: () => {// your hide node note logic }} | |
|
||||
| readonly(v0.1.7+) | Boolean | false | Whether it is read-only mode | |
|
||||
| enableFreeDrag(v0.2.4+) | Boolean | false | Enable node free drag | |
|
||||
| enableFreeDrag(v0.2.4+) | Boolean | false | Enable node free(Free drag means that nodes can be dragged to any position on the canvas. Please note that it is not a function of dragging nodes to become siblings of other nodes. The connection of free drag may have certain problems, so it is best not to use this feature) drag | |
|
||||
| watermarkConfig(v0.2.4+) | Object | | Watermark config, Please refer to the table 【Watermark config】 below for detailed configuration | |
|
||||
| textAutoWrapWidth(v0.3.4+) | Number | 500 | Each line of text in the node will wrap automatically when it reaches the width | |
|
||||
| customHandleMousewheel(v0.4.3+) | Function | null | User-defined mouse wheel event processing can pass a function, and the callback parameter is the event object | |
|
||||
| mousewheelAction(v0.4.3+) | String | zoom | The behavior of the mouse wheel, `zoom`(Zoom in and out)、`move`(Move up and down). If `customHandleMousewheel` passes a custom function, this property will not take effect | |
|
||||
| mousewheelAction(v0.4.3+) | String | zoom(v0.9.1+ default is move) | The behavior of the mouse wheel, `zoom`(Zoom in and out)、`move`(Move up and down). If `customHandleMousewheel` passes a custom function, this property will not take effect | |
|
||||
| mousewheelMoveStep(v0.4.3+) | Number | 100 | When the `mousewheelAction` is set to `move`, you can use this attribute to control the step length of the view movement when the mouse scrolls. The unit is `px` | |
|
||||
| mousewheelZoomActionReverse(v0.6.5+) | Boolean | false | When `mousewheelAction` is set to `zoom`, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed | |
|
||||
| mousewheelZoomActionReverse(v0.6.5+) | Boolean | false(v0.9.1+ default is true) | When `mousewheelAction` is set to `zoom`, Or when holding down the Ctrl key, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed | |
|
||||
| defaultInsertSecondLevelNodeText(v0.4.7+) | String | 二级节点 | Text of the default inserted secondary node | |
|
||||
| defaultInsertBelowSecondLevelNodeText(v0.4.7+) | String | 分支主题 | Text for nodes below the second level inserted by default | |
|
||||
| expandBtnStyle(v0.5.0+) | Object | { color: '#808080', fill: '#fff', fontSize: 13, strokeColor: '#333333' } | Expand the color of the stow button, (The fontSize and strokeColor fields were added in version 0.7.0+to set the text style for displaying the number of nodes when folded) | |
|
||||
@@ -82,7 +82,8 @@ const mindMap = new MindMap({
|
||||
| errorHandler(v0.6.15+) | Function | | Custom error handling functions currently only throw some asynchronous logic errors. Can pass a function that takes two parameters, the first being the wrong type and the second being the wrong object | |
|
||||
| disableMouseWheelZoom(v0.6.15+) | Boolean | false | Prohibit mouse wheel scaling, you can still use the API for scaling | |
|
||||
| resetCss(v0.6.16+) | String | * { margin: 0; padding: 0; box-sizing: border-box; } | When exporting images and SVGs, the default style overlay for rich text node content, which is embedded in HTML nodes in SVGs, will occur. If not overlaid, the node content will be offset | |
|
||||
| enableDblclickReset(v0.6.17+) | Boolean | true(v0.7.0+changed to false) | Turn on the mouse and double-click to reset the position and zoom of the mind map | |
|
||||
| enableDblclickReset(v0.6.17+)(v0.8.0+this attribute has been deleted) | Boolean | true(v0.7.0+changed to false) | Turn on the mouse and double-click to reset the position and zoom of the mind map | |
|
||||
| enableDblclickBackToRootNode(v0.8.0+) | Boolean | false | Whether to return to the root node when double clicking with the mouse, that is, to center the display of the root node | |
|
||||
| minExportImgCanvasScale(v0.7.0+) | Number | 2 | The scaling factor of canvas when exporting images and PDFs, which is set to the maximum value of window.devicePixelRatio to improve image clarity | |
|
||||
| hoverRectColor(v0.7.0+) | String | rgb(94, 200, 248) | The node mouse hover and the rectangular border color displayed when activated will add a transparency of 0.6 when hovering | |
|
||||
| hoverRectPadding(v0.7.0+) | Number | 2 | The distance between the node mouse hover and the displayed rectangular border when activated and the node content | |
|
||||
@@ -94,6 +95,15 @@ const mindMap = new MindMap({
|
||||
| dragPlaceholderRectFill(v0.7.2+) | String | | The filling color of the schematic rectangle for the new position when dragging nodes. If not transmitted, the default color for the connected line is used | |
|
||||
| dragOpacityConfig(v0.7.2+) | Object | { cloneNodeOpacity: 0.5, beingDragNodeOpacity: 0.3 } | The transparency configuration during node dragging, passing an object, and the field meanings are: the transparency of the cloned node or rectangle that follows the mouse movement, and the transparency of the dragged node | |
|
||||
| tagsColorMap(v0.7.2+) | Object | {} | The color of a custom node label can be transferred to an object, where key is the label content to be assigned a color, and value is the color of the label content. If not transferred internally, a corresponding color will be generated based on the label content | |
|
||||
| cooperateStyle(v0.7.3+) | Object | { avatarSize: 22, fontSize: 12 } | The configuration of personnel avatar style during node collaboration editing, with field meanings as follows: avatar size, and if it is a text avatar, the size of the text | |
|
||||
| associativeLineIsAlwaysAboveNode(v0.8.0+) | Boolean | true | Is the associated line always displayed above the node? If set to false, it will be at the top level when creating and activating the associated line, and in other cases, it will be below the node | |
|
||||
| defaultGeneralizationText(v0.8.0+) | String | 概要 | Insert default text for summary | |
|
||||
| handleIsSplitByWrapOnPasteCreateNewNode(v0.8.0+) | Function / null | null | When creating a new node by pasting text, control whether to automatically split the nodes based on line breaks. If there is a line break, multiple nodes will be created based on the line break. Otherwise, only one node will be created, and a function can be passed to return promise. resolve represents splitting based on line breaks, and reject represents ignoring line breaks | |
|
||||
| addHistoryTime(v0.8.0+) | Number | 100 | Only one historical record can be added within the specified time to avoid adding unnecessary intermediate states. Unit: ms | |
|
||||
| isDisableDrag(v0.8.1+) | Boolean | false | Is disable dragging the canvas | |
|
||||
| disableTouchZoom(v0.8.1+) | Boolean | false | Prohibit double finger scaling, you can still use the API for scaling, which takes effect on the TouchEvent plugin | |
|
||||
| highlightNodeBoxStyle(v0.9.0+) | Object | { stroke: 'rgb(94, 200, 248)', fill: 'transparent' } | Highlight box style when the mouse moves into the summary to highlight the node it belongs to | |
|
||||
| createNewNodeBehavior(v0.9.1+) | String | default | Behavior when creating a new node. default(By default, newly created nodes will be activated and enter editing mode. If multiple new nodes are created simultaneously, they will only be activated and will not enter editing mode)、notActive(Do not activate newly created nodes)、activeOnly(Only activate newly created nodes and do not enter editing mode) | |
|
||||
|
||||
### Data structure
|
||||
|
||||
@@ -209,17 +219,103 @@ Get whether a plugin is registered, The index of the plugin in the registered pl
|
||||
|
||||
List of all currently registered plugins.
|
||||
|
||||
## Instance props
|
||||
|
||||
### el
|
||||
|
||||
Container element.
|
||||
|
||||
### opt
|
||||
|
||||
Config options object.
|
||||
|
||||
### svg
|
||||
|
||||
> @svgdotjs/svg.js library calls the node instance returned by the SVG() method
|
||||
|
||||
Canvas SVG element.
|
||||
|
||||
### draw
|
||||
|
||||
> @svgdotjs/svg.js library calls the node instance returned by the group() method
|
||||
>
|
||||
> Child node of SVG node
|
||||
|
||||
Container element, used to carry content such as nodes and connections.
|
||||
|
||||
### lineDraw
|
||||
|
||||
> v0.8.0+
|
||||
>
|
||||
> @svgdotjs/svg.js library calls the node instance returned by the group() method
|
||||
>
|
||||
> Child node of draw node
|
||||
|
||||
Container for node wiring elements.
|
||||
|
||||
### nodeDraw
|
||||
|
||||
> v0.8.0+
|
||||
>
|
||||
> @svgdotjs/svg.js library calls the node instance returned by the group() method
|
||||
>
|
||||
> Child node of draw node
|
||||
|
||||
Container for node elements.
|
||||
|
||||
### associativeLineDraw
|
||||
|
||||
> v0.8.0+
|
||||
>
|
||||
> @svgdotjs/svg.js library calls the node instance returned by the group() method
|
||||
>
|
||||
> Available when the associated line plugin is registered
|
||||
>
|
||||
> Child node of draw node
|
||||
|
||||
Container for associative line content.
|
||||
|
||||
### otherDraw
|
||||
|
||||
> v0.8.0+
|
||||
>
|
||||
> @svgdotjs/svg.js library calls the node instance returned by the group() method
|
||||
>
|
||||
> Child node of draw node
|
||||
|
||||
Container for other content.
|
||||
|
||||
### elRect
|
||||
|
||||
The size and position information of the container element 'el'. The return result of calling the 'getBoundingClientRect()' method.
|
||||
|
||||
### width
|
||||
|
||||
The width of the container element 'el'.
|
||||
|
||||
### height
|
||||
|
||||
The height of the container element 'el'.
|
||||
|
||||
### themeConfig
|
||||
|
||||
Current Theme Configuration.
|
||||
|
||||
## Instance methods
|
||||
|
||||
### clearDraw()
|
||||
|
||||
> v0.8.0+
|
||||
|
||||
Clear `lineDraw`、`associativeLineDraw`、`nodeDraw`、`otherDraw` containers.
|
||||
|
||||
### destroy()
|
||||
|
||||
> v0.6.0+
|
||||
|
||||
Destroy mind maps. It will remove registered plugins, remove listening events, and delete all nodes on the canvas.
|
||||
|
||||
### getSvgData({ paddingX = 0, paddingY = 0 })
|
||||
### getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false })
|
||||
|
||||
> v0.3.0+
|
||||
|
||||
@@ -227,6 +323,8 @@ Destroy mind maps. It will remove registered plugins, remove listening events, a
|
||||
|
||||
`paddingY`: Padding y
|
||||
|
||||
`ignoreWatermark`:v0.8.0+, Do not draw watermarks. If you do not need to draw watermarks, you can pass 'true' because drawing watermarks is very slow
|
||||
|
||||
Get the `svg` data and return an object. The detailed structure is as follows:
|
||||
|
||||
```js
|
||||
@@ -312,6 +410,9 @@ Listen to an event. Event list:
|
||||
| svg_mouseleave(v0.5.1+) | Triggered when the mouse moves out of the SVG canvas | e(event object) |
|
||||
| node_icon_click(v0.6.10+) | Triggered when clicking on an icon within a node | this(node instance)、item(Click on the icon name)、e(event object) |
|
||||
| view_theme_change(v0.6.12+) | Triggered after calling the setTheme method to set the theme | theme(theme name) |
|
||||
| set_data(v0.7.3+) | Triggered when the setData method is called to dynamically set mind map data | data(New Mind Map Data) |
|
||||
| resize(v0.8.0+) | Triggered after the container size changes, actually when the 'resize' method of the mind map instance is called | |
|
||||
| beforeDestroy(v0.9.0+) | Triggered before destroying the mind map, i.e. triggered by calling the destroy method | |
|
||||
|
||||
### emit(event, ...args)
|
||||
|
||||
@@ -321,7 +422,9 @@ Trigger an event, which can be one of the events listed above or a custom event.
|
||||
|
||||
Unbind an event.
|
||||
|
||||
### setTheme(theme)
|
||||
### setTheme(theme, notRender = false)
|
||||
|
||||
- `notRender`: v0.8.0+, Is not call the render method to update the canvas.
|
||||
|
||||
Switches the theme. Available themes can be found in the options table above.
|
||||
|
||||
@@ -329,7 +432,9 @@ Switches the theme. Available themes can be found in the options table above.
|
||||
|
||||
Gets the current theme.
|
||||
|
||||
### setThemeConfig(config)
|
||||
### setThemeConfig(config, notRender = false)
|
||||
|
||||
- `notRender`: v0.8.0+, Is not call the render method to update the canvas.
|
||||
|
||||
Sets the theme configuration. `config` is the same as the `themeConfig` option
|
||||
in the options table above.
|
||||
@@ -370,7 +475,9 @@ This method only updates the configuration and has no other side effects, such a
|
||||
|
||||
Gets the current layout structure.
|
||||
|
||||
### setLayout(layout)
|
||||
### setLayout(layout, notRender = false)
|
||||
|
||||
- `notRender`: v0.8.0+, Is not call the render method to update the canvas.
|
||||
|
||||
Sets the layout structure. Available values can be found in the `layout` field
|
||||
in the options table above.
|
||||
@@ -417,7 +524,9 @@ redo. All commands are as follows:
|
||||
| GO_TARGET_NODE(v0.6.7+) | Navigate to a node, and if the node is collapsed, it will automatically expand to that node | node(Node instance or node uid to locate)、callback(v0.6.9+, Callback function after positioning completion) |
|
||||
| INSERT_MULTI_NODE(v0.7.2+) | Insert multiple sibling nodes into the specified node at the same time, with the operating node being the currently active node or the specified node | appointNodes(Optional, specify nodes, specify multiple nodes to pass an array), nodeList(Data list of newly inserted nodes, array type) |
|
||||
| INSERT_MULTI_CHILD_NODE(v0.7.2+) | Insert multiple child nodes into the specified node simultaneously, with the operation node being the currently active node or the specified node | appointNodes(Optional, specify nodes, specify multiple nodes to pass an array), childList(Data list of newly inserted nodes, array type) |
|
||||
| INSERT_FORMULA(v0.7.2+) | Insert mathematical formulas into nodes, operate on the currently active node or specified node | formula(Mathematical formula to insert, LaText syntax), appointNodes(Optional, specify the node to insert the formula into. Multiple nodes can be passed as arrays, otherwise it defaults to the currently active node) |
|
||||
| INSERT_FORMULA(v0.7.2+) | Insert mathematical formulas into nodes, operate on the currently active node or specified node | formula(Mathematical formula to insert, LaTeX syntax), appointNodes(Optional, specify the node to insert the formula into. Multiple nodes can be passed as arrays, otherwise it defaults to the currently active node) |
|
||||
| INSERT_PARENT_NODE(v0.8.0+) | Insert a parent node into the specified node, with the operation node being the currently active node or the specified node | openEdit(Activate the newly inserted node and enter editing mode, default to 'true'`)、 appointNodes(Optional, specify the node to insert into the parent node, and specify that multiple nodes can pass an array)、 appointData(Optional, specify the data for the newly created node, such as {text: 'xxx', ...}, Detailed structure can be referenced [exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js)) |
|
||||
| REMOVE_CURRENT_NODE(v0.8.0+) | Delete only the current node, operate on the currently active node or specified node | appointNodes(Optional, specify the nodes to be deleted, and multiple nodes can be passed as an array) |
|
||||
|
||||
### setData(data)
|
||||
|
||||
@@ -488,4 +597,4 @@ Register plugin, Use `MindMap.usePlugin` to register plugin only before instanti
|
||||
|
||||
> v0.4.0+
|
||||
|
||||
Remove registered plugin, Plugins registered through the `usePlugin` or `addPlugin` methods can be removed.
|
||||
Remove registered plugin, Plugins registered through the `usePlugin` or `addPlugin` methods can be removed.
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<td>customNoteContentShow(v0.1.6+)</td>
|
||||
<td>Object</td>
|
||||
<td>null</td>
|
||||
<td>Custom node note content display, object type, structure: {show: (noteContent, left, top) => {// your display node note logic }, hide: () => {// your hide node note logic }}</td>
|
||||
<td>Custom node note content display, object type, structure: {show: (noteContent, left, top, node) => {// your display node note logic. node is a new parameter added in v0.8.1+ version, representing node instances }, hide: () => {// your hide node note logic }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -137,7 +137,7 @@
|
||||
<td>enableFreeDrag(v0.2.4+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>false</td>
|
||||
<td>Enable node free drag</td>
|
||||
<td>Enable node free(Free drag means that nodes can be dragged to any position on the canvas. Please note that it is not a function of dragging nodes to become siblings of other nodes. The connection of free drag may have certain problems, so it is best not to use this feature) drag</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -164,7 +164,7 @@
|
||||
<tr>
|
||||
<td>mousewheelAction(v0.4.3+)</td>
|
||||
<td>String</td>
|
||||
<td>zoom</td>
|
||||
<td>zoom(v0.9.1+ default is move)</td>
|
||||
<td>The behavior of the mouse wheel, <code>zoom</code>(Zoom in and out)、<code>move</code>(Move up and down). If <code>customHandleMousewheel</code> passes a custom function, this property will not take effect</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
@@ -178,8 +178,8 @@
|
||||
<tr>
|
||||
<td>mousewheelZoomActionReverse(v0.6.5+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>false</td>
|
||||
<td>When <code>mousewheelAction</code> is set to <code>zoom</code>, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed</td>
|
||||
<td>false(v0.9.1+ default is true)</td>
|
||||
<td>When <code>mousewheelAction</code> is set to <code>zoom</code>, Or when holding down the Ctrl key, the default scrolling forward is to zoom out, and scrolling backward is to zoom in. If this property is set to true, it will be reversed</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -435,13 +435,20 @@
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableDblclickReset(v0.6.17+)</td>
|
||||
<td>enableDblclickReset(v0.6.17+)(v0.8.0+this attribute has been deleted)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true(v0.7.0+changed to false)</td>
|
||||
<td>Turn on the mouse and double-click to reset the position and zoom of the mind map</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableDblclickBackToRootNode(v0.8.0+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>false</td>
|
||||
<td>Whether to return to the root node when double clicking with the mouse, that is, to center the display of the root node</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>minExportImgCanvasScale(v0.7.0+)</td>
|
||||
<td>Number</td>
|
||||
<td>2</td>
|
||||
@@ -518,6 +525,69 @@
|
||||
<td>The color of a custom node label can be transferred to an object, where key is the label content to be assigned a color, and value is the color of the label content. If not transferred internally, a corresponding color will be generated based on the label content</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>cooperateStyle(v0.7.3+)</td>
|
||||
<td>Object</td>
|
||||
<td>{ avatarSize: 22, fontSize: 12 }</td>
|
||||
<td>The configuration of personnel avatar style during node collaboration editing, with field meanings as follows: avatar size, and if it is a text avatar, the size of the text</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>associativeLineIsAlwaysAboveNode(v0.8.0+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>Is the associated line always displayed above the node? If set to false, it will be at the top level when creating and activating the associated line, and in other cases, it will be below the node</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>defaultGeneralizationText(v0.8.0+)</td>
|
||||
<td>String</td>
|
||||
<td>概要</td>
|
||||
<td>Insert default text for summary</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>handleIsSplitByWrapOnPasteCreateNewNode(v0.8.0+)</td>
|
||||
<td>Function / null</td>
|
||||
<td>null</td>
|
||||
<td>When creating a new node by pasting text, control whether to automatically split the nodes based on line breaks. If there is a line break, multiple nodes will be created based on the line break. Otherwise, only one node will be created, and a function can be passed to return promise. resolve represents splitting based on line breaks, and reject represents ignoring line breaks</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>addHistoryTime(v0.8.0+)</td>
|
||||
<td>Number</td>
|
||||
<td>100</td>
|
||||
<td>Only one historical record can be added within the specified time to avoid adding unnecessary intermediate states. Unit: ms</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>isDisableDrag(v0.8.1+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>false</td>
|
||||
<td>Is disable dragging the canvas</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>disableTouchZoom(v0.8.1+)</td>
|
||||
<td>Boolean</td>
|
||||
<td>false</td>
|
||||
<td>Prohibit double finger scaling, you can still use the API for scaling, which takes effect on the TouchEvent plugin</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>highlightNodeBoxStyle(v0.9.0+)</td>
|
||||
<td>Object</td>
|
||||
<td>{ stroke: 'rgb(94, 200, 248)', fill: 'transparent' }</td>
|
||||
<td>Highlight box style when the mouse moves into the summary to highlight the node it belongs to</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>createNewNodeBehavior(v0.9.1+)</td>
|
||||
<td>String</td>
|
||||
<td>default</td>
|
||||
<td>Behavior when creating a new node. default(By default, newly created nodes will be activated and enter editing mode. If multiple new nodes are created simultaneously, they will only be activated and will not enter editing mode)、notActive(Do not activate newly created nodes)、activeOnly(Only activate newly created nodes and do not enter editing mode)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Data structure</h3>
|
||||
@@ -672,18 +742,77 @@ mindMap.setTheme(<span class="hljs-string">'Theme name'</span>)
|
||||
<p>v0.3.0+</p>
|
||||
</blockquote>
|
||||
<p>List of all currently registered plugins.</p>
|
||||
<h2>Instance props</h2>
|
||||
<h3>el</h3>
|
||||
<p>Container element.</p>
|
||||
<h3>opt</h3>
|
||||
<p>Config options object.</p>
|
||||
<h3>svg</h3>
|
||||
<blockquote>
|
||||
<p>@svgdotjs/svg.js library calls the node instance returned by the SVG() method</p>
|
||||
</blockquote>
|
||||
<p>Canvas SVG element.</p>
|
||||
<h3>draw</h3>
|
||||
<blockquote>
|
||||
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
|
||||
<p>Child node of SVG node</p>
|
||||
</blockquote>
|
||||
<p>Container element, used to carry content such as nodes and connections.</p>
|
||||
<h3>lineDraw</h3>
|
||||
<blockquote>
|
||||
<p>v0.8.0+</p>
|
||||
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
|
||||
<p>Child node of draw node</p>
|
||||
</blockquote>
|
||||
<p>Container for node wiring elements.</p>
|
||||
<h3>nodeDraw</h3>
|
||||
<blockquote>
|
||||
<p>v0.8.0+</p>
|
||||
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
|
||||
<p>Child node of draw node</p>
|
||||
</blockquote>
|
||||
<p>Container for node elements.</p>
|
||||
<h3>associativeLineDraw</h3>
|
||||
<blockquote>
|
||||
<p>v0.8.0+</p>
|
||||
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
|
||||
<p>Available when the associated line plugin is registered</p>
|
||||
<p>Child node of draw node</p>
|
||||
</blockquote>
|
||||
<p>Container for associative line content.</p>
|
||||
<h3>otherDraw</h3>
|
||||
<blockquote>
|
||||
<p>v0.8.0+</p>
|
||||
<p>@svgdotjs/svg.js library calls the node instance returned by the group() method</p>
|
||||
<p>Child node of draw node</p>
|
||||
</blockquote>
|
||||
<p>Container for other content.</p>
|
||||
<h3>elRect</h3>
|
||||
<p>The size and position information of the container element 'el'. The return result of calling the 'getBoundingClientRect()' method.</p>
|
||||
<h3>width</h3>
|
||||
<p>The width of the container element 'el'.</p>
|
||||
<h3>height</h3>
|
||||
<p>The height of the container element 'el'.</p>
|
||||
<h3>themeConfig</h3>
|
||||
<p>Current Theme Configuration.</p>
|
||||
<h2>Instance methods</h2>
|
||||
<h3>clearDraw()</h3>
|
||||
<blockquote>
|
||||
<p>v0.8.0+</p>
|
||||
</blockquote>
|
||||
<p>Clear <code>lineDraw</code>、<code>associativeLineDraw</code>、<code>nodeDraw</code>、<code>otherDraw</code> containers.</p>
|
||||
<h3>destroy()</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.0+</p>
|
||||
</blockquote>
|
||||
<p>Destroy mind maps. It will remove registered plugins, remove listening events, and delete all nodes on the canvas.</p>
|
||||
<h3>getSvgData({ paddingX = 0, paddingY = 0 })</h3>
|
||||
<h3>getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false })</h3>
|
||||
<blockquote>
|
||||
<p>v0.3.0+</p>
|
||||
</blockquote>
|
||||
<p><code>paddingX</code>: Padding x</p>
|
||||
<p><code>paddingY</code>: Padding y</p>
|
||||
<p><code>ignoreWatermark</code>:v0.8.0+, Do not draw watermarks. If you do not need to draw watermarks, you can pass 'true' because drawing watermarks is very slow</p>
|
||||
<p>Get the <code>svg</code> data and return an object. The detailed structure is as follows:</p>
|
||||
<pre class="hljs"><code>{
|
||||
svg, <span class="hljs-comment">// Element, the overall svg element of the mind map graphics, including: svg (canvas container), g (actual mind map group)</span>
|
||||
@@ -921,17 +1050,38 @@ poor performance and should be used sparingly.</p>
|
||||
<td>Triggered after calling the setTheme method to set the theme</td>
|
||||
<td>theme(theme name)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>set_data(v0.7.3+)</td>
|
||||
<td>Triggered when the setData method is called to dynamically set mind map data</td>
|
||||
<td>data(New Mind Map Data)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>resize(v0.8.0+)</td>
|
||||
<td>Triggered after the container size changes, actually when the 'resize' method of the mind map instance is called</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>beforeDestroy(v0.9.0+)</td>
|
||||
<td>Triggered before destroying the mind map, i.e. triggered by calling the destroy method</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>emit(event, ...args)</h3>
|
||||
<p>Trigger an event, which can be one of the events listed above or a custom event.</p>
|
||||
<h3>off(event, fn)</h3>
|
||||
<p>Unbind an event.</p>
|
||||
<h3>setTheme(theme)</h3>
|
||||
<h3>setTheme(theme, notRender = false)</h3>
|
||||
<ul>
|
||||
<li><code>notRender</code>: v0.8.0+, Is not call the render method to update the canvas.</li>
|
||||
</ul>
|
||||
<p>Switches the theme. Available themes can be found in the options table above.</p>
|
||||
<h3>getTheme()</h3>
|
||||
<p>Gets the current theme.</p>
|
||||
<h3>setThemeConfig(config)</h3>
|
||||
<h3>setThemeConfig(config, notRender = false)</h3>
|
||||
<ul>
|
||||
<li><code>notRender</code>: v0.8.0+, Is not call the render method to update the canvas.</li>
|
||||
</ul>
|
||||
<p>Sets the theme configuration. <code>config</code> is the same as the <code>themeConfig</code> option
|
||||
in the options table above.</p>
|
||||
<h3>getCustomThemeConfig()</h3>
|
||||
@@ -957,7 +1107,10 @@ in the options table above.</p>
|
||||
<p>This method only updates the configuration and has no other side effects, such as triggering canvas re-rendering</p>
|
||||
<h3>getLayout()</h3>
|
||||
<p>Gets the current layout structure.</p>
|
||||
<h3>setLayout(layout)</h3>
|
||||
<h3>setLayout(layout, notRender = false)</h3>
|
||||
<ul>
|
||||
<li><code>notRender</code>: v0.8.0+, Is not call the render method to update the canvas.</li>
|
||||
</ul>
|
||||
<p>Sets the layout structure. Available values can be found in the <code>layout</code> field
|
||||
in the options table above.</p>
|
||||
<h3>execCommand(name, ...args)</h3>
|
||||
@@ -1150,7 +1303,17 @@ redo. All commands are as follows:</p>
|
||||
<tr>
|
||||
<td>INSERT_FORMULA(v0.7.2+)</td>
|
||||
<td>Insert mathematical formulas into nodes, operate on the currently active node or specified node</td>
|
||||
<td>formula(Mathematical formula to insert, LaText syntax), appointNodes(Optional, specify the node to insert the formula into. Multiple nodes can be passed as arrays, otherwise it defaults to the currently active node)</td>
|
||||
<td>formula(Mathematical formula to insert, LaTeX syntax), appointNodes(Optional, specify the node to insert the formula into. Multiple nodes can be passed as arrays, otherwise it defaults to the currently active node)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>INSERT_PARENT_NODE(v0.8.0+)</td>
|
||||
<td>Insert a parent node into the specified node, with the operation node being the currently active node or the specified node</td>
|
||||
<td>openEdit(Activate the newly inserted node and enter editing mode, default to 'true'`)、 appointNodes(Optional, specify the node to insert into the parent node, and specify that multiple nodes can pass an array)、 appointData(Optional, specify the data for the newly created node, such as {text: 'xxx', ...}, Detailed structure can be referenced <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js">exampleData.js</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>REMOVE_CURRENT_NODE(v0.8.0+)</td>
|
||||
<td>Delete only the current node, operate on the currently active node or specified node</td>
|
||||
<td>appointNodes(Optional, specify the nodes to be deleted, and multiple nodes can be passed as an array)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
124
web/src/pages/Doc/en/cooperate/index.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Cooperate plugin beta
|
||||
|
||||
> v0.7.3+
|
||||
|
||||
This plugin is used to achieve collaborative editing.
|
||||
|
||||
## Introduce
|
||||
|
||||
This plugin implements collaborative editing through [Yjs](https://github.com/yjs/yjs). The basic principle is to convert the tree data of the mind map into flat object data, and then collaborate through shared data of type [Y.Map](https://docs.yjs.dev/api/shared-types/y.map). That is, when certain operations are performed on the canvas, the 'y.map' object will be updated, and other collaborative clients will receive the updated data, convert it back to tree structure data, and update the canvas to achieve real-time updates.
|
||||
|
||||
To achieve collaboration, the backend is indispensable, and 'Yjs' provides some [Connection Providers](https://docs.yjs.dev/ecosystem/connection-provider). At the same time, it also provides examples of the backend, but it is only the simplest implementation. In actual projects, you should need to rewrite or improve it.
|
||||
|
||||
You can choose the 'Provider' that suits you, and the default is [y-webrtc](https://github.com/yjs/y-webrtc).
|
||||
|
||||
## demo
|
||||
|
||||
If you want to try it through demo, you can do the following steps:
|
||||
|
||||
1. Clone project and installation dependencies:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/wanglin2/mind-map.git
|
||||
cd mind-map
|
||||
cd simple-mind-map
|
||||
npm i
|
||||
npm link
|
||||
cd ..
|
||||
cd web
|
||||
npm i
|
||||
npm link simple-mind-map
|
||||
```
|
||||
|
||||
2. Modify `web/src/pages/Edit/components/Edit.vue` file
|
||||
|
||||
To register Cooperate plugin, uncomment the line:
|
||||
|
||||
```js
|
||||
// .usePlugin(Cooperate)// Cooperate plugin
|
||||
```
|
||||
|
||||
Change the signaling server address to your local IP:
|
||||
|
||||
```js
|
||||
// cooperateTest function
|
||||
|
||||
signalingList: ['ws://【your ip】:4444']
|
||||
```
|
||||
|
||||
3. To register a collaborative plugin, uncomment the line:
|
||||
|
||||
```bash
|
||||
// Execute under web path
|
||||
npm run serve
|
||||
```
|
||||
|
||||
4. Start signaling server:
|
||||
|
||||
```bash
|
||||
// Execute under simple-mind-map path
|
||||
npm run wsServe
|
||||
```
|
||||
|
||||
The command executes the 'simple mind map/bin/wsServer.mjs' file, which is directly copied from the [y webrtc](https://github.com/yjs/y-webrtc) repository and may not be complete. Please be cautious when using it for actual projects.
|
||||
|
||||
5. Access the service address in two browsers:
|
||||
|
||||
```
|
||||
http://【your ip】:8080/#/?userName=userName
|
||||
```
|
||||
|
||||
You can set different userNames on different browsers. Then you can edit in one browser and see the automatic update in another browser.
|
||||
|
||||
## Register
|
||||
|
||||
```js
|
||||
import MindMap from 'simple-mind-map'
|
||||
import Cooperate from 'simple-mind-map/src/plugins/Cooperate.js'
|
||||
MindMap.usePlugin(Cooperate)
|
||||
```
|
||||
|
||||
After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.cooperate`.
|
||||
|
||||
## Methods
|
||||
|
||||
### getDoc()
|
||||
|
||||
Obtain Yjs doc instance.
|
||||
|
||||
### setProvider(provider, webrtcProviderConfig)
|
||||
|
||||
- `provider`: The connection provider for Yjs can refer to the [Connection Provider](https://docs.yjs.dev/ecosystem/connection-provider), default is `null`
|
||||
|
||||
- `webrtcProviderConfig`: the options of webrtc provider, An object needs to be passed in the following format:
|
||||
|
||||
```js
|
||||
{
|
||||
roomName: '', // Mandatory, room name
|
||||
signalingList: [''],// Mandatory, specify signaling server
|
||||
...// The other config of webrtc provider
|
||||
}
|
||||
```
|
||||
|
||||
For detailed configuration, please refer to [y-webrtc](https://github.com/yjs/y-webrtc)。
|
||||
|
||||
Set the connection provider for Yjs. If 'provider' is not transmitted, 'y webrtc' will be used by default. You can also use other 'providers'.
|
||||
|
||||
If the default 'y webrtc' is used, the necessary configuration needs to be passed in through the second parameter.
|
||||
|
||||
`simple-mind-map/bin/wsServer.mjs` file provides a simple signaling server code for testing and reference.
|
||||
|
||||
### setUserInfo(userInfo)
|
||||
|
||||
- `userInfo`: User information. The format is as follows:
|
||||
|
||||
```js
|
||||
{
|
||||
id: '', // Mandatory, user's unique ID
|
||||
name: '', // User name. Only one name and avatar can be transmitted. If both are transmitted, avatar will be displayed
|
||||
avatar: '', // User profile
|
||||
color: '' // If there is no avatar, the first character of the name will be displayed as a circle, and the color of the text will be white. The color of the circle can be set through this field
|
||||
}
|
||||
```
|
||||
|
||||
Set the current user's information for synchronization and display of perceptual data. If other collaborators activate a node, their avatar will be displayed above that node in your current canvas.
|
||||